1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-02 05:30:17 +08:00

Merge pull request #30634 from smoogipoo/multiplayer-remove-cmc-and-composite

Remove `CachedModelDependencyContainer` and `OnlinePlayComposite` from multiplayer
This commit is contained in:
Dean Herbert
2024-11-20 19:07:28 +09:00
committed by GitHub
Unverified
110 changed files with 2421 additions and 1744 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1115.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1118.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
var newRoom = new Room();
newRoom.CopyFrom(SelectedRoom.Value);
newRoom.RoomID.Value = null;
newRoom.RoomID = null;
MultiplayerClient.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
@@ -39,18 +39,18 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
var room = new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Daily Challenge: June 4, 2024" },
RoomID = 1234,
Name = "Daily Challenge: June 4, 2024",
Playlist =
{
[
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
Category = { Value = RoomCategory.DailyChallenge }
],
EndDate = DateTimeOffset.Now.AddHours(12),
Category = RoomCategory.DailyChallenge
};
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
@@ -62,18 +62,18 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
var room = new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Daily Challenge: June 4, 2024" },
RoomID = 1234,
Name = "Daily Challenge: June 4, 2024",
Playlist =
{
[
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
Category = { Value = RoomCategory.DailyChallenge }
],
EndDate = DateTimeOffset.Now.AddHours(12),
Category = RoomCategory.DailyChallenge
};
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
@@ -91,18 +91,18 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
var room = new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Daily Challenge: June 4, 2024" },
RoomID = 1234,
Name = "Daily Challenge: June 4, 2024",
Playlist =
{
[
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
Category = { Value = RoomCategory.DailyChallenge }
],
EndDate = DateTimeOffset.Now.AddHours(12),
Category = RoomCategory.DailyChallenge
};
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
@@ -26,11 +26,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
private readonly Bindable<Room> room = new Bindable<Room>(new Room());
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { BindTarget = room }
};
[Test]
public void TestBasicAppearance()
{
@@ -98,7 +93,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
Origin = Anchor.Centre,
Children = new Drawable[]
{
new DailyChallengeTimeRemainingRing(),
new DailyChallengeTimeRemainingRing(room.Value),
breakdown = new DailyChallengeScoreBreakdown(),
}
}
@@ -125,8 +120,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge
AddSliderStep("update time remaining", 0f, 1f, 0f, progress =>
{
var startedTimeAgo = TimeSpan.FromHours(24) * progress;
room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo;
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo;
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
AddStep("add normal score", () =>
{
@@ -68,19 +68,19 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
API.Perform(new CreateRoomRequest(room = new Room
{
RoomID = { Value = roomId },
Name = { Value = "Daily Challenge: June 4, 2024" },
RoomID = roomId,
Name = "Daily Challenge: June 4, 2024",
Playlist =
{
[
new PlaylistItem(CreateAPIBeatmap(new OsuRuleset().RulesetInfo))
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
StartDate = { Value = DateTimeOffset.Now },
EndDate = { Value = DateTimeOffset.Now.AddHours(24) },
Category = { Value = RoomCategory.DailyChallenge }
],
StartDate = DateTimeOffset.Now,
EndDate = DateTimeOffset.Now.AddHours(24),
Category = RoomCategory.DailyChallenge
}));
});
AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId }));
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
return false;
};
});
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
@@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
return false;
};
});
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = 1 }, new PlaylistItem(Beatmap.Value.BeatmapInfo))
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
@@ -18,11 +18,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
{
private readonly Bindable<Room> room = new Bindable<Room>(new Room());
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { BindTarget = room }
};
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
@@ -38,7 +33,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
ring = new DailyChallengeTimeRemainingRing
ring = new DailyChallengeTimeRemainingRing(room.Value)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
@@ -59,29 +54,29 @@ namespace osu.Game.Tests.Visual.DailyChallenge
AddStep("just started", () =>
{
room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1);
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now.AddMinutes(-1);
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
AddStep("midway through", () =>
{
room.Value.StartDate.Value = DateTimeOffset.Now.AddHours(-12);
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now.AddHours(-12);
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
AddStep("nearing end", () =>
{
room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-1).AddMinutes(8);
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now.AddDays(-1).AddMinutes(8);
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
AddStep("already ended", () =>
{
room.Value.StartDate.Value = DateTimeOffset.Now.AddDays(-2);
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now.AddDays(-2);
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
AddSliderStep("manual progress", 0f, 1f, 0f, progress =>
{
var startedTimeAgo = TimeSpan.FromHours(24) * progress;
room.Value.StartDate.Value = DateTimeOffset.Now - startedTimeAgo;
room.Value.EndDate.Value = room.Value.StartDate.Value.Value.AddDays(1);
room.Value.StartDate = DateTimeOffset.Now - startedTimeAgo;
room.Value.EndDate = room.Value.StartDate.Value.AddDays(1);
});
}
}
@@ -42,14 +42,14 @@ namespace osu.Game.Tests.Visual.Menus
beatmap.OnlineID = 1001;
getRoomRequest.TriggerSuccess(new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Aug 8, 2024" },
RoomID = 1234,
Name = "Aug 8, 2024",
Playlist =
{
[
new PlaylistItem(beatmap)
},
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) },
EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) }
],
StartDate = DateTimeOffset.Now.AddMinutes(-30),
EndDate = DateTimeOffset.Now.AddSeconds(60)
});
return true;
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -30,16 +28,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
protected abstract QueueMode Mode { get; }
protected BeatmapInfo InitialBeatmap { get; private set; }
protected BeatmapInfo OtherBeatmap { get; private set; }
protected BeatmapInfo InitialBeatmap { get; private set; } = null!;
protected BeatmapInfo OtherBeatmap { get; private set; } = null!;
protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen;
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps;
private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
private TestMultiplayerComponents multiplayerComponents;
private TestMultiplayerComponents multiplayerComponents = null!;
protected TestMultiplayerClient MultiplayerClient => multiplayerComponents.MultiplayerClient;
@@ -75,15 +73,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for lounge", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);
AddStep("open room", () => multiplayerComponents.ChildrenOfType<LoungeSubScreen>().Single().Open(new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = Mode },
Name = "Test Room",
QueueMode = Mode,
Playlist =
{
[
new PlaylistItem(InitialBeatmap)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
}));
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
@@ -98,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestCreatedWithCorrectMode()
{
AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == Mode);
AddUntilStep("room created with correct mode", () => MultiplayerClient.ClientAPIRoom?.QueueMode == Mode);
}
protected void RunGameplay()
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using System.Threading;
@@ -10,6 +8,7 @@ using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Testing;
@@ -25,14 +24,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private readonly Room room = new Room
{
HasPassword = { Value = true }
Password = "*"
};
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
private DrawableLoungeRoom drawableRoom;
private SearchTextBox searchTextBox;
private DrawableLoungeRoom drawableRoom = null!;
private SearchTextBox searchTextBox = null!;
private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim();
@@ -78,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SelectedRoom = new Bindable<Room?>()
}
}
};
@@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestFocusViaKeyboardCommit()
{
DrawableLoungeRoom.PasswordEntryPopover popover = null;
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
AddStep("click room twice", () =>
@@ -103,11 +103,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("enter password", () => popover.ChildrenOfType<OsuPasswordTextBox>().Single().Text = "password");
AddStep("commit via enter", () => InputManager.Key(Key.Enter));
AddAssert("popover has focus", () => checkFocus(popover));
AddAssert("popover has focus", () => checkFocus(popover!));
AddStep("attempt another enter", () => InputManager.Key(Key.Enter));
AddAssert("popover still has focus", () => checkFocus(popover));
AddAssert("popover still has focus", () => checkFocus(popover!));
AddStep("unblock response", () => allowResponseCallback.Set());
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestFocusViaMouseCommit()
{
DrawableLoungeRoom.PasswordEntryPopover popover = null;
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
AddStep("click room twice", () =>
@@ -144,11 +144,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
AddAssert("popover has focus", () => checkFocus(popover));
AddAssert("popover has focus", () => checkFocus(popover!));
AddStep("attempt another click", () => InputManager.Click(MouseButton.Left));
AddAssert("popover still has focus", () => checkFocus(popover));
AddAssert("popover still has focus", () => checkFocus(popover!));
AddStep("unblock response", () => allowResponseCallback.Set());
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using NUnit.Framework;
@@ -32,15 +30,40 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
private readonly Bindable<Room?> selectedRoom = new Bindable<Room?>();
[Test]
public void TestMultipleStatuses()
{
FillFlowContainer rooms = null;
FillFlowContainer rooms = null!;
AddStep("create rooms", () =>
{
PlaylistItem item1 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = { StarRating = 2.5 }
}.BeatmapInfo);
PlaylistItem item2 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo = { StarRating = 4.5 }
}.BeatmapInfo);
PlaylistItem item3 = new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5,
Metadata =
{
Artist = "very very very very very very very very very long artist",
ArtistUnicode = "very very very very very very very very very long artist",
Title = "very very very very very very very very very very very long title",
TitleUnicode = "very very very very very very very very very very very long title",
}
}
}.BeatmapInfo);
Child = rooms = new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -52,86 +75,48 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createLoungeRoom(new Room
{
Name = { Value = "Multiplayer room" },
Status = { Value = new RoomStatusOpen() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5
}
}.BeatmapInfo)
}
Name = "Multiplayer room",
Status = new RoomStatusOpen(),
EndDate = DateTimeOffset.Now.AddDays(1),
Type = MatchType.HeadToHead,
Playlist = [item1],
CurrentPlaylistItem = item1
}),
createLoungeRoom(new Room
{
Name = { Value = "Private room" },
Status = { Value = new RoomStatusOpenPrivate() },
HasPassword = { Value = true },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5,
Metadata =
{
Artist = "very very very very very very very very very long artist",
ArtistUnicode = "very very very very very very very very very long artist",
Title = "very very very very very very very very very very very long title",
TitleUnicode = "very very very very very very very very very very very long title",
}
}
}.BeatmapInfo)
}
Name = "Private room",
Status = new RoomStatusOpenPrivate(),
Password = "*",
EndDate = DateTimeOffset.Now.AddDays(1),
Type = MatchType.HeadToHead,
Playlist = [item3],
CurrentPlaylistItem = item3
}),
createLoungeRoom(new Room
{
Name = { Value = "Playlist room with multiple beatmaps" },
Status = { Value = new RoomStatusPlaying() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Playlist =
{
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5
}
}.BeatmapInfo),
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 4.5
}
}.BeatmapInfo)
}
Name = "Playlist room with multiple beatmaps",
Status = new RoomStatusPlaying(),
EndDate = DateTimeOffset.Now.AddDays(1),
Playlist = [item1, item2],
CurrentPlaylistItem = item1
}),
createLoungeRoom(new Room
{
Name = { Value = "Finished room" },
Status = { Value = new RoomStatusEnded() },
EndDate = { Value = DateTimeOffset.Now },
Name = "Finished room",
Status = new RoomStatusEnded(),
EndDate = DateTimeOffset.Now,
}),
createLoungeRoom(new Room
{
Name = { Value = "Spotlight room" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Spotlight },
Name = "Spotlight room",
Status = new RoomStatusOpen(),
Category = RoomCategory.Spotlight,
}),
createLoungeRoom(new Room
{
Name = { Value = "Featured artist room" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.FeaturedArtist },
Name = "Featured artist room",
Status = new RoomStatusOpen(),
Category = RoomCategory.FeaturedArtist,
}),
}
};
@@ -145,24 +130,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestEnableAndDisablePassword()
{
DrawableRoom drawableRoom = null;
Room room = null;
DrawableRoom drawableRoom = null!;
Room room = null!;
AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room
{
Name = { Value = "Room with password" },
Status = { Value = new RoomStatusOpen() },
Type = { Value = MatchType.HeadToHead },
Name = "Room with password",
Status = new RoomStatusOpen(),
Type = MatchType.HeadToHead,
}));
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<DrawableRoomParticipantsList>().Any());
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("set password", () => room.Password.Value = "password");
AddStep("set password", () => room.Password = "password");
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("unset password", () => room.Password.Value = string.Empty);
AddStep("unset password", () => room.Password = string.Empty);
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
}
@@ -179,43 +164,52 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
new DrawableMatchRoom(new Room
{
Name = { Value = "A host-only room" },
QueueMode = { Value = QueueMode.HostOnly },
Type = { Value = MatchType.HeadToHead }
}),
Name = "A host-only room",
QueueMode = QueueMode.HostOnly,
Type = MatchType.HeadToHead,
})
{
SelectedItem = new Bindable<PlaylistItem?>()
},
new DrawableMatchRoom(new Room
{
Name = { Value = "An all-players, team-versus room" },
QueueMode = { Value = QueueMode.AllPlayers },
Type = { Value = MatchType.TeamVersus }
}),
Name = "An all-players, team-versus room",
QueueMode = QueueMode.AllPlayers,
Type = MatchType.TeamVersus
})
{
SelectedItem = new Bindable<PlaylistItem?>()
},
new DrawableMatchRoom(new Room
{
Name = { Value = "A round-robin room" },
QueueMode = { Value = QueueMode.AllPlayersRoundRobin },
Type = { Value = MatchType.HeadToHead }
}),
Name = "A round-robin room",
QueueMode = QueueMode.AllPlayersRoundRobin,
Type = MatchType.HeadToHead
})
{
SelectedItem = new Bindable<PlaylistItem?>()
},
}
});
}
private DrawableRoom createLoungeRoom(Room room)
{
room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 };
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
if (room.RecentParticipants.Count == 0)
{
room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new APIUser
room.RecentParticipants = Enumerable.Range(0, 20).Select(i => new APIUser
{
Id = i,
Username = $"User {i}"
}));
}).ToArray();
}
return new DrawableLoungeRoom(room)
{
MatchingFilter = true,
SelectedRoom = { BindTarget = selectedRoom }
SelectedRoom = selectedRoom
};
}
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -17,7 +15,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneDrawableRoomParticipantsList : OnlinePlayTestScene
{
private DrawableRoomParticipantsList list;
private DrawableRoomParticipantsList list = null!;
public override void SetUpSteps()
{
@@ -27,18 +25,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room
{
Name = { Value = "test room" },
Host =
Name = "test room",
Host = new APIUser
{
Value = new APIUser
{
Id = 2,
Username = "peppy",
}
Id = 2,
Username = "peppy",
}
};
Child = list = new DrawableRoomParticipantsList
Child = list = new DrawableRoomParticipantsList(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -143,18 +138,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void addUser(int id)
{
SelectedRoom.Value.RecentParticipants.Add(new APIUser
SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Append(new APIUser
{
Id = id,
Username = $"User {id}"
});
SelectedRoom.Value.ParticipantCount.Value++;
}).ToArray();
SelectedRoom.Value.ParticipantCount++;
}
private void removeUserAt(int index)
{
SelectedRoom.Value.RecentParticipants.RemoveAt(index);
SelectedRoom.Value.ParticipantCount.Value--;
SelectedRoom.Value.RecentParticipants = SelectedRoom.Value.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value.RecentParticipants[index])).ToArray();
SelectedRoom.Value.ParticipantCount--;
}
}
}
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = QueueMode.AllPlayers
}).WaitSafely());
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
}
[Test]
@@ -55,20 +55,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("has 5 rooms", () => container.Rooms.Count == 5);
AddAssert("all spotlights at top", () => container.Rooms
.SkipWhile(r => r.Room.Category.Value == RoomCategory.Spotlight)
.All(r => r.Room.Category.Value == RoomCategory.Normal));
.SkipWhile(r => r.Room.Category == RoomCategory.Spotlight)
.All(r => r.Room.Category == RoomCategory.Normal));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID.Value == 0)));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0)));
AddAssert("has 4 rooms", () => container.Rooms.Count == 4);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0));
AddStep("select first room", () => container.Rooms.First().TriggerClick());
AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight)));
AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID?.Value)));
AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight)));
AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID)));
AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight)));
AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight)));
AddAssert("selection vacated", () => checkRoomSelected(null));
}
@@ -182,11 +182,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public });
AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword.Value));
AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword));
AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private });
AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword.Value));
AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword));
}
[Test]
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room();
Child = new MatchBeatmapDetailArea
Child = new MatchBeatmapDetailArea(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewItem()
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = SelectedRoom.Value.Playlist.Count,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
@@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
new APIMod(new OsuModDoubleTime()),
new APIMod(new OsuModAutoplay())
}
});
}).ToArray();
}
}
}
@@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create leaderboard", () =>
{
SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
SelectedRoom.Value = new Room { RoomID = 3 };
Child = new MatchLeaderboard
Child = new MatchLeaderboard(SelectedRoom.Value)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -43,12 +43,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private OsuButton readyButton => control.ChildrenOfType<OsuButton>().Single();
[Cached(typeof(IBindable<PlaylistItem>))]
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent)) { Model = { BindTarget = room } };
[BackgroundDependencyLoader]
private void load()
{
@@ -107,31 +101,33 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUpSteps]
public void SetUpSteps()
{
PlaylistItem item = null!;
AddStep("reset state", () =>
{
multiplayerClient.Invocations.Clear();
beatmapAvailability.Value = BeatmapAvailability.LocallyAvailable();
currentItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID
};
room.Value = new Room
{
Playlist = { currentItem.Value },
CurrentPlaylistItem = { BindTarget = currentItem }
Playlist = [item],
CurrentPlaylistItem = item
};
localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id) { User = API.LocalUser.Value };
localUser = new MultiplayerRoomUser(API.LocalUser.Value.Id)
{
User = API.LocalUser.Value
};
multiplayerRoom = new MultiplayerRoom(0)
{
Playlist =
{
TestMultiplayerClient.CreateMultiplayerPlaylistItem(currentItem.Value),
},
Playlist = { TestMultiplayerClient.CreateMultiplayerPlaylistItem(item) },
Users = { localUser },
Host = localUser,
};
@@ -144,6 +140,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(250, 50),
SelectedItem = new Bindable<PlaylistItem?>(item)
};
});
}
@@ -103,14 +103,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddRepeatStep("random stuff happens", performRandomAction, 30);
@@ -238,17 +238,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1);
AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1);
AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
}
@@ -259,14 +259,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
}, API.LocalUser.Value);
});
@@ -288,14 +288,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
}, API.LocalUser.Value);
});
@@ -308,7 +308,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount.Value == 1);
AddUntilStep("Check participant count correct", () => multiplayerClient.ClientAPIRoom?.ParticipantCount == 1);
AddUntilStep("Check participant list contains user", () => multiplayerClient.ClientAPIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
}
@@ -317,18 +317,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Password = { Value = "password" },
Name = "Test Room",
Password = "password",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password");
AddUntilStep("room has password", () => multiplayerClient.ClientAPIRoom?.Password == "password");
}
[Test]
@@ -338,15 +338,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
Password = { Value = "password" },
Name = "Test Room",
Password = "password",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
}, API.LocalUser.Value);
});
@@ -370,19 +370,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Password = { Value = "password" },
Name = "Test Room",
Password = "password",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddStep("change password", () => multiplayerClient.ChangeSettings(password: "password2"));
AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password.Value == "password2");
AddUntilStep("local password changed", () => multiplayerClient.ClientAPIRoom?.Password == "password2");
}
[Test]
@@ -401,14 +401,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
pressReadyButton();
@@ -430,8 +430,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
Name = "Test Room",
Playlist = [item]
};
});
@@ -471,8 +471,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
Name = "Test Room",
Playlist = [item]
};
});
@@ -512,8 +512,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
return new Room
{
Name = { Value = "Test Room" },
Playlist = { item }
Name = "Test Room",
Playlist = [item]
};
});
@@ -548,14 +548,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddStep("join other user (ready, host)", () =>
@@ -581,14 +581,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddStep("delete beatmap", () => beatmaps.Delete(importedSet));
@@ -620,14 +620,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddStep("disconnect", () => multiplayerClient.Disconnect());
@@ -639,15 +639,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModHidden()) }
}
}
]
});
AddStep("open mod overlay", () => this.ChildrenOfType<UserModSelectButton>().Single().TriggerClick());
@@ -679,14 +679,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
enterGameplay();
@@ -724,14 +724,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
enterGameplay();
@@ -754,14 +754,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
pressReadyButton();
@@ -791,15 +791,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
roomManager.AddServerSideRoom(new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
}, API.LocalUser.Value);
});
@@ -810,12 +810,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("disable polling", () => this.ChildrenOfType<ListingPollingComponent>().Single().TimeBetweenPolls.Value = 0);
AddStep("change server-side settings", () =>
{
roomManager.ServerSideRooms[0].Name.Value = "New name";
roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
ID = 2,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
});
roomManager.ServerSideRooms[0].Name = "New name";
roomManager.ServerSideRooms[0].Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
ID = 2,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
];
});
AddStep("join room", () => InputManager.Key(Key.Enter));
@@ -825,8 +828,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("local room has correct settings", () =>
{
var localRoom = this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().Room;
return localRoom.Name.Value == roomManager.ServerSideRooms[0].Name.Value
&& localRoom.Playlist.SequenceEqual(roomManager.ServerSideRooms[0].Playlist);
return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
});
}
@@ -836,15 +838,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
@@ -872,15 +874,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
AddStep("set spectating state", () => multiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
@@ -911,15 +913,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
enterGameplay();
@@ -942,15 +944,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
enterGameplay();
@@ -976,14 +978,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddStep("join other user and make host", () =>
@@ -1022,10 +1024,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
QueueMode = { Value = QueueMode.AllPlayers },
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
@@ -1036,7 +1038,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod { Acronym = "HD" } },
},
}
]
});
AddStep("select hidden", () => multiplayerClient.ChangeUserMods(new[] { new APIMod { Acronym = "HD" } }));
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -13,9 +12,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
{
[Cached(typeof(IBindable<PlaylistItem>))]
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
public override void SetUpSteps()
{
base.SetUpSteps();
@@ -33,7 +29,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 50,
Child = new MultiplayerMatchFooter()
Child = new MultiplayerMatchFooter
{
SelectedItem = new Bindable<PlaylistItem?>()
}
}
};
});
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("load match", () =>
{
SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
SelectedRoom.Value = new Room { Name = "Test Room" };
LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value));
});
@@ -77,33 +77,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest]
/*
* Fail rate around 1.5%
*
* TearDown : System.AggregateException : One or more errors occurred. (Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index'))
----> System.ArgumentOutOfRangeException : Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index')
* --TearDown
* at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
* at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
* at osu.Framework.Extensions.TaskExtensions.WaitSafely(Task task)
* at osu.Framework.Testing.TestScene.checkForErrors()
* at osu.Framework.Testing.TestScene.RunTestsFromNUnit()
*--ArgumentOutOfRangeException
* at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
* at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
* at osu.Framework.Bindables.BindableList`1.removeAt(Int32 index, BindableList`1 caller)
* at osu.Game.Online.Multiplayer.MultiplayerClient.<>c__DisplayClass106_0.<PlaylistItemChanged>b__0() in C:\BuildAgent\work\ecd860037212ac52\osu.Game\Online\Multiplayer\MultiplayerClient .cs:line 702
* at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal()
*/
public void TestCreatedRoom()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -112,16 +96,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestTaikoOnlyMod()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new TaikoModSwap()) }
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new TaikoModSwap()) }
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -133,32 +119,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestSettingValidity()
{
AddAssert("create button not enabled", () => !this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
AddAssert("create button enabled", () => this.ChildrenOfType<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().Single().Enabled.Value);
}
[Test]
[FlakyTest] // See above
public void TestStartMatchWhileSpectating()
{
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -179,16 +169,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestFreeModSelectionHasAllowedMods()
{
AddStep("add playlist item with allowed mod", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -206,16 +198,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestModSelectKeyWithAllowedMods()
{
AddStep("add playlist item with allowed mod", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -228,15 +222,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestModSelectKeyWithNoAllowedMods()
{
AddStep("add playlist item with no allowed mods", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
});
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -249,13 +245,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestNextPlaylistItemSelectedAfterCompletion()
{
AddStep("add two playlist items", () =>
{
SelectedRoom.Value.Playlist.AddRange(new[]
{
SelectedRoom.Value.Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
@@ -264,7 +259,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
});
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -286,24 +281,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
[FlakyTest] // See above
public void TestModSelectOverlay()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
new APIMod(new OsuModStrictTracking()),
},
AllowedMods = new[]
{
new APIMod(new OsuModFlashlight()),
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
new APIMod(new OsuModStrictTracking()),
},
AllowedMods = new[]
{
new APIMod(new OsuModFlashlight()),
}
}
});
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerPlayer : MultiplayerTestScene
{
private MultiplayerPlayer player;
private MultiplayerPlayer player = null!;
[Test]
public void TestGameplay()
@@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("score changed", () => player.GameplayState.ScoreProcessor.TotalScore.Value > 0);
}
private void setup(Func<IReadOnlyList<Mod>> mods = null)
private void setup(Func<IReadOnlyList<Mod>>? mods = null)
{
AddStep("set beatmap", () =>
{
@@ -64,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo)
Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.ServerAPIRoom!, new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
}, MultiplayerClient.ServerRoom?.Users.ToArray()));
}, MultiplayerClient.ServerRoom!.Users.ToArray()));
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded);
@@ -28,9 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerPlaylist : MultiplayerTestScene
{
[Cached(typeof(IBindable<PlaylistItem>))]
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
private MultiplayerPlaylist list = null!;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
@@ -51,12 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create list", () =>
{
Child = list = new MultiplayerPlaylist
Child = list = new MultiplayerPlaylist(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.4f, 0.8f)
Size = new Vector2(0.4f, 0.8f),
SelectedItem = new Bindable<PlaylistItem?>()
};
});
@@ -166,9 +164,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
RoomManager.CreateRoom(new Room
{
Name = { Value = "test name" },
Name = "test name",
Playlist =
{
[
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
{
RulesetID = Ruleset.Value.OnlineID
@@ -178,7 +176,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
RulesetID = Ruleset.Value.OnlineID,
Expired = true
}
}
]
});
});
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using NUnit.Framework;
@@ -27,10 +25,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerQueueList : MultiplayerTestScene
{
private MultiplayerQueueList playlist;
private BeatmapManager beatmaps;
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
private MultiplayerQueueList playlist = null!;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
private BeatmapInfo importedBeatmap = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create playlist", () =>
{
Child = playlist = new MultiplayerQueueList
Child = playlist = new MultiplayerQueueList(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 300),
Items = { BindTarget = MultiplayerClient.ClientAPIRoom!.Playlist }
};
MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(Room.Playlist))
playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom.Playlist);
};
});
@@ -69,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestDeleteButtonAlwaysVisibleForHost()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
assertDeleteButtonVisibility(1, true);
@@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
AddStep("join other user", () => MultiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("set other user as host", () => MultiplayerClient.TransferHost(1234));
@@ -100,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestSingleItemDoesNotHaveDeleteButton()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
assertDeleteButtonVisibility(0, false);
}
@@ -109,7 +112,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestCurrentItemHasDeleteButtonIfNotSingle()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
@@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerSpectateButton : MultiplayerTestScene
{
[Cached(typeof(IBindable<PlaylistItem>))]
private readonly Bindable<PlaylistItem> currentItem = new Bindable<PlaylistItem>();
private MultiplayerSpectateButton spectateButton = null!;
private MatchStartControl startControl = null!;
@@ -51,13 +48,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create button", () =>
{
AvailabilityTracker.SelectedItem.BindTo(currentItem);
PlaylistItem item = SelectedRoom.Value.Playlist.First();
AvailabilityTracker.SelectedItem.Value = item;
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
currentItem.Value = SelectedRoom.Value.Playlist.First();
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
@@ -72,12 +69,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
SelectedItem = new Bindable<PlaylistItem?>(item)
},
startControl = new MatchStartControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
SelectedItem = new Bindable<PlaylistItem?>(item)
}
}
}
@@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestItemAddedWhenCreateNewItemClicked()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
public void TestItemNotAddedIfExistingOnStart()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("finalise selection", () => songSelect.FinaliseSelection());
AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
@@ -89,24 +89,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestAddSameItemMultipleTimes()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2);
}
[Test]
public void TestAddItemAfterRearrangement()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("rearrange", () =>
{
var item = SelectedRoom.Value.Playlist[0];
SelectedRoom.Value.Playlist.RemoveAt(0);
SelectedRoom.Value.Playlist.Add(item);
});
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("rearrange", () => SelectedRoom.Value.Playlist = SelectedRoom.Value.Playlist.Skip(1).Append(SelectedRoom.Value.Playlist[0]).ToArray());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2);
}
@@ -117,9 +112,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestNewItemHasNewModInstances()
{
AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("item 1 has rate 1.5", () =>
{
@@ -150,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
mod = (OsuModDoubleTime)SelectedMods.Value[0];
});
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
AddAssert("item has rate 1.5", () =>
@@ -9,7 +9,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
namespace osu.Game.Tests.Visual.Multiplayer
@@ -18,10 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private readonly Mock<MultiplayerClient> multiplayerClient = new Mock<MultiplayerClient>();
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
// not used directly in component, but required due to it inheriting from OnlinePlayComposite.
new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader]
private void load()
{
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value = new Room();
Child = new StarRatingRangeDisplay
Child = new StarRatingRangeDisplay(SelectedRoom.Value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
@@ -33,11 +33,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.AddRange(new[]
{
SelectedRoom.Value.Playlist =
[
new PlaylistItem(new BeatmapInfo { StarRating = min }),
new PlaylistItem(new BeatmapInfo { StarRating = max }),
});
];
});
}
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using NUnit.Framework;
@@ -29,10 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneTeamVersus : ScreenTestScene
{
private BeatmapManager beatmaps;
private BeatmapSetInfo importedSet;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
private TestMultiplayerComponents multiplayerComponents;
private TestMultiplayerComponents multiplayerComponents = null!;
private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient;
@@ -64,15 +62,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Type = { Value = MatchType.TeamVersus },
Name = "Test Room",
Type = MatchType.TeamVersus,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddUntilStep("room type is team vs", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.TeamVersus);
@@ -84,15 +82,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Type = { Value = MatchType.TeamVersus },
Name = "Test Room",
Type = MatchType.TeamVersus,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
]
});
AddUntilStep("user on team 0", () => (multiplayerClient.ClientRoom?.Users.FirstOrDefault()?.MatchState as TeamVersusUserState)?.TeamID == 0);
@@ -121,25 +119,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Type = { Value = MatchType.HeadToHead },
Name = "Test Room",
Type = MatchType.HeadToHead,
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.HeadToHead);
AddUntilStep("match type head to head", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.HeadToHead);
AddStep("change match type", () => multiplayerClient.ChangeSettings(new MultiplayerRoomSettings
{
MatchType = MatchType.TeamVersus
}).WaitSafely());
AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type.Value == MatchType.TeamVersus);
AddUntilStep("api room updated to team versus", () => multiplayerClient.ClientAPIRoom?.Type == MatchType.TeamVersus);
}
[Test]
@@ -147,14 +145,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Name = "Test Room",
Playlist =
{
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
]
});
AddUntilStep("room type is head to head", () => multiplayerClient.ClientRoom?.Settings.MatchType == MatchType.HeadToHead);
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using NUnit.Framework;
using osu.Framework.Bindables;
@@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestRoomSettings settings;
private TestRoomSettings settings = null!;
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
@@ -47,19 +45,19 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("clear name and beatmap", () =>
{
SelectedRoom.Value.Name.Value = "";
SelectedRoom.Value.Playlist.Clear();
SelectedRoom.Value.Name = "";
SelectedRoom.Value.Playlist = [];
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name");
AddStep("set name", () => SelectedRoom.Value.Name = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)));
AddStep("set beatmap", () => SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]);
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => SelectedRoom.Value.Name.Value = "");
AddStep("clear name", () => SelectedRoom.Value.Name = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
@@ -69,13 +67,13 @@ namespace osu.Game.Tests.Visual.Playlists
const string expected_name = "expected name";
TimeSpan expectedDuration = TimeSpan.FromMinutes(15);
Room createdRoom = null;
Room createdRoom = null!;
AddStep("setup", () =>
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = r =>
{
@@ -85,8 +83,8 @@ namespace osu.Game.Tests.Visual.Playlists
});
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("has correct name", () => createdRoom.Name.Value == expected_name);
AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration);
AddAssert("has correct name", () => createdRoom.Name == expected_name);
AddAssert("has correct duration", () => createdRoom.Duration == expectedDuration);
}
[Test]
@@ -94,14 +92,14 @@ namespace osu.Game.Tests.Visual.Playlists
{
const string not_found_prefix = "beatmaps not found:";
string errorMessage = null;
string errorMessage = null!;
AddStep("setup", () =>
{
var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo;
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap));
SelectedRoom.Value.Name = "Test Room";
SelectedRoom.Value.Playlist = [new PlaylistItem(beatmap)];
errorMessage = $"{not_found_prefix} {beatmap.OnlineID}";
@@ -127,8 +125,8 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
SelectedRoom.Value.Name = "Test Room";
SelectedRoom.Value.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = _ => failText;
});
@@ -169,7 +167,7 @@ namespace osu.Game.Tests.Visual.Playlists
protected class TestRoomManager : IRoomManager
{
public Func<Room, string> CreateRequested;
public Func<Room, string>? CreateRequested;
public event Action RoomsUpdated
{
@@ -187,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void ClearRooms() => throw new NotImplementedException();
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
public void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
if (CreateRequested == null)
return;
@@ -200,7 +198,7 @@ namespace osu.Game.Tests.Visual.Playlists
onSuccess?.Invoke(room);
}
public void JoinRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null) => throw new NotImplementedException();
public void JoinRoom(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
@@ -19,17 +20,16 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("create list", () =>
{
SelectedRoom.Value = new Room { RoomID = { Value = 7 } };
for (int i = 0; i < 50; i++)
SelectedRoom.Value = new Room
{
SelectedRoom.Value.RecentParticipants.Add(new APIUser
RoomID = 7,
RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser
{
Username = "peppy",
Statistics = new UserStatistics { GlobalRank = 1234 },
Id = 2
});
}
}).ToArray()
};
});
}
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
Child = new ParticipantsDisplay(Direction.Horizontal)
Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Horizontal)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
Child = new ParticipantsDisplay(Direction.Vertical)
Child = new ParticipantsDisplay(SelectedRoom.Value, Direction.Vertical)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -35,11 +32,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsRoomCreation : OnlinePlayTestScene
{
private BeatmapManager manager;
private TestPlaylistsRoomSubScreen match;
private BeatmapSetInfo importedBeatmap;
private BeatmapManager manager = null!;
private TestPlaylistsRoomSubScreen match = null!;
private BeatmapSetInfo importedBeatmap = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -52,11 +47,11 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUpSteps]
public void SetupSteps()
{
AddStep("set room", () => SelectedRoom!.Value = new Room());
AddStep("set room", () => SelectedRoom.Value = new Room());
importBeatmap();
AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom!.Value)));
AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value)));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
@@ -65,14 +60,17 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host.Value);
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
room.Name = "my awesome room";
room.Host = API.LocalUser.Value;
room.RecentParticipants = [room.Host];
room.EndDate = DateTimeOffset.Now.AddMinutes(5);
room.Playlist =
[
new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
AddUntilStep("Progress details are hidden", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent!.Alpha == 0);
@@ -88,15 +86,18 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
room.Name.Value = "my awesome room";
room.MaxAttempts.Value = 5;
room.Host.Value = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host.Value);
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
room.Name = "my awesome room";
room.MaxAttempts = 5;
room.Host = API.LocalUser.Value;
room.RecentParticipants = [room.Host];
room.EndDate = DateTimeOffset.Now.AddMinutes(5);
room.Playlist =
[
new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
AddUntilStep("Progress details are visible", () => match.ChildrenOfType<RoomLocalUserInfo>().FirstOrDefault()?.Parent!.Alpha == 1);
@@ -107,21 +108,24 @@ namespace osu.Game.Tests.Visual.Playlists
{
setupAndCreateRoom(room =>
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
room.Name = "my awesome room";
room.Host = API.LocalUser.Value;
room.Playlist =
[
new PlaylistItem(importedBeatmap.Beatmaps.First())
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom!.Value.Playlist[0]);
AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]);
}
[Test]
public void TestBeatmapUpdatedOnReImport()
{
string realHash = null;
string realHash = null!;
int realOnlineId = 0;
int realOnlineSetId = 0;
@@ -139,40 +143,40 @@ namespace osu.Game.Tests.Visual.Playlists
BeatmapInfo =
{
OnlineID = realOnlineId,
Metadata = new BeatmapMetadata(),
BeatmapSet =
{
OnlineID = realOnlineSetId
}
Metadata = new BeatmapMetadata()
},
};
Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null);
modifiedBeatmap.BeatmapInfo.BeatmapSet!.OnlineID = realOnlineSetId;
modifiedBeatmap.HitObjects.Clear();
modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
Debug.Assert(modifiedBeatmap.BeatmapInfo.BeatmapSet != null);
manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet);
});
// Create the room using the real beatmap values.
setupAndCreateRoom(room =>
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
room.Playlist.Add(new PlaylistItem(new BeatmapInfo
{
MD5Hash = realHash,
OnlineID = realOnlineId,
Metadata = new BeatmapMetadata(),
BeatmapSet = new BeatmapSetInfo
room.Name = "my awesome room";
room.Host = API.LocalUser.Value;
room.Playlist =
[
new PlaylistItem(new BeatmapInfo
{
OnlineID = realOnlineSetId,
MD5Hash = realHash,
OnlineID = realOnlineId,
Metadata = new BeatmapMetadata(),
BeatmapSet = new BeatmapSetInfo
{
OnlineID = realOnlineSetId,
}
})
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
})
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
];
});
AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
@@ -181,17 +185,11 @@ namespace osu.Game.Tests.Visual.Playlists
{
var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
OnlineID = realOnlineId,
BeatmapSet =
{
OnlineID = realOnlineSetId
}
},
BeatmapInfo = { OnlineID = realOnlineId },
};
Debug.Assert(originalBeatmap.BeatmapInfo.BeatmapSet != null);
originalBeatmap.BeatmapInfo.BeatmapSet.OnlineID = realOnlineSetId;
manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet);
});
@@ -201,7 +199,7 @@ namespace osu.Game.Tests.Visual.Playlists
private void setupAndCreateRoom(Action<Room> room)
{
AddStep("setup room", () => room(SelectedRoom!.Value));
AddStep("setup room", () => room(SelectedRoom.Value));
AddStep("click create button", () =>
{
@@ -215,8 +213,7 @@ namespace osu.Game.Tests.Visual.Playlists
var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)?.Value.Detach();
importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach();
});
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
@@ -226,8 +223,7 @@ namespace osu.Game.Tests.Visual.Playlists
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
[Resolved(canBeNull: true)]
[CanBeNull]
private IDialogOverlay dialogOverlay { get; set; }
private IDialogOverlay? dialogOverlay { get; set; }
public TestPlaylistsRoomSubScreen(Room room)
: base(room)
@@ -53,13 +53,13 @@ namespace osu.Game.Tests.Visual.UserInterface
beatmap.OnlineID = 1001;
getRoomRequest.TriggerSuccess(new Room
{
RoomID = { Value = 1234 },
RoomID = 1234,
Playlist =
{
[
new PlaylistItem(beatmap)
},
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) },
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
],
StartDate = DateTimeOffset.Now.AddMinutes(-5),
EndDate = DateTimeOffset.Now.AddSeconds(30)
});
return true;
@@ -131,13 +131,13 @@ namespace osu.Game.Tests.Visual.UserInterface
beatmap.OnlineID = 1001;
getRoomRequest.TriggerSuccess(new Room
{
RoomID = { Value = 1234 },
RoomID = 1234,
Playlist =
{
[
new PlaylistItem(beatmap)
},
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-50) },
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
],
StartDate = DateTimeOffset.Now.AddMinutes(-50),
EndDate = DateTimeOffset.Now.AddSeconds(30)
});
return true;
+1
View File
@@ -0,0 +1 @@
osu
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="0959115c-c7d5-4a9d-b138-d34f47e6713a" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 1
}]]></component>
<component name="ProjectId" id="2p6efoueruThVmDy8XXhD8Ibwab" />
<component name="ProjectViewState">
<option name="autoscrollFromSource" value="true" />
<option name="autoscrollToSource" value="true" />
<option name="flattenPackages" value="true" />
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
</project>
@@ -14,9 +14,17 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary>
public partial class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<IBeatmapInfo>
{
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
public readonly Bindable<IBeatmapInfo?> Beatmap = new Bindable<IBeatmapInfo?>();
protected override double LoadDelay => 500;
/// <summary>
/// Delay before the background is loaded while on-screen.
/// </summary>
public double BackgroundLoadDelay { get; set; } = 500;
/// <summary>
/// Delay before the background is unloaded while off-screen.
/// </summary>
public double BackgroundUnloadDelay { get; set; } = 10000;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
@@ -29,10 +37,9 @@ namespace osu.Game.Beatmaps.Drawables
this.beatmapSetCoverType = beatmapSetCoverType;
}
/// <summary>
/// Delay before the background is unloaded while off-screen.
/// </summary>
protected virtual double UnloadDelay => 10000;
protected override double LoadDelay => BackgroundLoadDelay;
protected virtual double UnloadDelay => BackgroundUnloadDelay;
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad) =>
new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay) { RelativeSizeAxes = Axes.Both };
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -181,10 +180,10 @@ namespace osu.Game.Online.Multiplayer
await joinOrLeaveTaskChain.Add(async () =>
{
Debug.Assert(room.RoomID.Value != null);
Debug.Assert(room.RoomID != null);
// Join the server-side room.
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
Debug.Assert(joinedRoom != null);
// Populate users.
@@ -201,12 +200,11 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(joinedRoom.Playlist.Count > 0);
APIRoom.Playlist.Clear();
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(item => new PlaylistItem(item)));
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
APIRoom.Playlist = joinedRoom.Playlist.Select(item => new PlaylistItem(item)).ToArray();
APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
// The server will null out the end date upon the host joining the room, but the null value is never communicated to the client.
APIRoom.EndDate.Value = null;
APIRoom.EndDate = null;
Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser);
@@ -397,15 +395,15 @@ namespace osu.Game.Online.Multiplayer
switch (state)
{
case MultiplayerRoomState.Open:
APIRoom.Status.Value = APIRoom.HasPassword.Value ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen();
break;
case MultiplayerRoomState.Playing:
APIRoom.Status.Value = new RoomStatusPlaying();
APIRoom.Status = new RoomStatusPlaying();
break;
case MultiplayerRoomState.Closed:
APIRoom.Status.Value = new RoomStatusEnded();
APIRoom.Status = new RoomStatusEnded();
break;
}
@@ -459,7 +457,7 @@ namespace osu.Game.Online.Multiplayer
if (apiUser == null || apiRoom == null) return;
PostNotification?.Invoke(
new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name.Value))
new UserAvatarNotification(apiUser, NotificationsStrings.InvitedYouToTheMultiplayer(apiUser.Username, apiRoom.Name))
{
Activated = () =>
{
@@ -487,12 +485,12 @@ namespace osu.Game.Online.Multiplayer
{
Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.Add(user.User ?? new APIUser
APIRoom.RecentParticipants = APIRoom.RecentParticipants.Append(user.User ?? new APIUser
{
Id = user.UserID,
Username = "[Unresolved]"
});
APIRoom.ParticipantCount.Value++;
}).ToArray();
APIRoom.ParticipantCount++;
}
private Task handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
@@ -506,8 +504,8 @@ namespace osu.Game.Online.Multiplayer
PlayingUserIds.Remove(user.UserID);
Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID);
APIRoom.ParticipantCount.Value--;
APIRoom.RecentParticipants = APIRoom.RecentParticipants.Where(u => u.Id != user.UserID).ToArray();
APIRoom.ParticipantCount--;
callback?.Invoke(user);
RoomUpdated?.Invoke();
@@ -528,7 +526,7 @@ namespace osu.Game.Online.Multiplayer
var user = Room.Users.FirstOrDefault(u => u.UserID == userId);
Room.Host = user;
APIRoom.Host.Value = user?.User;
APIRoom.Host = user?.User;
RoomUpdated?.Invoke();
}, false);
@@ -734,7 +732,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Room.Playlist.Add(item);
APIRoom.Playlist.Add(new PlaylistItem(item));
APIRoom.Playlist = APIRoom.Playlist.Append(new PlaylistItem(item)).ToArray();
ItemAdded?.Invoke(item);
RoomUpdated?.Invoke();
@@ -753,7 +751,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(APIRoom != null);
Room.Playlist.Remove(Room.Playlist.Single(existing => existing.ID == playlistItemId));
APIRoom.Playlist.RemoveAll(existing => existing.ID == playlistItemId);
APIRoom.Playlist = APIRoom.Playlist.Where(i => i.ID != playlistItemId).ToArray();
Debug.Assert(Room.Playlist.Count > 0);
@@ -771,30 +769,10 @@ namespace osu.Game.Online.Multiplayer
if (Room == null)
return;
try
{
Debug.Assert(APIRoom != null);
Debug.Assert(APIRoom != null);
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
APIRoom.Playlist.RemoveAt(existingIndex);
APIRoom.Playlist.Insert(existingIndex, new PlaylistItem(item));
}
catch (Exception ex)
{
// Temporary code to attempt to figure out long-term failing tests.
StringBuilder exceptionText = new StringBuilder();
exceptionText.AppendLine("MultiplayerClient test failure investigation");
exceptionText.AppendLine($"Exception : {ex.ToString()}");
exceptionText.AppendLine($"Lookup : {item.ID}");
exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}");
exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}");
throw new AggregateException(exceptionText.ToString());
}
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
APIRoom.Playlist = APIRoom.Playlist.Select((pi, i) => pi.ID == item.ID ? new PlaylistItem(item) : APIRoom.Playlist[i]).ToArray();
ItemChanged?.Invoke(item);
RoomUpdated?.Invoke();
@@ -841,14 +819,14 @@ namespace osu.Game.Online.Multiplayer
// Update a few properties of the room instantaneously.
Room.Settings = settings;
APIRoom.Name.Value = Room.Settings.Name;
APIRoom.Password.Value = Room.Settings.Password;
APIRoom.Status.Value = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
APIRoom.Type.Value = Room.Settings.MatchType;
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
APIRoom.AutoSkip.Value = Room.Settings.AutoSkip;
APIRoom.Name = Room.Settings.Name;
APIRoom.Password = Room.Settings.Password;
APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate();
APIRoom.Type = Room.Settings.MatchType;
APIRoom.QueueMode = Room.Settings.QueueMode;
APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration;
APIRoom.CurrentPlaylistItem = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
APIRoom.AutoSkip = Room.Settings.AutoSkip;
RoomUpdated?.Invoke();
}
+5 -5
View File
@@ -44,12 +44,12 @@ namespace osu.Game.Online.Rooms
// API doesn't populate status so let's do it here.
foreach (var room in Response)
{
if (room.EndDate.Value != null && DateTimeOffset.Now >= room.EndDate.Value)
room.Status.Value = new RoomStatusEnded();
else if (room.HasPassword.Value)
room.Status.Value = new RoomStatusOpenPrivate();
if (room.EndDate != null && DateTimeOffset.Now >= room.EndDate)
room.Status = new RoomStatusEnded();
else if (room.HasPassword)
room.Status = new RoomStatusOpenPrivate();
else
room.Status.Value = new RoomStatusOpen();
room.Status = new RoomStatusOpen();
}
}
}
+1 -1
View File
@@ -27,6 +27,6 @@ namespace osu.Game.Online.Rooms
return req;
}
protected override string Target => $@"rooms/{Room.RoomID.Value}/users/{User!.Id}";
protected override string Target => $@"rooms/{Room.RoomID}/users/{User!.Id}";
}
}
+1 -1
View File
@@ -23,6 +23,6 @@ namespace osu.Game.Online.Rooms
return req;
}
protected override string Target => $"rooms/{room.RoomID.Value}/users/{User!.Id}";
protected override string Target => $"rooms/{room.RoomID}/users/{User!.Id}";
}
}
+2 -3
View File
@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Linq;
using Humanizer;
using Humanizer.Localisation;
using osu.Framework.Bindables;
using osu.Game.Rulesets;
using osu.Game.Utils;
@@ -30,7 +29,7 @@ namespace osu.Game.Online.Rooms
/// or the last-played <see cref="PlaylistItem"/> if all items are expired,
/// or <see langword="null"/> if <paramref name="playlist"/> was empty.
/// </summary>
public static PlaylistItem? GetCurrentItem(this ICollection<PlaylistItem> playlist)
public static PlaylistItem? GetCurrentItem(this IReadOnlyCollection<PlaylistItem> playlist)
{
if (playlist.Count == 0)
return null;
@@ -43,7 +42,7 @@ namespace osu.Game.Online.Rooms
/// <summary>
/// Returns the total duration from the <see cref="PlaylistItem"/> in playlist order from the supplied <paramref name="playlist"/>,
/// </summary>
public static string GetTotalDuration(this BindableList<PlaylistItem> playlist, RulesetStore rulesetStore) =>
public static string GetTotalDuration(this IReadOnlyList<PlaylistItem> playlist, RulesetStore rulesetStore) =>
playlist.Select(p =>
{
double rate = 1;
+14 -11
View File
@@ -120,18 +120,21 @@ namespace osu.Game.Online.Rooms
#endregion
public PlaylistItem With(Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default) => new PlaylistItem(beatmap.GetOr(Beatmap))
public PlaylistItem With(Optional<long> id = default, Optional<IBeatmapInfo> beatmap = default, Optional<ushort?> playlistOrder = default)
{
ID = ID,
OwnerID = OwnerID,
RulesetID = RulesetID,
Expired = Expired,
PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
PlayedAt = PlayedAt,
AllowedMods = AllowedMods,
RequiredMods = RequiredMods,
valid = { Value = Valid.Value },
};
return new PlaylistItem(beatmap.GetOr(Beatmap))
{
ID = id.GetOr(ID),
OwnerID = OwnerID,
RulesetID = RulesetID,
Expired = Expired,
PlaylistOrder = playlistOrder.GetOr(PlaylistOrder),
PlayedAt = PlayedAt,
AllowedMods = AllowedMods,
RequiredMods = RequiredMods,
valid = { Value = Valid.Value },
};
}
public bool Equals(PlaylistItem? other)
=> ID == other?.ID
+354 -171
View File
@@ -1,13 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.IO.Serialization.Converters;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
@@ -16,162 +15,332 @@ using osu.Game.Online.Rooms.RoomStatuses;
namespace osu.Game.Online.Rooms
{
[JsonObject(MemberSerialization.OptIn)]
public partial class Room : IDependencyInjectionCandidate
public partial class Room : INotifyPropertyChanged
{
[Cached]
[JsonProperty("id")]
public readonly Bindable<long?> RoomID = new Bindable<long?>();
public event PropertyChangedEventHandler? PropertyChanged;
[Cached]
[JsonProperty("name")]
public readonly Bindable<string> Name = new Bindable<string>();
[Cached]
[JsonProperty("host")]
public readonly Bindable<APIUser> Host = new Bindable<APIUser>();
[Cached]
[JsonProperty("playlist")]
public readonly BindableList<PlaylistItem> Playlist = new BindableList<PlaylistItem>();
[Cached]
[JsonProperty("channel_id")]
public readonly Bindable<int> ChannelId = new Bindable<int>();
[JsonProperty("current_playlist_item")]
[Cached]
public readonly Bindable<PlaylistItem> CurrentPlaylistItem = new Bindable<PlaylistItem>();
[JsonProperty("playlist_item_stats")]
[Cached]
public readonly Bindable<RoomPlaylistItemStats> PlaylistItemStats = new Bindable<RoomPlaylistItemStats>();
[JsonProperty("difficulty_range")]
[Cached]
public readonly Bindable<RoomDifficultyRange> DifficultyRange = new Bindable<RoomDifficultyRange>();
[Cached]
public readonly Bindable<RoomCategory> Category = new Bindable<RoomCategory>();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
[JsonProperty("category")]
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
private RoomCategory category
/// <summary>
/// The online room ID. Will be <c>null</c> while the room has not yet been created.
/// </summary>
public long? RoomID
{
get => Category.Value;
set => Category.Value = value;
get => roomId;
set => SetField(ref roomId, value);
}
[Cached]
public readonly Bindable<int?> MaxAttempts = new Bindable<int?>();
[Cached]
public readonly Bindable<RoomStatus> Status = new Bindable<RoomStatus>(new RoomStatusOpen());
[Cached]
public readonly Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
[Cached]
public readonly Bindable<MatchType> Type = new Bindable<MatchType>();
// Todo: osu-framework bug (https://github.com/ppy/osu-framework/issues/4106)
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
[JsonProperty("type")]
private MatchType type
/// <summary>
/// The room name.
/// </summary>
public string Name
{
get => Type.Value;
set => Type.Value = value;
get => name;
set => SetField(ref name, value);
}
[Cached]
public readonly Bindable<QueueMode> QueueMode = new Bindable<QueueMode>();
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
[JsonProperty("queue_mode")]
private QueueMode queueMode
/// <summary>
/// Sets the room password. Will be <c>null</c> after the room is created.
/// </summary>
/// <remarks>
/// To check if the room has a password, use <see cref="HasPassword"/>.
/// </remarks>
public string? Password
{
get => QueueMode.Value;
set => QueueMode.Value = value;
}
[Cached]
public readonly Bindable<TimeSpan> AutoStartDuration = new Bindable<TimeSpan>();
[JsonProperty("auto_start_duration")]
private ushort autoStartDuration
{
get => (ushort)AutoStartDuration.Value.TotalSeconds;
set => AutoStartDuration.Value = TimeSpan.FromSeconds(value);
}
[Cached]
public readonly Bindable<int?> MaxParticipants = new Bindable<int?>();
[Cached]
[JsonProperty("current_user_score")]
public readonly Bindable<PlaylistAggregateScore> UserScore = new Bindable<PlaylistAggregateScore>();
[JsonProperty("has_password")]
public readonly Bindable<bool> HasPassword = new Bindable<bool>();
[Cached]
[JsonProperty("recent_participants")]
public readonly BindableList<APIUser> RecentParticipants = new BindableList<APIUser>();
[Cached]
[JsonProperty("participant_count")]
public readonly Bindable<int> ParticipantCount = new Bindable<int>();
#region Properties only used for room creation request
[Cached(Name = nameof(Password))]
[JsonProperty("password")]
public readonly Bindable<string> Password = new Bindable<string>();
[Cached]
public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>();
[JsonProperty("duration")]
private int? duration
{
get => (int?)Duration.Value?.TotalMinutes;
get => password;
set
{
if (value == null)
Duration.Value = null;
else
Duration.Value = TimeSpan.FromMinutes(value.Value);
SetField(ref password, value);
HasPassword = !string.IsNullOrEmpty(value);
}
}
#endregion
/// <summary>
/// Whether the room has a password.
/// </summary>
/// <remarks>
/// To set a password, use <see cref="Password"/>.
/// </remarks>
[JsonProperty("has_password")]
public bool HasPassword
{
get => hasPassword;
private set => SetField(ref hasPassword, value);
}
/// <summary>
/// The room host. Will be <c>null</c> while the room has not yet been created.
/// </summary>
public APIUser? Host
{
get => host;
set => SetField(ref host, value);
}
/// <summary>
/// The room category.
/// </summary>
public RoomCategory Category
{
get => category;
set => SetField(ref category, value);
}
/// <summary>
/// The duration for which the room will be open. Will be <c>null</c> after the room is created.
/// </summary>
/// <remarks>
/// To check the room end time, use <see cref="EndDate"/>.
/// </remarks>
public TimeSpan? Duration
{
get => duration == null ? null : TimeSpan.FromMinutes(duration.Value);
set => SetField(ref duration, value == null ? null : (int)value.Value.TotalMinutes);
}
/// <summary>
/// The date at which the room was opened. Will be <c>null</c> while the room has not yet been created.
/// </summary>
public DateTimeOffset? StartDate
{
get => startDate;
set => SetField(ref startDate, value);
}
/// <summary>
/// The date at which the room will be closed.
/// </summary>
/// <remarks>
/// To set the room duration, use <see cref="Duration"/>.
/// </remarks>
public DateTimeOffset? EndDate
{
get => endDate;
set => SetField(ref endDate, value);
}
/// <summary>
/// The maximum number of users allowed in the room.
/// </summary>
public int? MaxParticipants
{
get => maxParticipants;
set => SetField(ref maxParticipants, value);
}
/// <summary>
/// The current number of users in the room.
/// </summary>
public int ParticipantCount
{
get => participantCount;
set => SetField(ref participantCount, value);
}
/// <summary>
/// The set of most recent participants in the room.
/// </summary>
public IReadOnlyList<APIUser> RecentParticipants
{
get => recentParticipants;
set => SetList(ref recentParticipants, value);
}
/// <summary>
/// The match type.
/// </summary>
public MatchType Type
{
get => type;
set => SetField(ref type, value);
}
/// <summary>
/// The maximum number of attempts on the playlist. Only valid for playlist rooms.
/// </summary>
public int? MaxAttempts
{
get => maxAttempts;
set => SetField(ref maxAttempts, value);
}
/// <summary>
/// The room playlist.
/// </summary>
public IReadOnlyList<PlaylistItem> Playlist
{
get => playlist;
set => SetList(ref playlist, value);
}
/// <summary>
/// Describes the items in the playlist.
/// </summary>
public RoomPlaylistItemStats? PlaylistItemStats
{
get => playlistItemStats;
set => SetField(ref playlistItemStats, value);
}
/// <summary>
/// Describes the range of difficulty of the room.
/// </summary>
public RoomDifficultyRange? DifficultyRange
{
get => difficultyRange;
set => SetField(ref difficultyRange, value);
}
/// <summary>
/// The playlist queueing mode. Only valid for multiplayer rooms.
/// </summary>
public QueueMode QueueMode
{
get => queueMode;
set => SetField(ref queueMode, value);
}
/// <summary>
/// Whether to automatically skip map intros. Only valid for multiplayer rooms.
/// </summary>
public bool AutoSkip
{
get => autoSkip;
set => SetField(ref autoSkip, value);
}
/// <summary>
/// The amount of time before the match is automatically started. Only valid for multiplayer rooms.
/// </summary>
public TimeSpan AutoStartDuration
{
get => TimeSpan.FromSeconds(autoStartDuration);
set => SetField(ref autoStartDuration, (ushort)value.TotalSeconds);
}
/// <summary>
/// Provides some extra scoring statistics for the local user in the room.
/// </summary>
public PlaylistAggregateScore? UserScore
{
get => userScore;
set => SetField(ref userScore, value);
}
/// <summary>
/// Represents the current item selected within the room.
/// </summary>
/// <remarks>
/// Only valid for room listing requests (i.e. in the lounge screen), and may not be valid while inside the room.
/// </remarks>
public PlaylistItem? CurrentPlaylistItem
{
get => currentPlaylistItem;
set => SetField(ref currentPlaylistItem, value);
}
/// <summary>
/// The chat channel id for the room. Will be <c>0</c> while the room has not yet been created.
/// </summary>
public int ChannelId
{
get => channelId;
private set => SetField(ref channelId, value);
}
/// <summary>
/// The current room status.
/// </summary>
public RoomStatus Status
{
get => status;
set => SetField(ref status, value);
}
/// <summary>
/// Describes which players are able to join the room.
/// </summary>
public RoomAvailability Availability
{
get => availability;
set => SetField(ref availability, value);
}
[JsonProperty("id")]
private long? roomId;
[JsonProperty("name")]
private string name = string.Empty;
[JsonProperty("password")]
private string? password;
// Not serialised (internal use only).
private bool hasPassword;
[JsonProperty("host")]
private APIUser? host;
[JsonProperty("category")]
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
private RoomCategory category;
[JsonProperty("duration")]
private int? duration;
// Only supports retrieval for now
[Cached]
[JsonProperty("starts_at")]
public readonly Bindable<DateTimeOffset?> StartDate = new Bindable<DateTimeOffset?>();
private DateTimeOffset? startDate;
// Only supports retrieval for now
[Cached]
[JsonProperty("ends_at")]
public readonly Bindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
private DateTimeOffset? endDate;
// Not yet serialised (not implemented).
private int? maxParticipants;
[JsonProperty("participant_count")]
private int participantCount;
[JsonProperty("recent_participants")]
private IReadOnlyList<APIUser> recentParticipants = [];
// Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930)
[JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)]
private int? maxAttempts
{
get => MaxAttempts.Value;
set => MaxAttempts.Value = value;
}
private int? maxAttempts;
[JsonProperty("playlist")]
private IReadOnlyList<PlaylistItem> playlist = [];
[JsonProperty("playlist_item_stats")]
private RoomPlaylistItemStats? playlistItemStats;
[JsonProperty("difficulty_range")]
private RoomDifficultyRange? difficultyRange;
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
[JsonProperty("type")]
private MatchType type;
[JsonConverter(typeof(SnakeCaseStringEnumConverter))]
[JsonProperty("queue_mode")]
private QueueMode queueMode;
[Cached]
[JsonProperty("auto_skip")]
public readonly Bindable<bool> AutoSkip = new Bindable<bool>();
private bool autoSkip;
public Room()
{
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
}
[JsonProperty("auto_start_duration")]
private ushort autoStartDuration;
[JsonProperty("current_user_score")]
private PlaylistAggregateScore? userScore;
[JsonProperty("current_playlist_item")]
private PlaylistItem? currentPlaylistItem;
[JsonProperty("channel_id")]
private int channelId;
// Not serialised (see: GetRoomsRequest).
private RoomStatus status = new RoomStatusOpen();
// Not yet serialised (not implemented).
private RoomAvailability availability;
/// <summary>
/// Copies values from another <see cref="Room"/> into this one.
@@ -182,43 +351,34 @@ namespace osu.Game.Online.Rooms
/// <param name="other">The <see cref="Room"/> to copy values from.</param>
public void CopyFrom(Room other)
{
RoomID.Value = other.RoomID.Value;
Name.Value = other.Name.Value;
RoomID = other.RoomID;
Name = other.Name;
Category.Value = other.Category.Value;
Category = other.Category;
if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
Host.Value = other.Host.Value;
if (other.Host != null && Host?.Id != other.Host.Id)
Host = other.Host;
ChannelId.Value = other.ChannelId.Value;
Status.Value = other.Status.Value;
Availability.Value = other.Availability.Value;
HasPassword.Value = other.HasPassword.Value;
Type.Value = other.Type.Value;
MaxParticipants.Value = other.MaxParticipants.Value;
ParticipantCount.Value = other.ParticipantCount.Value;
EndDate.Value = other.EndDate.Value;
UserScore.Value = other.UserScore.Value;
QueueMode.Value = other.QueueMode.Value;
AutoStartDuration.Value = other.AutoStartDuration.Value;
DifficultyRange.Value = other.DifficultyRange.Value;
PlaylistItemStats.Value = other.PlaylistItemStats.Value;
CurrentPlaylistItem.Value = other.CurrentPlaylistItem.Value;
AutoSkip.Value = other.AutoSkip.Value;
ChannelId = other.ChannelId;
Status = other.Status;
Availability = other.Availability;
HasPassword = other.HasPassword;
Type = other.Type;
MaxParticipants = other.MaxParticipants;
ParticipantCount = other.ParticipantCount;
EndDate = other.EndDate;
UserScore = other.UserScore;
QueueMode = other.QueueMode;
AutoStartDuration = other.AutoStartDuration;
DifficultyRange = other.DifficultyRange;
PlaylistItemStats = other.PlaylistItemStats;
CurrentPlaylistItem = other.CurrentPlaylistItem;
AutoSkip = other.AutoSkip;
other.RemoveExpiredPlaylistItems();
if (!Playlist.SequenceEqual(other.Playlist))
{
Playlist.Clear();
Playlist.AddRange(other.Playlist);
}
if (!RecentParticipants.SequenceEqual(other.RecentParticipants))
{
RecentParticipants.Clear();
RecentParticipants.AddRange(other.RecentParticipants);
}
Playlist = other.Playlist;
RecentParticipants = other.RecentParticipants;
}
public void RemoveExpiredPlaylistItems()
@@ -226,8 +386,8 @@ namespace osu.Game.Online.Rooms
// Todo: This is not the best way/place to do this, but the intention is to display all playlist items when the room has ended,
// and display only the non-expired playlist items while the room is still active. In order to achieve this, all expired items are removed from the source Room.
// More refactoring is required before this can be done locally instead - DrawableRoomPlaylist is currently directly bound to the playlist to display items in the room.
if (!(Status.Value is RoomStatusEnded))
Playlist.RemoveAll(i => i.Expired);
if (Status is not RoomStatusEnded)
Playlist = Playlist.Where(i => !i.Expired).ToArray();
}
[JsonObject(MemberSerialization.OptIn)]
@@ -240,7 +400,7 @@ namespace osu.Game.Online.Rooms
public int CountTotal;
[JsonProperty("ruleset_ids")]
public int[] RulesetIDs;
public int[] RulesetIDs = [];
}
[JsonObject(MemberSerialization.OptIn)]
@@ -252,5 +412,28 @@ namespace osu.Game.Online.Rooms
[JsonProperty("max")]
public double Max;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null!)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetList<T>(ref IReadOnlyList<T> list, IReadOnlyList<T> value, [CallerMemberName] string propertyName = null!)
{
if (list.SequenceEqual(value))
return false;
list = value;
OnPropertyChanged(propertyName);
return true;
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null!)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
}
@@ -156,15 +156,15 @@ namespace osu.Game.Screens.Menu
Room = room;
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
if (room.StartDate.Value != null && room.RoomID.Value != lastDailyChallengeRoomID)
if (room.StartDate != null && room.RoomID != lastDailyChallengeRoomID)
{
lastDailyChallengeRoomID = room.RoomID.Value;
lastDailyChallengeRoomID = room.RoomID;
// new challenge is live, reset intro played static.
statics.SetValue(Static.DailyChallengeIntroPlayed, false);
// we only want to notify the user if the new challenge just went live.
if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800)
if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value).TotalSeconds) < 1800)
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
}
@@ -180,7 +180,7 @@ namespace osu.Game.Screens.Menu
if (Room == null)
return;
var remaining = (Room.EndDate.Value - DateTimeOffset.Now) ?? TimeSpan.Zero;
var remaining = (Room.EndDate - DateTimeOffset.Now) ?? TimeSpan.Zero;
if (remaining <= TimeSpan.Zero)
{
@@ -1,32 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class BeatmapTitle : OnlinePlayComposite
public partial class BeatmapTitle : CompositeDrawable
{
private readonly Room room;
private readonly LinkFlowContainer textFlow;
public BeatmapTitle()
{
AutoSizeAxes = Axes.Both;
[Resolved]
private OsuColour colours { get; set; } = null!;
public BeatmapTitle(Room room)
{
this.room = room;
AutoSizeAxes = Axes.Both;
InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both };
}
[BackgroundDependencyLoader]
private void load()
protected override void LoadComplete()
{
Playlist.CollectionChanged += (_, _) => updateText();
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateText();
}
@@ -46,8 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
[Resolved]
private OsuColour colours { get; set; } = null!;
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Playlist))
updateText();
}
private void updateText()
{
@@ -56,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
textFlow.Clear();
var beatmap = Playlist.FirstOrDefault()?.Beatmap;
var beatmap = room.Playlist.FirstOrDefault()?.Beatmap;
if (beatmap == null)
{
@@ -78,5 +89,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap");
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -35,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
});
}
private GetRoomsRequest lastPollRequest;
private GetRoomsRequest? lastPollRequest;
protected override Task Poll()
{
@@ -53,11 +51,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
req.Success += result =>
{
result = result.Where(r => r.Category.Value != RoomCategory.DailyChallenge).ToList();
result = result.Where(r => r.Category != RoomCategory.DailyChallenge).ToList();
foreach (var existing in RoomManager.Rooms.ToArray())
{
if (result.All(r => r.RoomID.Value != existing.RoomID.Value))
if (result.All(r => r.RoomID != existing.RoomID))
RoomManager.RemoveRoom(existing);
}
@@ -1,12 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterfaceV2;
@@ -14,23 +11,22 @@ using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Select;
using osuTK;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class MatchBeatmapDetailArea : BeatmapDetailArea
{
public Action CreateNewItem;
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
[Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; }
public Action? CreateNewItem;
private readonly Room room;
private readonly GridContainer playlistArea;
private readonly DrawableRoomPlaylist playlist;
public MatchBeatmapDetailArea()
public MatchBeatmapDetailArea(Room room)
{
this.room = room;
Add(playlistArea = new GridContainer
{
RelativeSizeAxes = Axes.Both,
@@ -72,10 +68,21 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
playlist.Items.BindTo(Playlist);
playlist.SelectedItem.BindTo(SelectedItem);
playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray());
room.PropertyChanged += onRoomPropertyChanged;
updateRoomPlaylist();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Playlist))
updateRoomPlaylist();
}
private void updateRoomPlaylist()
=> playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist);
protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods)
{
base.OnTabChanged(tab, selectedMods);
@@ -93,5 +100,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Prepend(new BeatmapDetailAreaPlaylistTabItem()).ToArray();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,41 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class OnlinePlayBackgroundSprite : OnlinePlayComposite
{
protected readonly BeatmapSetCoverType BeatmapSetCoverType;
private UpdateableBeatmapBackgroundSprite sprite;
public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
BeatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = sprite = CreateBackgroundSprite();
CurrentPlaylistItem.BindValueChanged(_ => updateBeatmap());
Playlist.CollectionChanged += (_, _) => updateBeatmap();
updateBeatmap();
}
private void updateBeatmap()
{
sprite.Beatmap.Value = CurrentPlaylistItem.Value?.Beatmap ?? Playlist.GetCurrentItem()?.Beatmap;
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
}
@@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
/// <summary>
/// A header used in the multiplayer interface which shows text / details beneath a line.
/// </summary>
public partial class OverlinedHeader : OnlinePlayComposite
public partial class OverlinedHeader : CompositeDrawable
{
private bool showLine = true;
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -9,19 +10,38 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class OverlinedPlaylistHeader : OverlinedHeader
{
private readonly Room room;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
public OverlinedPlaylistHeader()
public OverlinedPlaylistHeader(Room room)
: base("Playlist")
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
Playlist.BindCollectionChanged((_, _) => Details.Value = Playlist.GetTotalDuration(rulesets), true);
room.PropertyChanged += onRoomPropertyChanged;
updateDuration();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Playlist))
updateDuration();
}
private void updateDuration()
=> Details.Value = room.Playlist.GetTotalDuration(rulesets);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,33 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class ParticipantCountDisplay : OnlinePlayComposite
public partial class ParticipantCountDisplay : CompositeDrawable
{
private const float text_size = 30;
private const float transition_duration = 100;
private OsuSpriteText slash, maxText;
private readonly Room room;
public ParticipantCountDisplay()
private OsuSpriteText slash = null!;
private OsuSpriteText maxText = null!;
private OsuSpriteText count = null!;
public ParticipantCountDisplay(Room room)
{
this.room = room;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
OsuSpriteText count;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@@ -50,14 +53,33 @@ namespace osu.Game.Screens.OnlinePlay.Components
},
}
};
MaxParticipants.BindValueChanged(_ => updateMax(), true);
ParticipantCount.BindValueChanged(c => count.Text = c.NewValue.ToString("#,0"), true);
}
private void updateMax()
protected override void LoadComplete()
{
if (MaxParticipants.Value == null)
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateRoomParticipantCount();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.MaxParticipants):
updateRoomMaxParticipants();
break;
case nameof(Room.ParticipantCount):
updateRoomParticipantCount();
break;
}
}
private void updateRoomMaxParticipants()
{
if (room.MaxParticipants == null)
{
slash.FadeOut(transition_duration);
maxText.FadeOut(transition_duration);
@@ -65,9 +87,18 @@ namespace osu.Game.Screens.OnlinePlay.Components
else
{
slash.FadeIn(transition_duration);
maxText.Text = MaxParticipants.Value.ToString();
maxText.Text = room.MaxParticipants.ToString()!;
maxText.FadeIn(transition_duration);
}
}
private void updateRoomParticipantCount()
=> count.Text = room.ParticipantCount.ToString("#,0");
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,25 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using System.ComponentModel;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class ParticipantsDisplay : OnlinePlayComposite
public partial class ParticipantsDisplay : CompositeDrawable
{
public Bindable<string> Details = new Bindable<string>();
public readonly Bindable<string> Details = new Bindable<string>();
public ParticipantsDisplay(Direction direction)
private readonly Room room;
public ParticipantsDisplay(Room room, Direction direction)
{
this.room = room;
OsuScrollContainer scroll;
ParticipantsList list;
AddInternal(scroll = new OsuScrollContainer(direction)
{
Child = list = new ParticipantsList()
Child = list = new ParticipantsList(room)
});
switch (direction)
@@ -46,14 +51,32 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
[BackgroundDependencyLoader]
private void load()
protected override void LoadComplete()
{
ParticipantCount.BindValueChanged(_ => setParticipantCount());
MaxParticipants.BindValueChanged(_ => setParticipantCount(), true);
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateRoomParticipantCount();
}
private void setParticipantCount() =>
Details.Value = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString();
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.MaxParticipants):
case nameof(Room.ParticipantCount):
updateRoomParticipantCount();
break;
}
}
private void updateRoomParticipantCount()
=> Details.Value = room.MaxParticipants != null ? $"{room.ParticipantCount}/{room.MaxParticipants}" : room.ParticipantCount.ToString();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,21 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using System.ComponentModel;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class ParticipantsList : OnlinePlayComposite
public partial class ParticipantsList : CompositeDrawable
{
public const float TILE_SIZE = 35;
@@ -57,15 +56,29 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
}
[BackgroundDependencyLoader]
private void load()
private readonly Room room;
public ParticipantsList(Room room)
{
RecentParticipants.CollectionChanged += (_, _) => updateParticipants();
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateParticipants();
}
private ScheduledDelegate scheduledUpdate;
private FillFlowContainer<UserTile> tiles;
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.RecentParticipants))
updateParticipants();
}
private ScheduledDelegate? scheduledUpdate;
private FillFlowContainer<UserTile>? tiles;
private void updateParticipants()
{
@@ -83,8 +96,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
Spacing = Vector2.One
};
for (int i = 0; i < RecentParticipants.Count; i++)
tiles.Add(new UserTile { User = RecentParticipants[i] });
for (int i = 0; i < room.RecentParticipants.Count; i++)
tiles.Add(new UserTile { User = room.RecentParticipants[i] });
AddInternal(tiles);
@@ -92,9 +105,15 @@ namespace osu.Game.Screens.OnlinePlay.Components
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
private partial class UserTile : CompositeDrawable
{
public APIUser User
public APIUser? User
{
get => avatar.User;
set => avatar.User = value;
@@ -1,26 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class RoomLocalUserInfo : OnlinePlayComposite
public partial class RoomLocalUserInfo : CompositeDrawable
{
private OsuSpriteText attemptDisplay;
private readonly Room room;
private OsuSpriteText attemptDisplay = null!;
[Resolved]
private OsuColour colours { get; set; }
private OsuColour colours { get; set; } = null!;
public RoomLocalUserInfo()
public RoomLocalUserInfo(Room room)
{
this.room = room;
AutoSizeAxes = Axes.Both;
}
@@ -45,19 +47,30 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
MaxAttempts.BindValueChanged(_ => updateAttempts());
UserScore.BindValueChanged(_ => updateAttempts(), true);
room.PropertyChanged += onRoomPropertyChanged;
updateAttempts();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.UserScore):
case nameof(Room.MaxAttempts):
updateAttempts();
break;
}
}
private void updateAttempts()
{
if (MaxAttempts.Value != null)
if (room.MaxAttempts != null)
{
attemptDisplay.Text = $"Maximum attempts: {MaxAttempts.Value:N0}";
attemptDisplay.Text = $"Maximum attempts: {room.MaxAttempts:N0}";
if (UserScore.Value != null)
if (room.UserScore != null)
{
int remaining = MaxAttempts.Value.Value - UserScore.Value.PlaylistItemAttempts.Sum(a => a.Attempts);
int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts);
attemptDisplay.Text += $" ({remaining} remaining)";
if (remaining == 0)
@@ -69,5 +82,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
attemptDisplay.Text = string.Empty;
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
@@ -20,18 +17,17 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class RoomManager : Component, IRoomManager
{
[CanBeNull]
public event Action RoomsUpdated;
public event Action? RoomsUpdated;
private readonly BindableList<Room> rooms = new BindableList<Room>();
public IBindableList<Room> Rooms => rooms;
protected IBindable<Room> JoinedRoom => joinedRoom;
private readonly Bindable<Room> joinedRoom = new Bindable<Room>();
protected IBindable<Room?> JoinedRoom => joinedRoom;
private readonly Bindable<Room?> joinedRoom = new Bindable<Room?>();
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
public RoomManager()
{
@@ -44,9 +40,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
PartRoom();
}
public virtual void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
public virtual void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
room.Host.Value = api.LocalUser.Value;
room.Host = api.LocalUser.Value;
var req = new CreateRoomRequest(room);
@@ -69,9 +65,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
api.Queue(req);
}
private JoinRoomRequest currentJoinRoomRequest;
private JoinRoomRequest? currentJoinRoomRequest;
public virtual void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
public virtual void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
currentJoinRoomRequest?.Cancel();
currentJoinRoomRequest = new JoinRoomRequest(room, password);
@@ -97,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
currentJoinRoomRequest?.Cancel();
if (JoinedRoom.Value == null)
if (joinedRoom.Value == null)
return;
if (api.State.Value == APIState.Online)
@@ -111,14 +107,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
public void AddOrUpdateRoom(Room room)
{
Debug.Assert(ThreadSafety.IsUpdateThread);
Debug.Assert(room.RoomID.Value != null);
Debug.Assert(room.RoomID != null);
if (ignoredRooms.Contains(room.RoomID.Value.Value))
if (ignoredRooms.Contains(room.RoomID.Value))
return;
try
{
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
var existing = rooms.FirstOrDefault(e => e.RoomID == room.RoomID);
if (existing == null)
rooms.Add(room);
else
@@ -126,9 +122,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
catch (Exception ex)
{
Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
Logger.Error(ex, $"Failed to update room: {room.Name}.");
ignoredRooms.Add(room.RoomID.Value.Value);
ignoredRooms.Add(room.RoomID.Value);
rooms.Remove(room);
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Threading.Tasks;
using osu.Game.Online.Rooms;
@@ -20,21 +18,21 @@ namespace osu.Game.Screens.OnlinePlay.Components
this.room = room;
}
private GetRoomRequest lastPollRequest;
private GetRoomRequest? lastPollRequest;
protected override Task Poll()
{
if (!API.IsLoggedIn)
return base.Poll();
if (room.RoomID.Value == null)
if (room.RoomID == null)
return base.Poll();
var tcs = new TaskCompletionSource<bool>();
lastPollRequest?.Cancel();
var req = new GetRoomRequest(room.RoomID.Value.Value);
var req = new GetRoomRequest(room.RoomID.Value);
req.Success += result =>
{
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -13,22 +12,27 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using osuTK;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class StarRatingRangeDisplay : OnlinePlayComposite
public partial class StarRatingRangeDisplay : CompositeDrawable
{
private readonly Room room;
[Resolved]
private OsuColour colours { get; set; }
private OsuColour colours { get; set; } = null!;
private StarRatingDisplay minDisplay;
private Drawable minBackground;
private StarRatingDisplay maxDisplay;
private Drawable maxBackground;
private StarRatingDisplay minDisplay = null!;
private Drawable minBackground = null!;
private StarRatingDisplay maxDisplay = null!;
private Drawable maxBackground = null!;
public StarRatingRangeDisplay()
public StarRatingRangeDisplay(Room room)
{
this.room = room;
AutoSizeAxes = Axes.Both;
}
@@ -76,8 +80,19 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
base.LoadComplete();
DifficultyRange.BindValueChanged(_ => updateRange());
Playlist.BindCollectionChanged((_, _) => updateRange(), true);
room.PropertyChanged += onRoomPropertyChanged;
updateRange();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Playlist):
case nameof(Room.DifficultyRange):
updateRange();
break;
}
}
private void updateRange()
@@ -85,16 +100,16 @@ namespace osu.Game.Screens.OnlinePlay.Components
StarDifficulty minDifficulty;
StarDifficulty maxDifficulty;
if (DifficultyRange.Value != null && Playlist.Count == 0)
if (room.DifficultyRange != null && room.Playlist.Count == 0)
{
// When Playlist is empty (in lounge) we take retrieved range
minDifficulty = new StarDifficulty(DifficultyRange.Value.Min, 0);
maxDifficulty = new StarDifficulty(DifficultyRange.Value.Max, 0);
minDifficulty = new StarDifficulty(room.DifficultyRange.Min, 0);
maxDifficulty = new StarDifficulty(room.DifficultyRange.Max, 0);
}
else
{
// When Playlist is not empty (in room) we compute actual range
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
var orderedDifficulties = room.Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);
@@ -107,5 +122,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,39 +1,52 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Components
{
public partial class StatusColouredContainer : Container
{
[Resolved]
private OsuColour colours { get; set; } = null!;
private readonly double transitionDuration;
private readonly Room room;
[Resolved(typeof(Room), nameof(Room.Status))]
private Bindable<RoomStatus> status { get; set; }
[Resolved(typeof(Room), nameof(Room.Category))]
private Bindable<RoomCategory> category { get; set; }
public StatusColouredContainer(double transitionDuration = 100)
public StatusColouredContainer(Room room, double transitionDuration = 100)
{
this.room = room;
this.transitionDuration = transitionDuration;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
protected override void LoadComplete()
{
status.BindValueChanged(s =>
{
this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration);
}, true);
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateRoomStatus();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Status))
updateRoomStatus();
}
private void updateRoomStatus()
{
this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -119,14 +119,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING };
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { Value = room }
};
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
@@ -228,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Origin = Anchor.Centre,
Children = new Drawable[]
{
new DailyChallengeTimeRemainingRing(),
new DailyChallengeTimeRemainingRing(room),
breakdown = new DailyChallengeScoreBreakdown(),
totals = new DailyChallengeTotalsDisplay(),
}
@@ -301,7 +293,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Spacing = new Vector2(10),
Children = new Drawable[]
{
new PlaylistsReadyButton
new PlaylistsReadyButton(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -353,12 +345,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private void presentScore(long id)
{
if (this.IsCurrentScreen())
this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id));
this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id));
}
private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e)
{
if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID)
if (e.RoomID != room.RoomID || e.PlaylistItemID != playlistItem.ID)
return;
userLookupCache.GetUserAsync(e.UserID).ContinueWith(t =>
@@ -410,7 +402,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> change)
{
if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null && metadataClient.IsConnected.Value)
if (change.OldValue?.RoomID == room.RoomID && change.NewValue == null && metadataClient.IsConnected.Value)
{
notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification });
}
@@ -437,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
roomManager.JoinRoom(room);
startLoopingTrack(this, musicController);
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t =>
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t =>
{
if (t.Exception != null)
{
@@ -489,7 +481,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
roomManager.PartRoom();
metadataClient.EndWatchingMultiplayerRoom(room.RoomID.Value!.Value).FireAndForget();
metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget();
return base.OnExiting(e);
}
@@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(),
Text = room.Name.Split(':', StringSplitOptions.TrimEntries).Last(),
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
Shear = new Vector2(-OsuGame.SHEAR, 0f),
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
@@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
if (request?.CompletionState == APIRequestCompletionState.Waiting)
return;
request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID);
request = new IndexPlaylistScoresRequest(room.RoomID!.Value, playlistItem.ID);
request.Success += req => Schedule(() =>
{
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -10,15 +11,15 @@ using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class DailyChallengeTimeRemainingRing : OnlinePlayComposite
public partial class DailyChallengeTimeRemainingRing : CompositeDrawable
{
private CircularProgress progress = null!;
private OsuSpriteText timeText = null!;
private readonly Room room;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
@@ -26,6 +27,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Resolved]
private OsuColour colours { get; set; } = null!;
private CircularProgress progress = null!;
private OsuSpriteText timeText = null!;
public DailyChallengeTimeRemainingRing(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader]
private void load()
{
@@ -90,12 +99,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
base.LoadComplete();
StartDate.BindValueChanged(_ => Scheduler.AddOnce(updateState));
EndDate.BindValueChanged(_ => Scheduler.AddOnce(updateState));
room.PropertyChanged += onRoomPropertyChanged;
updateState();
FinishTransforms(true);
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.StartDate):
case nameof(Room.EndDate):
Scheduler.AddOnce(updateState);
break;
}
}
private ScheduledDelegate? scheduledUpdate;
private void updateState()
@@ -105,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
const float transition_duration = 300;
if (StartDate.Value == null || EndDate.Value == null || EndDate.Value < DateTimeOffset.Now)
if (room.StartDate == null || room.EndDate == null || room.EndDate < DateTimeOffset.Now)
{
timeText.Text = TimeSpan.Zero.ToString(@"hh\:mm\:ss");
progress.Progress = 0;
@@ -114,8 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
return;
}
var roomDuration = EndDate.Value.Value - StartDate.Value.Value;
var remaining = EndDate.Value.Value - DateTimeOffset.Now;
var roomDuration = room.EndDate.Value - room.StartDate.Value;
var remaining = room.EndDate.Value - DateTimeOffset.Now;
timeText.Text = remaining.ToString(@"hh\:mm\:ss");
progress.Progress = remaining.TotalSeconds / roomDuration.TotalSeconds;
@@ -138,5 +158,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
scheduledUpdate = Scheduler.AddDelayed(updateState, 1000);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -16,6 +16,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -26,30 +27,33 @@ using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
using osuTK.Graphics;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class DrawableRoom : CompositeDrawable
public abstract partial class DrawableRoom : CompositeDrawable
{
protected const float CORNER_RADIUS = 10;
private const float height = 100;
public readonly Room Room;
protected Container ButtonsContainer { get; private set; }
protected readonly Bindable<PlaylistItem?> SelectedItem = new Bindable<PlaylistItem?>();
protected Container ButtonsContainer { get; private set; } = null!;
private readonly Bindable<MatchType> roomType = new Bindable<MatchType>();
private readonly Bindable<RoomCategory> roomCategory = new Bindable<RoomCategory>();
private readonly Bindable<bool> hasPassword = new Bindable<bool>();
private DrawableRoomParticipantsList drawableRoomParticipantsList;
private RoomSpecialCategoryPill specialCategoryPill;
private PasswordProtectedIcon passwordIcon;
private EndDateInfo endDateInfo;
private DrawableRoomParticipantsList? drawableRoomParticipantsList;
private RoomSpecialCategoryPill? specialCategoryPill;
private PasswordProtectedIcon? passwordIcon;
private EndDateInfo? endDateInfo;
private SpriteText? roomName;
private UpdateableBeatmapBackgroundSprite background = null!;
private DelayedLoadWrapper wrapper = null!;
private DelayedLoadWrapper wrapper;
public DrawableRoom(Room room)
protected DrawableRoom(Room room)
{
Room = room;
@@ -77,7 +81,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
AutoSizeAxes = Axes.X
};
InternalChildren = new[]
InternalChildren = new Drawable[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
new Box
@@ -85,7 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
CreateBackground().With(d =>
background = CreateBackground().With(d =>
{
d.RelativeSizeAxes = Axes.Both;
}),
@@ -155,17 +159,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Spacing = new Vector2(5),
Children = new Drawable[]
{
new RoomStatusPill
new RoomStatusPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
specialCategoryPill = new RoomSpecialCategoryPill
specialCategoryPill = new RoomSpecialCategoryPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
endDateInfo = new EndDateInfo
endDateInfo = new EndDateInfo(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -180,13 +184,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new TruncatingSpriteText
roomName = new TruncatingSpriteText
{
RelativeSizeAxes = Axes.X,
Font = OsuFont.GetFont(size: 28),
Current = { BindTarget = Room.Name }
Font = OsuFont.GetFont(size: 28)
},
new RoomStatusText()
new RoomStatusText(Room)
{
SelectedItem = { BindTarget = SelectedItem }
}
}
}
},
@@ -218,7 +224,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Children = new Drawable[]
{
ButtonsContainer,
drawableRoomParticipantsList = new DrawableRoomParticipantsList
drawableRoomParticipantsList = new DrawableRoomParticipantsList(Room)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
@@ -243,36 +249,71 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
Room.PropertyChanged += onRoomPropertyChanged;
wrapper.DelayedLoadComplete += _ =>
{
Debug.Assert(specialCategoryPill != null);
Debug.Assert(endDateInfo != null);
Debug.Assert(passwordIcon != null);
wrapper.FadeInFromZero(200);
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
{
if (c.NewValue > RoomCategory.Normal)
specialCategoryPill.Show();
else
specialCategoryPill.Hide();
}, true);
roomType.BindTo(Room.Type);
roomType.BindValueChanged(t =>
{
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
}, true);
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
updateRoomName();
updateRoomCategory();
updateRoomType();
updateRoomHasPassword();
};
SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true);
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
switch (e.PropertyName)
{
Model = { Value = Room }
};
case nameof(Room.Name):
updateRoomName();
break;
case nameof(Room.Category):
updateRoomCategory();
break;
case nameof(Room.Type):
updateRoomType();
break;
case nameof(Room.HasPassword):
updateRoomHasPassword();
break;
}
}
private void updateRoomName()
{
if (roomName != null)
roomName.Text = Room.Name;
}
private void updateRoomCategory()
{
if (Room.Category > RoomCategory.Normal)
specialCategoryPill?.Show();
else
specialCategoryPill?.Hide();
}
private void updateRoomType()
{
if (endDateInfo != null)
endDateInfo.Alpha = Room.Type == MatchType.Playlists ? 1 : 0;
}
private void updateRoomHasPassword()
{
if (passwordIcon != null)
passwordIcon.Alpha = Room.HasPassword ? 1 : 0;
}
private int numberOfAvatars = 7;
@@ -289,29 +330,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite();
protected virtual UpdateableBeatmapBackgroundSprite CreateBackground() => new UpdateableBeatmapBackgroundSprite();
protected virtual IEnumerable<Drawable> CreateBottomDetails()
{
var pills = new List<Drawable>();
if (Room.Type.Value != MatchType.Playlists)
if (Room.Type != MatchType.Playlists)
{
pills.AddRange(new OnlinePlayComposite[]
pills.AddRange(new Drawable[]
{
new MatchTypePill(),
new QueueModePill(),
new MatchTypePill(Room),
new QueueModePill(Room),
});
}
pills.AddRange(new Drawable[]
{
new PlaylistCountPill
new PlaylistCountPill(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new StarRatingRangeDisplay
new StarRatingRangeDisplay(Room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -322,19 +363,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return pills;
}
private partial class RoomStatusText : OnlinePlayComposite
protected override void Dispose(bool isDisposing)
{
[Resolved]
private OsuColour colours { get; set; }
base.Dispose(isDisposing);
Room.PropertyChanged -= onRoomPropertyChanged;
}
private partial class RoomStatusText : CompositeDrawable
{
public readonly IBindable<PlaylistItem?> SelectedItem = new Bindable<PlaylistItem?>();
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; }
private OsuColour colours { get; set; } = null!;
private SpriteText statusText;
private LinkFlowContainer beatmapText;
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
public RoomStatusText()
private readonly Room room;
private SpriteText statusText = null!;
private LinkFlowContainer beatmapText = null!;
public RoomStatusText(Room room)
{
this.room = room;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
@@ -383,17 +434,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override void LoadComplete()
{
base.LoadComplete();
CurrentPlaylistItem.BindValueChanged(onSelectedItemChanged, true);
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
}
private CancellationTokenSource beatmapLookupCancellation;
private CancellationTokenSource? beatmapLookupCancellation;
private void onSelectedItemChanged(ValueChangedEvent<PlaylistItem> item)
private void onSelectedItemChanged(ValueChangedEvent<PlaylistItem?> item)
{
beatmapLookupCancellation?.Cancel();
beatmapText.Clear();
if (Type.Value == MatchType.Playlists)
if (room.Type == MatchType.Playlists)
{
statusText.Text = "Ready to play";
return;
@@ -1,13 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Specialized;
using System.Diagnostics;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -16,31 +13,33 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Users.Drawables;
using osuTK;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class DrawableRoomParticipantsList : OnlinePlayComposite
public partial class DrawableRoomParticipantsList : CompositeDrawable
{
public const float SHEAR_WIDTH = 12f;
private const float avatar_size = 36;
private const float height = 60f;
private static readonly Vector2 shear = new Vector2(SHEAR_WIDTH / height, 0);
private FillFlowContainer<CircularAvatar> avatarFlow;
private readonly Room room;
private CircularAvatar hostAvatar;
private LinkFlowContainer hostText;
private HiddenUserCount hiddenUsers;
private OsuSpriteText totalCount;
private FillFlowContainer<CircularAvatar> avatarFlow = null!;
private CircularAvatar hostAvatar = null!;
private LinkFlowContainer hostText = null!;
private HiddenUserCount hiddenUsers = null!;
private OsuSpriteText totalCount = null!;
public DrawableRoomParticipantsList()
public DrawableRoomParticipantsList(Room room)
{
this.room = room;
AutoSizeAxes = Axes.X;
Height = height;
}
@@ -165,14 +164,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
ParticipantCount.BindValueChanged(_ =>
{
updateHiddenUsers();
totalCount.Text = ParticipantCount.Value.ToString();
}, true);
room.PropertyChanged += onRoomPropertyChanged;
Host.BindValueChanged(onHostChanged, true);
updateRoomHost();
updateRoomParticipantCount();
updateRoomParticipants();
}
private int numberOfCircles = 4;
@@ -192,43 +188,38 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
clearUsers();
foreach (var u in RecentParticipants)
foreach (var u in room.RecentParticipants)
addUser(u);
updateHiddenUsers();
}
}
private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
private void updateRoomParticipants()
{
switch (e.Action)
HashSet<APIUser> newUsers = room.RecentParticipants.ToHashSet();
avatarFlow.RemoveAll(a =>
{
case NotifyCollectionChangedAction.Add:
Debug.Assert(e.NewItems != null);
// Avatar with no user. Really shouldn't ever be the case but asserting it correctly is difficult.
if (a.User == null)
return false;
foreach (var added in e.NewItems.OfType<APIUser>())
addUser(added);
break;
// User was previously and still is a participant. Keep them around but remove them from the new set.
// This will be useful when we add all remaining users (now just the new participants) to the flow.
if (newUsers.Contains(a.User))
{
newUsers.Remove(a.User);
return false;
}
case NotifyCollectionChangedAction.Remove:
Debug.Assert(e.OldItems != null);
// User is no longer a participant. Remove them from the flow.
return true;
}, true);
foreach (var removed in e.OldItems.OfType<APIUser>())
removeUser(removed);
break;
case NotifyCollectionChangedAction.Reset:
clearUsers();
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
// Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
clearUsers();
foreach (var u in RecentParticipants)
addUser(u);
break;
}
// Add all remaining users to the flow.
foreach (var u in newUsers)
addUser(u);
updateHiddenUsers();
}
@@ -241,11 +232,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Add(new CircularAvatar { User = user });
}
private void removeUser(APIUser user)
{
avatarFlow.RemoveAll(a => a.User == user, true);
}
private void clearUsers()
{
avatarFlow.Clear();
@@ -255,8 +241,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void updateHiddenUsers()
{
int hiddenCount = 0;
if (RecentParticipants.Count > NumberOfCircles)
hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
if (room.RecentParticipants.Count > NumberOfCircles)
hiddenCount = room.ParticipantCount - NumberOfCircles + 1;
hiddenUsers.Count = hiddenCount;
@@ -264,26 +250,56 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
avatarFlow.Remove(avatarFlow.Last(), true);
else if (displayedCircles < NumberOfCircles)
{
var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
var nextUser = room.RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
if (nextUser != null) addUser(nextUser);
}
}
private void onHostChanged(ValueChangedEvent<APIUser> host)
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
hostAvatar.User = host.NewValue;
switch (e.PropertyName)
{
case nameof(Room.Host):
updateRoomHost();
break;
case nameof(Room.ParticipantCount):
updateRoomParticipantCount();
break;
case nameof(Room.RecentParticipants):
updateRoomParticipants();
break;
}
}
private void updateRoomHost()
{
hostAvatar.User = room.Host;
hostText.Clear();
if (host.NewValue != null)
if (room.Host != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue);
hostText.AddUserLink(room.Host);
}
}
private void updateRoomParticipantCount()
{
updateHiddenUsers();
totalCount.Text = room.ParticipantCount.ToString();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
private partial class CircularAvatar : CompositeDrawable
{
public APIUser User
public APIUser? User
{
get => avatar.User;
set => avatar.User = value;
@@ -2,49 +2,69 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class EndDateInfo : OnlinePlayComposite
public partial class EndDateInfo : CompositeDrawable
{
public EndDateInfo()
private readonly Room room;
public EndDateInfo(Room room)
{
this.room = room;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new EndDatePart
InternalChild = new EndDatePart(room)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
EndDate = { BindTarget = EndDate }
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12)
};
}
private partial class EndDatePart : DrawableDate
{
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
private readonly Room room;
public EndDatePart()
public EndDatePart(Room room)
: base(DateTimeOffset.UtcNow)
{
EndDate.BindValueChanged(date =>
{
// If null, set a very large future date to prevent unnecessary schedules.
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
}, true);
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateEndDate();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.EndDate))
updateEndDate();
}
private void updateEndDate()
{
// If null, set a very large future date to prevent unnecessary schedules.
Date = room.EndDate ?? DateTimeOffset.Now.AddYears(1);
}
protected override string Format()
{
if (EndDate.Value == null)
if (room.EndDate == null)
return string.Empty;
var diffToNow = Date.Subtract(DateTimeOffset.Now);
@@ -60,6 +80,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return $"Closing {base.Format()}";
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
}
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using System.ComponentModel;
using osu.Framework.Extensions;
using osu.Game.Online.Rooms;
@@ -9,16 +9,36 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class MatchTypePill : OnlinePlayPill
{
private readonly Room room;
public MatchTypePill(Room room)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
Type.BindValueChanged(onMatchTypeChanged, true);
room.PropertyChanged += onRoomPropertyChanged;
updateRoomType();
}
private void onMatchTypeChanged(ValueChangedEvent<MatchType> type)
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
TextFlow.Text = type.NewValue.GetLocalisableDescription();
if (e.PropertyName == nameof(Room.Type))
updateRoomType();
}
private void updateRoomType()
{
TextFlow.Text = room.Type.GetLocalisableDescription();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -3,13 +3,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public abstract partial class OnlinePlayPill : OnlinePlayComposite
public abstract partial class OnlinePlayPill : CompositeDrawable
{
protected PillContainer Pill { get; private set; } = null!;
protected OsuTextFlowContainer TextFlow { get; private set; } = null!;
@@ -1,10 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using System.Linq;
using Humanizer;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
@@ -13,26 +15,50 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
/// </summary>
public partial class PlaylistCountPill : OnlinePlayPill
{
private readonly Room room;
public PlaylistCountPill(Room room)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
PlaylistItemStats.BindValueChanged(_ => updateCount());
Playlist.BindCollectionChanged((_, _) => updateCount(), true);
room.PropertyChanged += onRoomPropertyChanged;
updateCount();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Playlist):
case nameof(Room.PlaylistItemStats):
updateCount();
break;
}
}
private void updateCount()
{
int activeItems = Playlist.Count > 0 || PlaylistItemStats.Value == null
int activeItems = room.Playlist.Count > 0 || room.PlaylistItemStats == null
// For now, use the playlist as the source of truth if it has any items.
// This allows the count to display correctly on the room screen (after joining a room).
? Playlist.Count(i => !i.Expired)
: PlaylistItemStats.Value.CountActive;
? room.Playlist.Count(i => !i.Expired)
: room.PlaylistItemStats.CountActive;
TextFlow.Clear();
TextFlow.AddText(activeItems.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
TextFlow.AddText(" ");
TextFlow.AddText("Beatmap".ToQuantity(activeItems, ShowQuantityAs.None));
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,24 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using System.ComponentModel;
using osu.Framework.Extensions;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class QueueModePill : OnlinePlayPill
{
private readonly Room room;
public QueueModePill(Room room)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
QueueMode.BindValueChanged(onQueueModeChanged, true);
room.PropertyChanged += onRoomPropertyChanged;
updateRoomQueueMode();
}
private void onQueueModeChanged(ValueChangedEvent<QueueMode> mode)
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
TextFlow.Text = mode.NewValue.GetLocalisableDescription();
if (e.PropertyName == nameof(Room.QueueMode))
updateRoomQueueMode();
}
private void updateRoomQueueMode()
=> TextFlow.Text = room.QueueMode.GetLocalisableDescription();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,21 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class RoomSpecialCategoryPill : OnlinePlayPill
{
private readonly Room room;
[Resolved]
private OsuColour colours { get; set; } = null!;
protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold);
public RoomSpecialCategoryPill(Room room)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
@@ -23,11 +32,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Pill.Background.Alpha = 1;
TextFlow.Colour = Color4.Black;
Category.BindValueChanged(c =>
{
TextFlow.Text = c.NewValue.GetLocalisableDescription();
Pill.Background.Colour = colours.ForRoomCategory(c.NewValue) ?? colours.Pink;
}, true);
room.PropertyChanged += onRoomPropertyChanged;
updateRoomCategory();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Category))
updateRoomCategory();
}
private void updateRoomCategory()
{
TextFlow.Text = room.Category.GetLocalisableDescription();
Pill.Background.Colour = colours.ForRoomCategory(room.Category) ?? colours.Pink;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
@@ -19,25 +20,47 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override FontUsage Font => base.Font.With(weight: FontWeight.SemiBold);
private readonly Room room;
public RoomStatusPill(Room room)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
EndDate.BindValueChanged(_ => updateDisplay());
Status.BindValueChanged(_ => updateDisplay(), true);
FinishTransforms(true);
TextFlow.Colour = Colour4.Black;
Pill.Background.Alpha = 1;
room.PropertyChanged += onRoomPropertyChanged;
updateDisplay();
FinishTransforms(true);
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Status):
case nameof(Room.EndDate):
updateDisplay();
break;
}
}
private void updateDisplay()
{
RoomStatus status = Status.Value;
Pill.Background.FadeColour(room.Status.GetAppropriateColour(colours), 100);
TextFlow.Text = room.Status.Message;
}
Pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
TextFlow.Text = status.Message;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -11,6 +9,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
@@ -24,8 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
public partial class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{
public readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>();
public readonly Bindable<Room?> SelectedRoom = new Bindable<Room?>();
public readonly Bindable<FilterCriteria?> Filter = new Bindable<FilterCriteria?>();
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
@@ -33,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private readonly FillFlowContainer<DrawableLoungeRoom> roomFlow;
[Resolved]
private IRoomManager roomManager { get; set; }
private IRoomManager roomManager { get; set; } = null!;
// handle deselection
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
@@ -67,10 +66,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
rooms.BindTo(roomManager.Rooms);
Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
Filter.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
}
private void applyFilterCriteria(FilterCriteria criteria)
private void applyFilterCriteria(FilterCriteria? criteria)
{
roomFlow.Children.ForEach(r =>
{
@@ -80,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
bool matchingFilter = true;
matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
if (!string.IsNullOrEmpty(criteria.SearchString))
{
@@ -102,10 +101,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return true;
case RoomPermissionsFilter.Public:
return !room.Room.HasPassword.Value;
return !room.Room.HasPassword;
case RoomPermissionsFilter.Private:
return room.Room.HasPassword.Value;
return room.Room.HasPassword;
default:
throw new ArgumentOutOfRangeException(nameof(accessType), accessType, $"Unsupported {nameof(RoomPermissionsFilter)} in filter");
@@ -113,7 +112,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args)
private void roomsChanged(object? sender, NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
@@ -140,9 +139,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void addRooms(IEnumerable<Room> rooms)
{
foreach (var room in rooms)
roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } });
roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = SelectedRoom });
applyFilterCriteria(Filter?.Value);
applyFilterCriteria(Filter.Value);
}
private void removeRooms(IEnumerable<Room> rooms)
@@ -170,10 +169,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
foreach (var room in roomFlow)
{
roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal
roomFlow.SetLayoutPosition(room, room.Room.Category > RoomCategory.Normal
// Always show spotlight playlists at the top of the listing.
? float.MinValue
: -(room.Room.RoomID.Value ?? 0));
: -(room.Room.RoomID ?? 0));
}
}
@@ -213,7 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
Room room;
Room? room;
if (SelectedRoom.Value == null)
room = visibleRooms.FirstOrDefault()?.Room;
@@ -236,7 +235,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.Dispose(isDisposing);
if (roomManager != null)
if (roomManager.IsNotNull())
roomManager.RoomsUpdated -= updateSorting;
}
}
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -28,6 +27,7 @@ using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osuTK;
using osuTK.Graphics;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
@@ -39,14 +39,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private const float transition_duration = 60;
private const float selection_border_width = 4;
public readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
public required Bindable<Room?> SelectedRoom
{
get => selectedRoom;
set => selectedRoom.Current = value;
}
[Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
private LoungeSubScreen? lounge { get; set; }
private Sample sampleSelect;
private Sample sampleJoin;
private Drawable selectionBox;
private readonly BindableWithCurrent<Room?> selectedRoom = new BindableWithCurrent<Room?>();
private Sample? sampleSelect;
private Sample? sampleJoin;
private Drawable selectionBox = null!;
public DrawableLoungeRoom(Room room)
: base(room)
@@ -61,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
AddRangeInternal(new Drawable[]
{
new StatusColouredContainer(transition_duration)
new StatusColouredContainer(Room, transition_duration)
{
RelativeSizeAxes = Axes.Both,
Child = selectionBox = new Container
@@ -89,12 +94,24 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.LoadComplete();
Alpha = matchingFilter ? 1 : 0;
selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0;
selectionBox.Alpha = selectedRoom.Value == Room ? 1 : 0;
SelectedRoom.BindValueChanged(updateSelectedRoom);
selectedRoom.BindValueChanged(updateSelectedRoom);
Room.PropertyChanged += onRoomPropertyChanged;
updateSelectedItem();
}
private void updateSelectedRoom(ValueChangedEvent<Room> selected)
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.CurrentPlaylistItem))
updateSelectedItem();
}
private void updateSelectedItem()
=> SelectedItem.Value = Room.CurrentPlaylistItem;
private void updateSelectedRoom(ValueChangedEvent<Room?> selected)
{
if (selected.NewValue == Room)
selectionBox.FadeIn(transition_duration);
@@ -104,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
public bool FilteringActive { get; set; }
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Room.Name.Value };
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Room.Name };
private bool matchingFilter = true;
@@ -140,7 +157,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
if (e.Repeat)
return false;
if (SelectedRoom.Value != Room)
if (selectedRoom.Value != Room)
return false;
switch (e.Action)
@@ -157,18 +174,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
}
protected override bool ShouldBeConsideredForInput(Drawable child) => SelectedRoom.Value == Room || child is HoverSounds;
protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds;
protected override bool OnClick(ClickEvent e)
{
if (Room != SelectedRoom.Value)
if (Room != selectedRoom.Value)
{
sampleSelect?.Play();
SelectedRoom.Value = Room;
selectedRoom.Value = Room;
return true;
}
if (Room.HasPassword.Value)
if (Room.HasPassword)
{
this.ShowPopover();
return true;
@@ -179,12 +196,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
return true;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Room.PropertyChanged -= onRoomPropertyChanged;
}
public partial class PasswordEntryPopover : OsuPopover
{
private readonly Room room;
[Resolved(canBeNull: true)]
private LoungeSubScreen lounge { get; set; }
private LoungeSubScreen? lounge { get; set; }
public override bool HandleNonPositionalInput => true;
@@ -195,10 +218,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
this.room = room;
}
private OsuPasswordTextBox passwordTextBox;
private RoundedButton joinButton;
private OsuSpriteText errorText;
private Sample sampleJoinFail;
private OsuPasswordTextBox passwordTextBox = null!;
private RoundedButton joinButton = null!;
private OsuSpriteText errorText = null!;
private Sample? sampleJoinFail;
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Game.Online.Rooms;
@@ -19,21 +20,44 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
playlist.BindCollectionChanged((_, _) => PlaylistItem = playlist.GetCurrentItem());
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedRoom.BindValueChanged(onSelectedRoomChanged, true);
}
private void onSelectedRoomChanged(ValueChangedEvent<Room?> room)
{
if (room.OldValue != null)
playlist.UnbindFrom(room.OldValue.Playlist);
room.OldValue.PropertyChanged -= onRoomPropertyChanged;
if (room.NewValue != null)
playlist.BindTo(room.NewValue.Playlist);
else
playlist.Clear();
room.NewValue.PropertyChanged += onRoomPropertyChanged;
updateCurrentItem();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Playlist))
updateCurrentItem();
}
private void updateCurrentItem()
=> PlaylistItem = SelectedRoom.Value?.Playlist.GetCurrentItem();
public override bool OnExiting(ScreenExitEvent e)
{
// This screen never exits.
return true;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (SelectedRoom.Value != null)
SelectedRoom.Value.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -259,7 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
selectionLease.Return();
selectionLease = null;
if (SelectedRoom.Value?.RoomID.Value == null)
if (SelectedRoom.Value?.RoomID == null)
SelectedRoom.Value = new Room();
music?.EnsurePlayingSomething();
@@ -326,23 +326,23 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
/// <param name="room">The room to copy.</param>
public void OpenCopy(Room room)
{
Debug.Assert(room.RoomID.Value != null);
Debug.Assert(room.RoomID != null);
if (joiningRoomOperation != null)
return;
joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
var req = new GetRoomRequest(room.RoomID.Value.Value);
var req = new GetRoomRequest(room.RoomID.Value);
req.Success += r =>
{
// ID must be unset as we use this as a marker for whether this is a client-side (not-yet-created) room or not.
r.RoomID.Value = null;
r.RoomID = null;
// Null out dates because end date is not supported client-side and the settings overlay will populate a duration.
r.EndDate.Value = null;
r.Duration.Value = null;
r.EndDate = null;
r.Duration = null;
Open(r);
@@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.Chat;
using osu.Game.Online.Rooms;
@@ -10,8 +10,6 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
{
public partial class MatchChatDisplay : StandAloneChatDisplay
{
private readonly IBindable<int> channelId = new Bindable<int>();
[Resolved]
private ChannelManager? channelManager { get; set; }
@@ -29,23 +27,30 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
{
base.LoadComplete();
// Required for the time being since this component is created prior to the room being joined.
channelId.BindTo(room.ChannelId);
channelId.BindValueChanged(_ => updateChannel(), true);
room.PropertyChanged += onRoomPropertyChanged;
updateChannel();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.ChannelId))
updateChannel();
}
private void updateChannel()
{
if (room.RoomID.Value == null || channelId.Value == 0)
if (room.RoomID == null || room.ChannelId == 0)
return;
Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" });
Channel.Value = channelManager?.JoinChannel(new Channel { Id = room.ChannelId, Type = ChannelType.Multiplayer, Name = $"#lazermp_{room.RoomID.Value}" });
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
if (leaveChannelOnDispose)
channelManager?.LeaveChannel(Channel.Value);
}
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.ComponentModel;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
@@ -13,30 +12,44 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
{
public partial class MatchLeaderboard : Leaderboard<MatchLeaderboardScope, APIUserScoreAggregate>
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<long?> roomId { get; set; } = null!;
private readonly Room room;
[BackgroundDependencyLoader]
private void load()
public MatchLeaderboard(Room room)
{
roomId.BindValueChanged(id =>
{
if (id.NewValue == null)
return;
this.room = room;
}
SetScores(null);
RefetchScores();
}, true);
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
fetchInitialScores();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.RoomID))
fetchInitialScores();
}
private void fetchInitialScores()
{
if (room.RoomID == null)
return;
SetScores(null);
RefetchScores();
}
protected override bool IsOnlineScope => true;
protected override APIRequest? FetchScores(CancellationToken cancellationToken)
{
if (roomId.Value == null)
if (room.RoomID == null)
return null;
var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0);
var req = new GetRoomLeaderboardRequest(room.RoomID.Value);
req.Success += r => Schedule(() =>
{
@@ -52,6 +65,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index);
protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
public enum MatchLeaderboardScope
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -23,7 +21,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected const float TRANSITION_DURATION = 350;
protected const float FIELD_PADDING = 25;
protected OnlinePlayComposite Settings { get; set; }
protected Drawable Settings { get; set; } = null!;
protected override bool BlockScrollInput => false;
@@ -50,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected abstract void SelectBeatmap();
protected abstract OnlinePlayComposite CreateSettings(Room room);
protected abstract Drawable CreateSettings(Room room);
protected override void PopIn()
{
@@ -1,16 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using JetBrains.Annotations;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
@@ -21,26 +18,27 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
public partial class DrawableMatchRoom : DrawableRoom
{
public readonly IBindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
public Action OnEdit;
public Action? OnEdit;
public new required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
private readonly IBindable<APIUser> host = new Bindable<APIUser>();
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private readonly bool allowEdit;
[CanBeNull]
private Drawable editButton;
private BackgroundSprite background;
private Drawable? editButton;
public DrawableMatchRoom(Room room, bool allowEdit = true)
: base(room)
{
this.allowEdit = allowEdit;
host.BindTo(room.Host);
base.SelectedItem.BindTo(SelectedItem);
}
[BackgroundDependencyLoader]
@@ -62,17 +60,31 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
base.LoadComplete();
if (editButton != null)
host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true);
SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true);
Room.PropertyChanged += onRoomPropertyChanged;
updateRoomHost();
}
protected override Drawable CreateBackground() => background = new BackgroundSprite();
private partial class BackgroundSprite : UpdateableBeatmapBackgroundSprite
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
protected override double LoadDelay => 0;
if (e.PropertyName == nameof(Room.Host))
updateRoomHost();
}
private void updateRoomHost()
{
if (editButton != null)
editButton.Alpha = Room.Host?.Equals(api.LocalUser.Value) == true ? 1 : 0;
}
protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d =>
{
d.BackgroundLoadDelay = 0;
});
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
@@ -29,13 +30,13 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Match
{
[Cached(typeof(IPreviewTrackOwner))]
public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner
{
[Cached(typeof(IBindable<PlaylistItem>))]
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
public override bool? ApplyModTrackAdjustments => true;
@@ -60,8 +61,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
/// </summary>
protected readonly Bindable<IReadOnlyList<Mod>> UserMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
[Resolved(CanBeNull = true)]
private IOverlayManager overlayManager { get; set; }
@@ -83,6 +82,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!;
[Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; }
[Cached]
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
@@ -110,8 +112,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
this.allowEdit = allowEdit;
Padding = new MarginPadding { Top = Header.HEIGHT };
RoomId.BindTo(room.RoomID);
}
[BackgroundDependencyLoader]
@@ -164,7 +164,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
new DrawableMatchRoom(Room, allowEdit)
{
OnEdit = () => settingsOverlay.Show(),
SelectedItem = { BindTarget = SelectedItem }
SelectedItem = SelectedItem
}
},
null,
@@ -253,22 +253,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
base.LoadComplete();
RoomId.BindValueChanged(id =>
{
if (id.NewValue == null)
{
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
mainContent.Hide();
settingsOverlay.Show();
}
else
{
mainContent.Show();
settingsOverlay.Hide();
}
}, true);
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
@@ -276,24 +260,38 @@ namespace osu.Game.Screens.OnlinePlay.Match
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(UserModsSelectOverlay);
Room.PropertyChanged += onRoomPropertyChanged;
updateSetupState();
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
private void onRoomPropertyChanged(object sender, PropertyChangedEventArgs e)
{
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { Value = Room }
};
if (e.PropertyName == nameof(Room.RoomID))
updateSetupState();
}
[Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; }
private void updateSetupState()
{
if (Room.RoomID == null)
{
// A new room is being created.
// The main content should be hidden until the settings overlay is hidden, signaling the room is ready to be displayed.
mainContent.Hide();
settingsOverlay.Show();
}
else
{
mainContent.Show();
settingsOverlay.Hide();
}
}
protected virtual bool IsConnected => api.State.Value == APIState.Online;
public override bool OnBackButton()
{
if (Room.RoomID.Value == null)
if (Room.RoomID == null)
{
if (!ensureExitConfirmed())
return true;
@@ -366,7 +364,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (!IsConnected)
return true;
bool hasUnsavedChanges = Room.RoomID.Value == null && Room.Playlist.Count > 0;
bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0;
if (dialogOverlay == null || !hasUnsavedChanges)
return true;
@@ -539,6 +537,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
base.Dispose(isDisposing);
userModsSelectOverlayRegistration?.Dispose();
Room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -23,6 +23,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public partial class MatchStartControl : CompositeDrawable
{
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
@@ -32,9 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved]
private MultiplayerClient client { get; set; } = null!;
[Resolved]
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private readonly MultiplayerReadyButton readyButton;
private readonly MultiplayerCountdownButton countdownButton;
@@ -94,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
base.LoadComplete();
currentItem.BindValueChanged(_ => updateState());
SelectedItem.BindValueChanged(_ => updateState());
client.RoomUpdated += onRoomUpdated;
client.LoadRequested += onLoadRequested;
updateState();
@@ -210,7 +214,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
readyButton.Enabled.Value = countdownButton.Enabled.Value =
client.Room.State != MultiplayerRoomState.Closed
&& currentItem.Value?.ID == client.Room.Settings.PlaylistItemId
&& SelectedItem.Value?.ID == client.Room.Settings.PlaylistItemId
&& !client.Room.Playlist.Single(i => i.ID == client.Room.Settings.PlaylistItemId).Expired
&& !operationInProgress.Value;
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
@@ -13,6 +13,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private const float ready_button_width = 600;
private const float spectate_button_width = 200;
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
public MultiplayerMatchFooter()
{
RelativeSizeAxes = Axes.Both;
@@ -22,17 +30,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
new Drawable?[]
{
null,
new MultiplayerSpectateButton
{
RelativeSizeAxes = Axes.Both,
SelectedItem = selectedItem
},
null,
new MatchStartControl
{
RelativeSizeAxes = Axes.Both,
SelectedItem = selectedItem
},
null
}
@@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -28,14 +29,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public partial class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
{
private MatchSettings settings = null!;
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
protected override OsuButton SubmitButton => settings.ApplyButton;
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private MatchSettings settings = null!;
public MultiplayerMatchSettingsOverlay(Room room)
: base(room)
@@ -44,19 +51,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
protected override void SelectBeatmap() => settings.SelectBeatmap();
protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room)
protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room)
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y,
SettingsApplied = Hide
SettingsApplied = Hide,
SelectedItem = { BindTarget = SelectedItem }
};
protected partial class MatchSettings : OnlinePlayComposite
protected partial class MatchSettings : CompositeDrawable
{
private const float disabled_alpha = 0.2f;
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
public readonly Bindable<PlaylistItem?> SelectedItem = new Bindable<PlaylistItem?>();
public Action? SettingsApplied;
public OsuTextBox NameField = null!;
@@ -66,7 +75,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
public OsuTextBox PasswordTextBox = null!;
public OsuCheckbox AutoSkipCheckbox = null!;
public RoundedButton ApplyButton = null!;
public OsuSpriteText ErrorText = null!;
private OsuEnumDropdown<StartMode> startModeDropdown = null!;
@@ -270,7 +278,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
drawablePlaylist = new DrawableRoomPlaylist
{
RelativeSizeAxes = Axes.X,
Height = DrawableRoomPlaylistItem.HEIGHT
Height = DrawableRoomPlaylistItem.HEIGHT,
SelectedItem = { BindTarget = SelectedItem }
},
selectBeatmapButton = new RoundedButton
{
@@ -316,7 +325,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
ApplyButton = new CreateOrUpdateButton
ApplyButton = new CreateOrUpdateButton(room)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
@@ -343,14 +352,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
};
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true);
RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true);
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
RoomID.BindValueChanged(roomId => playlistContainer.Alpha = roomId.NewValue == null ? 1 : 0, true);
Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true);
QueueMode.BindValueChanged(mode => QueueModeDropdown.Current.Value = mode.NewValue, true);
AutoStartDuration.BindValueChanged(duration => startModeDropdown.Current.Value = (StartMode)(int)duration.NewValue.TotalSeconds, true);
AutoSkip.BindValueChanged(autoSkip => AutoSkipCheckbox.Current.Value = autoSkip.NewValue, true);
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
operationInProgress.BindValueChanged(v =>
@@ -366,15 +367,88 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
base.LoadComplete();
drawablePlaylist.Items.BindTo(Playlist);
drawablePlaylist.SelectedItem.BindTo(CurrentPlaylistItem);
room.PropertyChanged += onRoomPropertyChanged;
updateRoomName();
updateRoomType();
updateRoomQueueMode();
updateRoomPassword();
updateRoomAutoSkip();
updateRoomMaxParticipants();
updateRoomAutoStartDuration();
updateRoomPlaylist();
drawablePlaylist.Items.BindCollectionChanged((_, __) => room.Playlist = drawablePlaylist.Items.ToArray());
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Name):
updateRoomName();
break;
case nameof(Room.Type):
updateRoomName();
break;
case nameof(Room.QueueMode):
updateRoomQueueMode();
break;
case nameof(Room.Password):
updateRoomPassword();
break;
case nameof(Room.AutoSkip):
updateRoomAutoSkip();
break;
case nameof(Room.MaxParticipants):
updateRoomMaxParticipants();
break;
case nameof(Room.AutoStartDuration):
updateRoomAutoStartDuration();
break;
case nameof(Room.Playlist):
updateRoomPlaylist();
break;
}
}
private void updateRoomName()
=> NameField.Text = room.Name;
private void updateRoomType()
=> TypePicker.Current.Value = room.Type;
private void updateRoomQueueMode()
=> QueueModeDropdown.Current.Value = room.QueueMode;
private void updateRoomPassword()
=> PasswordTextBox.Text = room.Password ?? string.Empty;
private void updateRoomAutoSkip()
=> AutoSkipCheckbox.Current.Value = room.AutoSkip;
private void updateRoomMaxParticipants()
=> MaxParticipantsField.Text = room.MaxParticipants?.ToString();
private void updateRoomAutoStartDuration()
=> typeLabel.Text = room.AutoStartDuration.GetLocalisableDescription();
private void updateRoomPlaylist()
=> drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, room.Playlist);
protected override void Update()
{
base.Update();
ApplyButton.Enabled.Value = Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value;
ApplyButton.Enabled.Value = room.Playlist.Count > 0 && NameField.Text.Length > 0 && !operationInProgress.Value;
playlistContainer.Alpha = room.RoomID == null ? 1 : 0;
}
private void apply()
@@ -387,8 +461,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
Debug.Assert(applyingSettingsOperation == null);
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
TimeSpan autoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value);
// If the client is already in a room, update via the client.
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
if (client.Room != null)
@@ -398,7 +470,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
password: PasswordTextBox.Text,
matchType: TypePicker.Current.Value,
queueMode: QueueModeDropdown.Current.Value,
autoStartDuration: autoStartDuration,
autoStartDuration: TimeSpan.FromSeconds((int)startModeDropdown.Current.Value),
autoSkip: AutoSkipCheckbox.Current.Value)
.ContinueWith(t => Schedule(() =>
{
@@ -410,17 +482,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}
else
{
room.Name.Value = NameField.Text;
room.Type.Value = TypePicker.Current.Value;
room.Password.Value = PasswordTextBox.Current.Value;
room.QueueMode.Value = QueueModeDropdown.Current.Value;
room.AutoStartDuration.Value = autoStartDuration;
room.AutoSkip.Value = AutoSkipCheckbox.Current.Value;
room.Name = NameField.Text;
room.Type = TypePicker.Current.Value;
room.Password = PasswordTextBox.Current.Value;
room.QueueMode = QueueModeDropdown.Current.Value;
room.AutoStartDuration = TimeSpan.FromSeconds((int)startModeDropdown.Current.Value);
room.AutoSkip = AutoSkipCheckbox.Current.Value;
if (int.TryParse(MaxParticipantsField.Text, out int max))
room.MaxParticipants.Value = max;
room.MaxParticipants = max;
else
room.MaxParticipants.Value = null;
room.MaxParticipants = null;
manager.CreateRoom(room, onSuccess, onError);
}
@@ -448,7 +520,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
{
ErrorText.Text = "The selected beatmap is not available online.";
CurrentPlaylistItem.Value.MarkInvalid();
SelectedItem.Value?.MarkInvalid();
}
else
{
@@ -460,17 +532,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
applyingSettingsOperation.Dispose();
applyingSettingsOperation = null;
});
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
public partial class CreateOrUpdateButton : RoundedButton
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<long?> roomId { get; set; } = null!;
private readonly Room room;
protected override void LoadComplete()
public CreateOrUpdateButton(Room room)
{
base.LoadComplete();
roomId.BindValueChanged(id => Text = id.NewValue == null ? "Create" : "Update", true);
this.room = room;
}
[BackgroundDependencyLoader]
@@ -478,6 +554,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
BackgroundColour = colours.YellowDark;
}
protected override void Update()
{
base.Update();
Text = room.RoomID == null ? "Create" : "Update";
}
}
private enum StartMode
@@ -21,6 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public partial class MultiplayerSpectateButton : CompositeDrawable
{
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
[Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!;
@@ -30,13 +36,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved]
private MultiplayerClient client { get; set; } = null!;
[Resolved]
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private readonly RoundedButton button;
private IBindable<bool> operationInProgress = null!;
private readonly RoundedButton button;
public MultiplayerSpectateButton()
{
InternalChild = button = new RoundedButton
@@ -71,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
base.LoadComplete();
currentItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(checkForAutomaticDownload), true);
client.RoomUpdated += onRoomUpdated;
updateState();
}
@@ -117,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void checkForAutomaticDownload()
{
PlaylistItem? item = currentItem.Value;
PlaylistItem? item = SelectedItem.Value;
downloadCheckCancellation?.Cancel();
@@ -19,6 +19,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
public readonly Bindable<MultiplayerPlaylistDisplayMode> DisplayMode = new Bindable<MultiplayerPlaylistDisplayMode>();
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
/// <summary>
/// Invoked when an item requests to be edited.
/// </summary>
@@ -27,14 +33,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
[Resolved]
private MultiplayerClient client { get; set; } = null!;
[Resolved]
private IBindable<PlaylistItem?> currentItem { get; set; } = null!;
private readonly Room room;
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private MultiplayerPlaylistTabControl playlistTabControl = null!;
private MultiplayerQueueList queueList = null!;
private MultiplayerHistoryList historyList = null!;
private bool firstPopulation = true;
public MultiplayerPlaylist(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader]
private void load()
{
@@ -55,17 +65,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
Masking = true,
Children = new Drawable[]
{
queueList = new MultiplayerQueueList
queueList = new MultiplayerQueueList(room)
{
RelativeSizeAxes = Axes.Both,
SelectedItem = { BindTarget = currentItem },
SelectedItem = { BindTarget = selectedItem },
RequestEdit = item => RequestEdit?.Invoke(item)
},
historyList = new MultiplayerHistoryList
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
SelectedItem = { BindTarget = currentItem }
SelectedItem = { BindTarget = selectedItem }
}
}
}
@@ -1,12 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API;
@@ -21,28 +20,49 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
/// </summary>
public partial class MultiplayerQueueList : DrawableRoomPlaylist
{
public MultiplayerQueueList()
private readonly Room room;
private QueueFillFlowContainer flow = null!;
public MultiplayerQueueList(Room room)
{
this.room = room;
ShowItemOwners = true;
}
protected override FillFlowContainer<RearrangeableListItem<PlaylistItem>> CreateListFillFlowContainer() => new QueueFillFlowContainer
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateRoomPlaylist();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.Playlist))
updateRoomPlaylist();
}
private void updateRoomPlaylist()
=> flow.InvalidateLayout();
protected override FillFlowContainer<RearrangeableListItem<PlaylistItem>> CreateListFillFlowContainer() => flow = new QueueFillFlowContainer
{
Spacing = new Vector2(0, 2)
};
protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
private partial class QueueFillFlowContainer : FillFlowContainer<RearrangeableListItem<PlaylistItem>>
{
[Resolved(typeof(Room), nameof(Room.Playlist))]
private BindableList<PlaylistItem> roomPlaylist { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
roomPlaylist.BindCollectionChanged((_, _) => InvalidateLayout());
}
public new void InvalidateLayout() => base.InvalidateLayout();
public override IEnumerable<Drawable> FlowingChildren => base.FlowingChildren.OfType<RearrangeableListItem<PlaylistItem>>().OrderBy(item => item.Model.PlaylistOrder);
}
@@ -50,10 +70,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
private partial class QueuePlaylistItem : DrawableRoomPlaylistItem
{
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
[Resolved]
private MultiplayerClient multiplayerClient { get; set; }
private MultiplayerClient multiplayerClient { get; set; } = null!;
public QueuePlaylistItem(PlaylistItem item)
: base(item)
@@ -91,7 +111,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
{
base.Dispose(isDisposing);
if (multiplayerClient != null)
if (multiplayerClient.IsNotNull())
multiplayerClient.RoomUpdated -= onRoomUpdated;
}
}
@@ -73,8 +73,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Room CreateNewRoom() => new Room
{
Name = { Value = $"{api.LocalUser}'s awesome room" },
Type = { Value = MatchType.HeadToHead },
Name = $"{api.LocalUser}'s awesome room",
Type = MatchType.HeadToHead,
};
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
@@ -24,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private OngoingOperationTracker operationTracker { get; set; } = null!;
private readonly Room room;
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
private readonly PlaylistItem? itemToEdit;
@@ -38,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null)
: base(room, itemToEdit)
{
this.room = room;
this.itemToEdit = itemToEdit;
}
@@ -111,8 +113,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
else
{
Playlist.Clear();
Playlist.Add(item);
room.Playlist = [item];
this.Exit();
}
@@ -1,13 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
@@ -45,17 +44,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public override string ShortTitle => "room";
[Resolved]
private MultiplayerClient client { get; set; }
private MultiplayerClient client { get; set; } = null!;
[Resolved(canBeNull: true)]
private OsuGame game { get; set; }
private OsuGame? game { get; set; }
private AddItemButton addItemButton;
private AddItemButton addItemButton = null!;
public MultiplayerMatchSubScreen(Room room)
: base(room)
{
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
Title = room.RoomID == null ? "New room" : room.Name;
Activity.Value = new UserActivity.InLobby(room);
}
@@ -95,7 +94,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
},
Content = new[]
{
new Drawable[]
new Drawable?[]
{
// Participants column
new GridContainer
@@ -139,10 +138,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
null,
new Drawable[]
{
new MultiplayerPlaylist
new MultiplayerPlaylist(Room)
{
RelativeSizeAxes = Axes.Both,
RequestEdit = OpenSongSelection
RequestEdit = OpenSongSelection,
SelectedItem = SelectedItem
}
},
new[]
@@ -220,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
/// Opens the song selection screen to add or edit an item.
/// </summary>
/// <param name="itemToEdit">An optional playlist item to edit. If null, a new item will be added instead.</param>
internal void OpenSongSelection(PlaylistItem itemToEdit = null)
internal void OpenSongSelection(PlaylistItem? itemToEdit = null)
{
if (!this.IsCurrentScreen())
return;
@@ -228,9 +228,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit));
}
protected override Drawable CreateFooter() => new MultiplayerMatchFooter();
protected override Drawable CreateFooter() => new MultiplayerMatchFooter
{
SelectedItem = SelectedItem
};
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room);
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room)
{
SelectedItem = SelectedItem
};
protected override void UpdateMods()
{
@@ -245,7 +251,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
}
[Resolved(canBeNull: true)]
private IDialogOverlay dialogOverlay { get; set; }
private IDialogOverlay? dialogOverlay { get; set; }
private bool exitConfirmed;
@@ -275,8 +281,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return base.OnExiting(e);
}
private ModSettingChangeTracker modSettingChangeTracker;
private ScheduledDelegate debouncedModSettingsUpdate;
private ModSettingChangeTracker? modSettingChangeTracker;
private ScheduledDelegate? debouncedModSettingsUpdate;
private void onUserModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
{
@@ -352,7 +358,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Activity.Value = new UserActivity.InLobby(Room);
}
private bool localUserCanAddItem => client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly;
private bool localUserCanAddItem => client.IsHost || Room.QueueMode != QueueMode.HostOnly;
private void updateCurrentItem()
{
@@ -422,7 +428,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return;
// If there's only one playlist item and we are the host, assume we want to change it. Else add a new one.
PlaylistItem itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null;
PlaylistItem? itemToEdit = client.IsHost && Room.Playlist.Count == 1 ? Room.Playlist.Single() : null;
OpenSongSelection(itemToEdit);
@@ -434,7 +440,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.Dispose(isDisposing);
if (client != null)
if (client.IsNotNull())
{
client.RoomUpdated -= onRoomUpdated;
client.LoadRequested -= onLoadRequested;
@@ -1,14 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Graphics.UserInterface;
@@ -29,17 +28,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override UserActivity InitialActivity => new UserActivity.InMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
[Resolved]
private MultiplayerClient client { get; set; }
private MultiplayerClient client { get; set; } = null!;
private IBindable<bool> isConnected;
private IBindable<bool> isConnected = null!;
private readonly TaskCompletionSource<bool> resultsReady = new TaskCompletionSource<bool>();
private readonly MultiplayerRoomUser[] users;
private LoadingLayer loadingDisplay;
private MultiplayerGameplayLeaderboard multiplayerLeaderboard;
private LoadingLayer loadingDisplay = null!;
private MultiplayerGameplayLeaderboard multiplayerLeaderboard = null!;
/// <summary>
/// Construct a multiplayer player.
@@ -53,8 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
AllowPause = false,
AllowRestart = false,
AllowFailAnimation = false,
AllowSkipping = room.AutoSkip.Value,
AutomaticallySkipIntro = room.AutoSkip.Value,
AllowSkipping = room.AutoSkip,
AutomaticallySkipIntro = room.AutoSkip,
AlwaysShowLeaderboard = true,
})
{
@@ -153,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
GameplayClockContainer.Reset();
}
private void failAndBail(string message = null)
private void failAndBail(string? message = null)
{
if (!string.IsNullOrEmpty(message))
Logger.Log(message, LoggingTarget.Runtime, LogLevel.Important);
@@ -196,14 +193,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override ResultsScreen CreateResults(ScoreInfo score)
{
Debug.Assert(Room.RoomID.Value != null);
Debug.Assert(Room.RoomID != null);
return multiplayerLeaderboard.TeamScores.Count == 2
? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem, multiplayerLeaderboard.TeamScores)
? new MultiplayerTeamResultsScreen(score, Room.RoomID.Value, PlaylistItem, multiplayerLeaderboard.TeamScores)
{
ShowUserStatistics = true,
}
: new MultiplayerResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem)
: new MultiplayerResultsScreen(score, Room.RoomID.Value, PlaylistItem)
{
ShowUserStatistics = true
};
@@ -213,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.Dispose(isDisposing);
if (client != null)
if (client.IsNotNull())
{
client.GameplayStarted -= onGameplayStarted;
client.ResultsReady -= onResultsReady;
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
@@ -18,12 +16,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public partial class MultiplayerRoomManager : RoomManager
{
[Resolved]
private MultiplayerClient multiplayerClient { get; set; }
private MultiplayerClient multiplayerClient { get; set; } = null!;
public override void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError);
public override void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password, onSuccess, onError), onError);
public override void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
public override void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
if (!multiplayerClient.IsConnected.Value)
{
@@ -33,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
// this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join.
// should probably be done at a higher level, but due to the current structure of things this is the easiest place for now.
if (room.Status.Value is RoomStatusEnded)
if (room.Status is RoomStatusEnded)
{
onError?.Invoke("Cannot join an ended room.");
return;
@@ -51,9 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
multiplayerClient.LeaveRoom();
}
private void joinMultiplayerRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
private void joinMultiplayerRoom(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
Debug.Assert(room.RoomID.Value != null);
Debug.Assert(room.RoomID != null);
multiplayerClient.JoinRoom(room, password).ContinueWith(t =>
{
@@ -1,115 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Match;
namespace osu.Game.Screens.OnlinePlay
{
/// <summary>
/// A <see cref="CompositeDrawable"/> that exposes bindables for <see cref="Room"/> properties.
/// </summary>
public partial class OnlinePlayComposite : CompositeDrawable
{
[Resolved(typeof(Room))]
protected Bindable<long?> RoomID { get; private set; }
[Resolved(typeof(Room), nameof(Room.Name))]
protected Bindable<string> RoomName { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<APIUser> Host { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<RoomStatus> Status { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<MatchType> Type { get; private set; }
/// <summary>
/// The currently selected item in the <see cref="RoomSubScreen"/>, or the current item from <see cref="Playlist"/>
/// if this <see cref="OnlinePlayComposite"/> is not within a <see cref="RoomSubScreen"/>.
/// </summary>
[Resolved(typeof(Room))]
protected Bindable<PlaylistItem> CurrentPlaylistItem { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<Room.RoomPlaylistItemStats> PlaylistItemStats { get; private set; }
[Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<Room.RoomDifficultyRange> DifficultyRange { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<RoomCategory> Category { get; private set; }
[Resolved(typeof(Room))]
protected BindableList<APIUser> RecentParticipants { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<int> ParticipantCount { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<int?> MaxParticipants { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<int?> MaxAttempts { get; private set; }
[Resolved(typeof(Room))]
public Bindable<PlaylistAggregateScore> UserScore { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<DateTimeOffset?> StartDate { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<DateTimeOffset?> EndDate { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<RoomAvailability> Availability { get; private set; }
[Resolved(typeof(Room))]
public Bindable<string> Password { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<TimeSpan?> Duration { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<QueueMode> QueueMode { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<TimeSpan> AutoStartDuration { get; private set; }
[Resolved(typeof(Room))]
protected Bindable<bool> AutoSkip { get; private set; }
[Resolved(CanBeNull = true)]
private IBindable<PlaylistItem> subScreenSelectedItem { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
subScreenSelectedItem?.BindValueChanged(_ => UpdateSelectedItem());
Playlist.BindCollectionChanged((_, _) => UpdateSelectedItem(), true);
}
protected void UpdateSelectedItem()
{
// null room ID means this is a room in the process of being created.
if (RoomID.Value == null)
CurrentPlaylistItem.Value = Playlist.GetCurrentItem();
else if (subScreenSelectedItem != null)
CurrentPlaylistItem.Value = subScreenSelectedItem.Value;
}
}
}
@@ -32,9 +32,6 @@ namespace osu.Game.Screens.OnlinePlay
public override bool AllowEditing => false;
[Resolved(typeof(Room), nameof(Room.Playlist))]
protected BindableList<PlaylistItem> Playlist { get; private set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -22,9 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public partial class PlaylistsLoungeSubScreen : LoungeSubScreen
{
[Resolved]
private IAPIProvider api { get; set; }
private IAPIProvider api { get; set; } = null!;
private Dropdown<PlaylistsCategory> categoryDropdown;
private Dropdown<PlaylistsCategory> categoryDropdown = null!;
protected override IEnumerable<Drawable> CreateFilterControls()
{
@@ -67,8 +65,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
return new Room
{
Name = { Value = $"{api.LocalUser}'s awesome playlist" },
Type = { Value = MatchType.Playlists }
Name = $"{api.LocalUser}'s awesome playlist",
Type = MatchType.Playlists
};
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
@@ -21,11 +19,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public partial class PlaylistsPlayer : RoomSubmittingPlayer
{
public Action Exited;
public Action? Exited;
protected override UserActivity InitialActivity => new UserActivity.InPlaylistGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration configuration = null)
public PlaylistsPlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null)
: base(room, playlistItem, configuration)
{
}
@@ -57,8 +55,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override ResultsScreen CreateResults(ScoreInfo score)
{
Debug.Assert(Room.RoomID.Value != null);
return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem)
Debug.Assert(Room.RoomID != null);
return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem)
{
AllowRetry = true,
ShowUserStatistics = true,
@@ -1,9 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.ComponentModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -17,20 +16,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
public partial class PlaylistsReadyButton : ReadyButton
{
[Resolved(typeof(Room), nameof(Room.EndDate))]
private Bindable<DateTimeOffset?> endDate { get; set; }
[Resolved(typeof(Room), nameof(Room.MaxAttempts))]
private Bindable<int?> maxAttempts { get; set; }
[Resolved(typeof(Room), nameof(Room.UserScore))]
private Bindable<PlaylistAggregateScore> userScore { get; set; }
[Resolved]
private IBindable<WorkingBeatmap> gameBeatmap { get; set; }
private IBindable<WorkingBeatmap> gameBeatmap { get; set; } = null!;
public PlaylistsReadyButton()
private readonly Room room;
public PlaylistsReadyButton(Room room)
{
this.room = room;
Text = "Start";
}
@@ -46,15 +39,24 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
base.LoadComplete();
userScore.BindValueChanged(aggregate =>
{
if (maxAttempts.Value == null)
return;
room.PropertyChanged += onRoomPropertyChanged;
updateRoomUserScore();
}
int remaining = maxAttempts.Value.Value - aggregate.NewValue.PlaylistItemAttempts.Sum(a => a.Attempts);
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Room.UserScore))
updateRoomUserScore();
}
hasRemainingAttempts = remaining > 0;
});
private void updateRoomUserScore()
{
if (room.MaxAttempts == null || room.UserScore == null)
return;
int remaining = room.MaxAttempts.Value - room.UserScore.PlaylistItemAttempts.Sum(a => a.Attempts);
hasRemainingAttempts = remaining > 0;
}
protected override void Update()
@@ -80,6 +82,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private bool enoughTimeLeft =>
// This should probably consider the length of the currently selected item, rather than a constant 30 seconds.
endDate.Value != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value;
room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
}
@@ -1,26 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Rooms;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public partial class PlaylistsRoomFooter : CompositeDrawable
{
public Action OnStart;
public Action? OnStart;
public PlaylistsRoomFooter()
public PlaylistsRoomFooter(Room room)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new[]
{
new PlaylistsReadyButton
new PlaylistsReadyButton(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -3,6 +3,7 @@
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Humanizer;
using Humanizer.Localisation;
@@ -25,6 +26,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
using osuTK;
using osu.Game.Localisation;
using osu.Game.Rulesets;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
@@ -45,14 +47,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override void SelectBeatmap() => settings.SelectBeatmap();
protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room)
protected override Drawable CreateSettings(Room room) => settings = new MatchSettings(room)
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y,
EditPlaylist = () => EditPlaylist?.Invoke()
};
protected partial class MatchSettings : OnlinePlayComposite
protected partial class MatchSettings : CompositeDrawable
{
private const float disabled_alpha = 0.2f;
@@ -142,7 +144,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
LengthLimit = 100
LengthLimit = 100,
Text = room.Name
},
},
new Section("Duration")
@@ -313,12 +316,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
loadingLayer = new LoadingLayer(true)
};
RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true);
Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true);
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true);
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true);
DurationField.Current.BindValueChanged(duration =>
{
if (hasValidDuration)
@@ -332,11 +329,72 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
localUser = api.LocalUser.GetBoundCopy();
localUser.BindValueChanged(populateDurations, true);
playlist.Items.BindTo(Playlist);
Playlist.BindCollectionChanged(onPlaylistChanged, true);
}
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomPropertyChanged;
updateRoomName();
updateRoomAvailability();
updateRoomMaxParticipants();
updateRoomDuration();
updateRoomMaxAttempts();
updateRoomPlaylist();
playlist.Items.BindCollectionChanged((_, __) => room.Playlist = playlist.Items.ToArray());
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Name):
updateRoomName();
break;
case nameof(Room.Availability):
updateRoomAvailability();
break;
case nameof(Room.MaxParticipants):
updateRoomMaxParticipants();
break;
case nameof(Room.Duration):
updateRoomDuration();
break;
case nameof(Room.MaxAttempts):
updateRoomMaxAttempts();
break;
case nameof(Room.Playlist):
updateRoomPlaylist();
break;
}
}
private void updateRoomName()
=> NameField.Text = room.Name;
private void updateRoomAvailability()
=> AvailabilityPicker.Current.Value = room.Availability;
private void updateRoomMaxParticipants()
=> MaxParticipantsField.Text = room.MaxParticipants?.ToString();
private void updateRoomDuration()
=> DurationField.Current.Value = room.Duration ?? TimeSpan.FromMinutes(30);
private void updateRoomMaxAttempts()
=> MaxAttemptsField.Text = room.MaxAttempts?.ToString();
private void updateRoomPlaylist()
=> playlist.Items.ReplaceRange(0, playlist.Items.Count, room.Playlist);
private void populateDurations(ValueChangedEvent<APIUser> user)
{
// roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427)
@@ -370,9 +428,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public void SelectBeatmap() => editPlaylistButton.TriggerClick();
private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) =>
playlistLength.Text = $"Length: {Playlist.GetTotalDuration(rulesets)}";
playlistLength.Text = $"Length: {room.Playlist.GetTotalDuration(rulesets)}";
private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0
private bool hasValidSettings => room.RoomID == null && NameField.Text.Length > 0 && room.Playlist.Count > 0
&& hasValidDuration;
private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter;
@@ -384,20 +442,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
hideError();
RoomName.Value = NameField.Text;
Availability.Value = AvailabilityPicker.Current.Value;
if (int.TryParse(MaxParticipantsField.Text, out int max))
MaxParticipants.Value = max;
else
MaxParticipants.Value = null;
if (int.TryParse(MaxAttemptsField.Text, out max))
MaxAttempts.Value = max;
else
MaxAttempts.Value = null;
Duration.Value = DurationField.Current.Value;
room.Name = NameField.Text;
room.Availability = AvailabilityPicker.Current.Value;
room.MaxParticipants = int.TryParse(MaxParticipantsField.Text, out int maxParticipants) ? maxParticipants : null;
room.MaxAttempts = int.TryParse(MaxAttemptsField.Text, out int maxAttempts) ? maxAttempts : null;
room.Duration = DurationField.Current.Value;
loadingLayer.Show();
manager?.CreateRoom(room, onSuccess, onError);
@@ -422,7 +471,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
.Select(int.Parse)
.ToArray();
foreach (var item in Playlist)
foreach (var item in room.Playlist)
{
if (invalidBeatmapIDs.Contains(item.Beatmap.OnlineID))
item.MarkInvalid();
@@ -436,6 +485,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
ErrorText.FadeIn(50);
loadingLayer.Hide();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomPropertyChanged;
}
}
public partial class CreateRoomButton : RoundedButton
@@ -1,11 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -22,6 +20,7 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
@@ -33,20 +32,23 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private readonly IBindable<bool> isIdle = new BindableBool();
private MatchLeaderboard leaderboard;
private SelectionPollingComponent selectionPollingComponent;
[Resolved(CanBeNull = true)]
private IdleTracker? idleTracker { get; set; }
private FillFlowContainer progressSection;
private MatchLeaderboard leaderboard = null!;
private SelectionPollingComponent selectionPollingComponent = null!;
private FillFlowContainer progressSection = null!;
private DrawableRoomPlaylist drawablePlaylist = null!;
public PlaylistsRoomSubScreen(Room room)
: base(room, false) // Editing is temporarily not allowed.
{
Title = room.RoomID.Value == null ? "New playlist" : room.Name.Value;
Title = room.RoomID == null ? "New playlist" : room.Name;
Activity.Value = new UserActivity.InLobby(room);
}
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] IdleTracker idleTracker)
[BackgroundDependencyLoader]
private void load()
{
if (idleTracker != null)
isIdle.BindTo(idleTracker.IsIdle);
@@ -59,19 +61,47 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
base.LoadComplete();
isIdle.BindValueChanged(_ => updatePollingRate(), true);
RoomId.BindValueChanged(id =>
{
if (id.NewValue != null)
{
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault());
}
}, true);
Room.MaxAttempts.BindValueChanged(_ => progressSection.Alpha = Room.MaxAttempts.Value != null ? 1 : 0, true);
Room.PropertyChanged += onRoomPropertyChanged;
updateSetupState();
updateRoomMaxAttempts();
updateRoomPlaylist();
}
private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.RoomID):
updateSetupState();
break;
case nameof(Room.MaxAttempts):
updateRoomMaxAttempts();
break;
case nameof(Room.Playlist):
updateRoomPlaylist();
break;
}
}
private void updateSetupState()
{
if (Room.RoomID != null)
{
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
Schedule(() => SelectedItem.Value = Room.Playlist.FirstOrDefault());
}
}
private void updateRoomMaxAttempts()
=> progressSection.Alpha = Room.MaxAttempts != null ? 1 : 0;
private void updateRoomPlaylist()
=> drawablePlaylist.Items.ReplaceRange(0, drawablePlaylist.Items.Count, Room.Playlist);
protected override Drawable CreateMainContent() => new Container
{
RelativeSizeAxes = Axes.Both,
@@ -92,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
},
Content = new[]
{
new Drawable[]
new Drawable?[]
{
// Playlist items column
new GridContainer
@@ -101,20 +131,19 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Padding = new MarginPadding { Right = 5 },
Content = new[]
{
new Drawable[] { new OverlinedPlaylistHeader(), },
new Drawable[] { new OverlinedPlaylistHeader(Room), },
new Drawable[]
{
new DrawableRoomPlaylist
drawablePlaylist = new DrawableRoomPlaylist
{
RelativeSizeAxes = Axes.Both,
Items = { BindTarget = Room.Playlist },
SelectedItem = { BindTarget = SelectedItem },
AllowSelection = true,
AllowShowingResults = true,
RequestResults = item =>
{
Debug.Assert(RoomId.Value != null);
ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, RoomId.Value.Value, item));
Debug.Assert(Room.RoomID != null);
ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item));
}
}
},
@@ -183,7 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Children = new Drawable[]
{
new OverlinedHeader("Progress"),
new RoomLocalUserInfo(),
new RoomLocalUserInfo(Room),
}
},
},
@@ -191,7 +220,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
new OverlinedHeader("Leaderboard")
},
new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, },
new Drawable[] { leaderboard = new MatchLeaderboard(Room) { RelativeSizeAxes = Axes.Both }, },
},
RowDimensions = new[]
{
@@ -224,7 +253,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
}
};
protected override Drawable CreateFooter() => new PlaylistsRoomFooter
protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room)
{
OnStart = StartPlay
};
@@ -248,5 +277,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
Exited = () => leaderboard.RefetchScores()
});
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Room.PropertyChanged -= onRoomPropertyChanged;
}
}
}

Some files were not shown because too many files have changed in this diff Show More