1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-11 03:37:20 +08:00

Merge pull request #31637 from smoogipoo/room-management-lio

Create, join, and part multiplayer rooms only via the multiplayer server
This commit is contained in:
Dean Herbert 2025-03-04 13:13:55 +09:00 committed by GitHub
commit 58a671decb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 501 additions and 547 deletions

View File

@ -9,7 +9,6 @@ using osu.Framework.Testing;
using osu.Game.Extensions;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.NonVisual.Multiplayer
@ -73,10 +72,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("create room initially in gameplay", () =>
{
var newRoom = new Room();
newRoom.CopyFrom(SelectedRoom.Value!);
newRoom.RoomID = null;
MultiplayerClient.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
@ -87,7 +82,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
});
};
RoomManager.CreateRoom(newRoom);
MultiplayerClient.JoinRoom(MultiplayerClient.ServerSideRooms.Single()).ConfigureAwait(false);
});
AddUntilStep("wait for room join", () => RoomJoined);

View File

@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Bindable<LocalUserPlayingState> playingState = new Bindable<LocalUserPlayingState>();
GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), healthProcessor: new OsuHealthProcessor(0), localUserPlayingState: playingState);
TestSpectatorClient spectatorClient = new TestSpectatorClient();
TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestMultiplayerRoomManager(new TestRoomRequestsHandler()));
TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestRoomRequestsHandler());
AddStep("create spectator list", () =>
{

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load()
{
var mockLounge = new Mock<LoungeSubScreen>();
var mockLounge = new Mock<IOnlinePlayLounge>();
mockLounge
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string>>()))
.Callback<Room, string, Action<Room>, Action<string>>((_, _, _, d) =>

View File

@ -58,7 +58,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayerComponents multiplayerComponents = null!;
private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient;
private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
@ -257,7 +256,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Playlist =
@ -286,7 +285,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Playlist =
@ -336,7 +335,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Password = "password",
@ -789,7 +788,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
@ -810,8 +809,8 @@ 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 = "New name";
roomManager.ServerSideRooms[0].Playlist =
multiplayerClient.ServerSideRooms[0].Name = "New name";
multiplayerClient.ServerSideRooms[0].Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
@ -828,7 +827,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("local room has correct settings", () =>
{
var localRoom = this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().Room;
return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
return localRoom.Name == multiplayerClient.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
});
}

View File

@ -8,36 +8,33 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
public partial class TestSceneMultiplayerLoungeSubScreen : MultiplayerTestScene
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen = null!;
private Room? lastJoinedRoom;
private string? lastJoinedPassword;
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
public TestSceneMultiplayerLoungeSubScreen()
: base(false)
{
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen()));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
AddStep("bind to event", () =>
{
lastJoinedRoom = null;
lastJoinedPassword = null;
RoomManager.JoinRoomRequested = onRoomJoined;
});
}
[Test]
@ -47,8 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter));
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == null);
AddAssert("room joined", () => MultiplayerClient.RoomJoined);
}
[Test]
@ -67,6 +63,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("hit escape", () => InputManager.Key(Key.Escape));
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@ -79,6 +77,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddStep("exit screen", () => Stack.Exit());
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@ -93,9 +93,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible);
AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@ -110,9 +112,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible);
AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@ -127,8 +131,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == "password");
AddUntilStep("room joined", () => MultiplayerClient.RoomJoined);
}
[Test]
@ -143,14 +146,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == "password");
AddAssert("room joined", () => MultiplayerClient.RoomJoined);
}
private void onRoomJoined(Room room, string? password)
{
lastJoinedRoom = room;
lastJoinedPassword = password;
}
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies();
}
}

View File

@ -317,6 +317,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
}
[Test]
public void TestChangeSettingsButtonVisibleForHost()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value!.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
AddUntilStep("button visible", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.GreaterThan(0));
AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }));
AddStep("make other user host", () => MultiplayerClient.TransferHost(PLAYER_1_ID));
AddAssert("button hidden", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.EqualTo(0));
}
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{
[Resolved(canBeNull: true)]

View File

@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
addItemStep();
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0));
@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
assertQueueTabCount(2);
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
assertQueueTabCount(0);
}
@ -157,12 +157,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
{
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("join room with items", () =>
{
RoomManager.CreateRoom(new Room
API.Queue(new CreateRoomRequest(new Room
{
Name = "test name",
Playlist =
@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Expired = true
}
]
});
}));
});
AddUntilStep("wait for room join", () => RoomJoined);

View File

@ -36,6 +36,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestManyRooms()
{
AddStep("add rooms", () => RoomManager.AddRooms(500));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 500);
}
[Test]
@ -75,6 +76,7 @@ namespace osu.Game.Tests.Visual.Playlists
public void TestEnteringRoomTakesLeaseOnSelection()
{
AddStep("add rooms", () => RoomManager.AddRooms(1));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 1);
AddAssert("selected room is not disabled", () => !loungeScreen.SelectedRoom.Disabled);

View File

@ -3,14 +3,13 @@
using System;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Visual.OnlinePlay;
@ -21,13 +20,33 @@ namespace osu.Game.Tests.Visual.Playlists
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestRoomSettings settings = null!;
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
private Func<Room, string?>? handleRequest;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("setup api", () =>
{
handleRequest = null;
((DummyAPIAccess)API).HandleRequest = req =>
{
if (req is not CreateRoomRequest createReq || handleRequest == null)
return false;
if (handleRequest(createReq.Room) is string errorText)
createReq.TriggerFailure(new APIException(errorText, null));
else
{
var createdRoom = new APICreatedRoom();
createdRoom.CopyFrom(createReq.Room);
createReq.TriggerSuccess(createdRoom);
}
return true;
};
});
AddStep("create overlay", () =>
{
SelectedRoom.Value = new Room();
@ -75,10 +94,10 @@ namespace osu.Game.Tests.Visual.Playlists
settings.DurationField.Current.Value = expectedDuration;
SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = r =>
handleRequest = r =>
{
createdRoom = r;
return string.Empty;
return null;
};
});
@ -103,7 +122,7 @@ namespace osu.Game.Tests.Visual.Playlists
errorMessage = $"{not_found_prefix} {beatmap.OnlineID}";
RoomManager.CreateRequested = _ => errorMessage;
handleRequest = _ => errorMessage;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
@ -128,7 +147,7 @@ namespace osu.Game.Tests.Visual.Playlists
SelectedRoom.Value!.Name = "Test Room";
SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = _ => failText;
handleRequest = _ => failText;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
@ -159,48 +178,5 @@ namespace osu.Game.Tests.Visual.Playlists
{
}
}
private class TestDependencies : OnlinePlayTestSceneDependencies
{
protected override IRoomManager CreateRoomManager() => new TestRoomManager();
}
protected class TestRoomManager : IRoomManager
{
public Func<Room, string>? CreateRequested;
public event Action RoomsUpdated
{
add { }
remove { }
}
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
public IBindableList<Room> Rooms => null!;
public void AddOrUpdateRoom(Room room) => throw new NotImplementedException();
public void RemoveRoom(Room room) => throw new NotImplementedException();
public void ClearRooms() => throw new NotImplementedException();
public void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
if (CreateRequested == null)
return;
string error = CreateRequested.Invoke(room);
if (!string.IsNullOrEmpty(error))
onError?.Invoke(error);
else
onSuccess?.Invoke(room);
}
public void JoinRoom(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
}
}
}

View File

@ -11,7 +11,6 @@ using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
@ -26,15 +25,12 @@ namespace osu.Game.Tests.Visual
/// <item>Provides a <see cref="TestMultiplayerClient"/> to be resolved as a dependency in the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen,
/// which is typically a part of <see cref="OsuGameBase"/>.</item>
/// <item>Rebinds the <see cref="DummyAPIAccess"/> to handle requests via a <see cref="TestRoomRequestsHandler"/>.</item>
/// <item>Provides a <see cref="TestMultiplayerRoomManager"/> for the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen.</item>
/// </list>
/// </p>
/// </summary>
public partial class TestMultiplayerComponents : OsuScreen
{
public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen => multiplayerScreen;
public TestMultiplayerRoomManager RoomManager => multiplayerScreen.RoomManager;
public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen { get; }
public IScreen CurrentScreen => screenStack.CurrentScreen;
@ -53,17 +49,17 @@ namespace osu.Game.Tests.Visual
private BeatmapManager beatmapManager { get; set; }
private readonly OsuScreenStack screenStack;
private readonly TestMultiplayer multiplayerScreen;
private readonly TestRoomRequestsHandler requestsHandler = new TestRoomRequestsHandler();
public TestMultiplayerComponents()
{
multiplayerScreen = new TestMultiplayer();
MultiplayerScreen = new Screens.OnlinePlay.Multiplayer.Multiplayer();
InternalChildren = new Drawable[]
{
userLookupCache,
beatmapLookupCache,
MultiplayerClient = new TestMultiplayerClient(RoomManager),
MultiplayerClient = new TestMultiplayerClient(requestsHandler),
screenStack = new OsuScreenStack
{
Name = nameof(TestMultiplayerComponents),
@ -71,13 +67,13 @@ namespace osu.Game.Tests.Visual
}
};
screenStack.Push(multiplayerScreen);
screenStack.Push(MultiplayerScreen);
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager);
((DummyAPIAccess)api).HandleRequest = request => requestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager);
}
public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton();
@ -90,13 +86,5 @@ namespace osu.Game.Tests.Visual
screenStack.Exit();
return true;
}
private partial class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
{
public new TestMultiplayerRoomManager RoomManager { get; private set; }
public TestRoomRequestsHandler RequestsHandler { get; private set; }
protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler());
}
}
}

View File

@ -10,6 +10,13 @@ namespace osu.Game.Online.Multiplayer
/// </summary>
public interface IMultiplayerLoungeServer
{
/// <summary>
/// Request to create a multiplayer room.
/// </summary>
/// <param name="room">The room to create.</param>
/// <returns>The created multiplayer room.</returns>
Task<MultiplayerRoom> CreateRoom(MultiplayerRoom room);
/// <summary>
/// Request to join a multiplayer room.
/// </summary>

View File

@ -171,42 +171,73 @@ namespace osu.Game.Online.Multiplayer
private CancellationTokenSource? joinCancellationSource;
/// <summary>
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
/// Creates and joins a <see cref="MultiplayerRoom"/> described by an API <see cref="Room"/>.
/// </summary>
/// <param name="room">The API <see cref="Room"/>.</param>
/// <param name="password">An optional password to use for the join operation.</param>
public async Task JoinRoom(Room room, string? password = null)
/// <param name="room">The API <see cref="Room"/> describing the room to create.</param>
/// <exception cref="InvalidOperationException">If the current user is already in another room.</exception>
public async Task CreateRoom(Room room)
{
if (Room != null)
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
throw new InvalidOperationException("Cannot create a multiplayer room while already in one.");
var cancellationSource = joinCancellationSource = new CancellationTokenSource();
await joinOrLeaveTaskChain.Add(async () =>
{
Debug.Assert(room.RoomID != null);
var multiplayerRoom = await CreateRoomInternal(new MultiplayerRoom(room)).ConfigureAwait(false);
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
}
// Join the server-side room.
var joinedRoom = await JoinRoom(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
Debug.Assert(joinedRoom != null);
/// <summary>
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
/// </summary>
/// <param name="room">The API <see cref="Room"/>.</param>
/// <param name="password">An optional password to use for the join operation.</param>
/// <exception cref="InvalidOperationException">If the current user is already in another room, or <paramref name="room"/> does not represent an active room.</exception>
public async Task JoinRoom(Room room, string? password = null)
{
if (Room != null)
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
if (room.RoomID == null)
throw new InvalidOperationException("Cannot join an inactive room.");
var cancellationSource = joinCancellationSource = new CancellationTokenSource();
await joinOrLeaveTaskChain.Add(async () =>
{
var multiplayerRoom = await JoinRoomInternal(room.RoomID.Value, password ?? room.Password).ConfigureAwait(false);
await setupJoinedRoom(room, multiplayerRoom, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
}
/// <summary>
/// Performs post-join setup of a <see cref="MultiplayerRoom"/>.
/// </summary>
/// <param name="apiRoom">The incoming API <see cref="Room"/> that was requested to be joined.</param>
/// <param name="joinedRoom">The resuling <see cref="MultiplayerRoom"/> that was joined.</param>
/// <param name="cancellationToken">A token to cancel the process.</param>
private async Task setupJoinedRoom(Room apiRoom, MultiplayerRoom joinedRoom, CancellationToken cancellationToken)
{
// Populate users.
Debug.Assert(joinedRoom.Users != null);
await PopulateUsers(joinedRoom.Users).ConfigureAwait(false);
if (joinedRoom.Host != null)
await PopulateUsers([joinedRoom.Host]).ConfigureAwait(false);
// Update the stored room (must be done on update thread for thread-safety).
await runOnUpdateThreadAsync(() =>
{
Debug.Assert(Room == null);
Debug.Assert(APIRoom == null);
Room = joinedRoom;
APIRoom = room;
Debug.Assert(joinedRoom.Playlist.Count > 0);
APIRoom = apiRoom;
APIRoom.RoomID = joinedRoom.RoomID;
APIRoom.Host = joinedRoom.Host?.User;
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 = null;
@ -221,8 +252,7 @@ namespace osu.Game.Online.Multiplayer
postServerShuttingDownNotification();
OnRoomJoined();
}, cancellationSource.Token).ConfigureAwait(false);
}, cancellationSource.Token).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
}
/// <summary>
@ -232,16 +262,11 @@ namespace osu.Game.Online.Multiplayer
{
}
/// <summary>
/// Joins the <see cref="MultiplayerRoom"/> with a given ID.
/// </summary>
/// <param name="roomId">The room ID.</param>
/// <param name="password">An optional password to use when joining the room.</param>
/// <returns>The joined <see cref="MultiplayerRoom"/>.</returns>
protected abstract Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null);
public Task LeaveRoom()
{
if (Room == null)
return Task.CompletedTask;
// The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled.
// This includes the setting of Room itself along with the initial update of the room settings on join.
joinCancellationSource?.Cancel();
@ -265,6 +290,24 @@ namespace osu.Game.Online.Multiplayer
});
}
/// <summary>
/// Creates the <see cref="MultiplayerRoom"/> with the given settings.
/// </summary>
/// <param name="room">The room.</param>
/// <returns>The joined <see cref="MultiplayerRoom"/></returns>
protected abstract Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room);
/// <summary>
/// Joins the <see cref="MultiplayerRoom"/> with a given ID.
/// </summary>
/// <param name="roomId">The room ID.</param>
/// <param name="password">An optional password to use when joining the room.</param>
/// <returns>The joined <see cref="MultiplayerRoom"/>.</returns>
protected abstract Task<MultiplayerRoom> JoinRoomInternal(long roomId, string? password = null);
/// <summary>
/// Leaves the currently-joined <see cref="MultiplayerRoom"/>.
/// </summary>
protected abstract Task LeaveRoomInternal();
public abstract Task InvitePlayer(int userId);

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
namespace osu.Game.Online.Multiplayer
@ -16,12 +17,8 @@ namespace osu.Game.Online.Multiplayer
{
if (t.IsFaulted)
{
Exception? exception = t.Exception;
if (exception is AggregateException ae)
exception = ae.InnerException;
Debug.Assert(exception != null);
Debug.Assert(t.Exception != null);
Exception exception = t.Exception.AsSingular();
if (exception.GetHubExceptionMessage() is string message)
// Hub exceptions generally contain something we can show the user directly.

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MessagePack;
using Newtonsoft.Json;
using osu.Game.Online.Rooms;
@ -65,6 +66,14 @@ namespace osu.Game.Online.Multiplayer
RoomID = roomId;
}
public MultiplayerRoom(Room room)
{
RoomID = room.RoomID ?? 0;
Settings = new MultiplayerRoomSettings(room);
Host = room.Host != null ? new MultiplayerRoomUser(room.Host.OnlineID) : null;
Playlist = room.Playlist.Select(p => new MultiplayerPlaylistItem(p)).ToArray();
}
public override string ToString() => $"RoomID:{RoomID} Host:{Host?.UserID} Users:{Users.Count} State:{State} Settings: [{Settings}]";
}
}

View File

@ -35,6 +35,20 @@ namespace osu.Game.Online.Multiplayer
[IgnoreMember]
public bool AutoStartEnabled => AutoStartDuration != TimeSpan.Zero;
public MultiplayerRoomSettings()
{
}
public MultiplayerRoomSettings(Room room)
{
Name = room.Name;
Password = room.Password ?? string.Empty;
MatchType = room.Type;
QueueMode = room.QueueMode;
AutoStartDuration = room.AutoStartDuration;
AutoSkip = room.AutoSkip;
}
public bool Equals(MultiplayerRoomSettings? other)
{
if (ReferenceEquals(this, other)) return true;

View File

@ -76,7 +76,32 @@ namespace osu.Game.Online.Multiplayer
}
}
protected override async Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
protected override async Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room)
{
if (!IsConnected.Value)
throw new OperationCanceledException();
Debug.Assert(connection != null);
try
{
return await connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.CreateRoom), room).ConfigureAwait(false);
}
catch (HubException exception)
{
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
{
Debug.Assert(connector != null);
await connector.Reconnect().ConfigureAwait(false);
return await CreateRoomInternal(room).ConfigureAwait(false);
}
throw;
}
}
protected override async Task<MultiplayerRoom> JoinRoomInternal(long roomId, string? password = null)
{
if (!IsConnected.Value)
throw new OperationCanceledException();
@ -94,7 +119,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(connector != null);
await connector.Reconnect().ConfigureAwait(false);
return await JoinRoom(roomId, password).ConfigureAwait(false);
return await JoinRoomInternal(roomId, password).ConfigureAwait(false);
}
throw;

View File

@ -15,6 +15,9 @@ namespace osu.Game.Online.Rooms
public CreateRoomRequest(Room room)
{
Room = room;
// Also copy back to the source model, since it is likely to have been stored elsewhere.
Success += r => Room.CopyFrom(r);
}
protected override WebRequest CreateWebRequest()
@ -23,7 +26,6 @@ namespace osu.Game.Online.Rooms
req.ContentType = "application/json";
req.Method = HttpMethod.Post;
req.AddRaw(JsonConvert.SerializeObject(Room));
return req;

View File

@ -16,6 +16,9 @@ namespace osu.Game.Online.Rooms
{
Room = room;
Password = password;
// Also copy back to the source model, since it is likely to have been stored elsewhere.
Success += r => Room.CopyFrom(r);
}
protected override WebRequest CreateWebRequest()

View File

@ -66,5 +66,20 @@ namespace osu.Game.Online.Rooms
public MultiplayerPlaylistItem()
{
}
public MultiplayerPlaylistItem(PlaylistItem item)
{
ID = item.ID;
OwnerID = item.OwnerID;
BeatmapID = item.Beatmap.OnlineID;
BeatmapChecksum = item.Beatmap.MD5Hash;
RulesetID = item.RulesetID;
RequiredMods = item.RequiredMods.ToArray();
AllowedMods = item.AllowedMods.ToArray();
Expired = item.Expired;
PlaylistOrder = item.PlaylistOrder ?? 0;
PlayedAt = item.PlayedAt;
StarRating = item.Beatmap.StarRating;
}
}
}

View File

@ -342,6 +342,23 @@ namespace osu.Game.Online.Rooms
// Not yet serialised (not implemented).
private RoomAvailability availability;
public Room()
{
}
public Room(MultiplayerRoom room)
{
RoomID = room.RoomID;
Name = room.Settings.Name;
Password = room.Settings.Password;
Type = room.Settings.MatchType;
QueueMode = room.Settings.QueueMode;
AutoStartDuration = room.Settings.AutoStartDuration;
AutoSkip = room.Settings.AutoSkip;
Host = room.Host != null ? new APIUser { Id = room.Host.UserID } : null;
Playlist = room.Playlist.Select(p => new PlaylistItem(p)).ToArray();
}
/// <summary>
/// Copies values from another <see cref="Room"/> into this one.
/// </summary>

View File

@ -5,16 +5,15 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
// Todo: This class should be inlined into the lounge.
public partial class RoomManager : Component, IRoomManager
{
public event Action? RoomsUpdated;
@ -23,89 +22,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
public IBindableList<Room> Rooms => rooms;
protected IBindable<Room?> JoinedRoom => joinedRoom;
private readonly Bindable<Room?> joinedRoom = new Bindable<Room?>();
[Resolved]
private IAPIProvider api { get; set; } = null!;
public RoomManager()
{
RelativeSizeAxes = Axes.Both;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
PartRoom();
}
public virtual void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
room.Host = api.LocalUser.Value;
var req = new CreateRoomRequest(room);
req.Success += result =>
{
joinedRoom.Value = room;
AddOrUpdateRoom(result);
room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere.
// The server may not contain all properties (such as password), so invoke success with the given room.
onSuccess?.Invoke(room);
};
req.Failure += exception =>
{
onError?.Invoke(req.Response?.Error ?? exception.Message);
};
api.Queue(req);
}
private JoinRoomRequest? currentJoinRoomRequest;
public virtual void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
currentJoinRoomRequest?.Cancel();
currentJoinRoomRequest = new JoinRoomRequest(room, password);
currentJoinRoomRequest.Success += result =>
{
joinedRoom.Value = room;
AddOrUpdateRoom(result);
room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere.
onSuccess?.Invoke(room);
};
currentJoinRoomRequest.Failure += exception =>
{
if (exception is OperationCanceledException)
return;
onError?.Invoke(exception.Message);
};
api.Queue(currentJoinRoomRequest);
}
public virtual void PartRoom()
{
currentJoinRoomRequest?.Cancel();
if (joinedRoom.Value == null)
return;
if (api.State.Value == APIState.Online)
api.Queue(new PartRoomRequest(joinedRoom.Value));
joinedRoom.Value = null;
}
private readonly HashSet<long> ignoredRooms = new HashSet<long>();
public void AddOrUpdateRoom(Room room)

View File

@ -34,7 +34,6 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.DailyChallenge.Events;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Match.Components;
@ -71,9 +70,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
[Cached(Type = typeof(IRoomManager))]
private RoomManager roomManager { get; set; }
[Cached]
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
@ -115,7 +111,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
this.room = room;
playlistItem = room.Playlist.Single();
roomManager = new RoomManager();
Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING };
}
@ -131,7 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
roomManager,
beatmapAvailabilityTracker,
new ScreenStack(new RoomBackgroundScreen(playlistItem))
{
@ -426,7 +420,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
base.OnEntering(e);
waves.Show();
roomManager.JoinRoom(room);
API.Queue(new JoinRoomRequest(room, null));
startLoopingTrack(this, musicController);
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID!.Value).ContinueWith(t =>
@ -480,7 +474,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
previewTrackManager.StopAnyPlaying(this);
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
roomManager.PartRoom();
API.Queue(new PartRoomRequest(room));
metadataClient.EndWatchingMultiplayerRoom(room.RoomID!.Value).FireAndForget();
return base.OnExiting(e);

View File

@ -38,27 +38,5 @@ namespace osu.Game.Screens.OnlinePlay
/// Removes all <see cref="Room"/>s from this <see cref="IRoomManager"/>.
/// </summary>
void ClearRooms();
/// <summary>
/// Creates a new <see cref="Room"/>.
/// </summary>
/// <param name="room">The <see cref="Room"/> to create.</param>
/// <param name="onSuccess">An action to be invoked if the creation succeeds.</param>
/// <param name="onError">An action to be invoked if an error occurred.</param>
void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null);
/// <summary>
/// Joins a <see cref="Room"/>.
/// </summary>
/// <param name="room">The <see cref="Room"/> to join. <see cref="Room.RoomID"/> must be populated.</param>
/// <param name="password">An optional password to use for the join operation.</param>
/// <param name="onSuccess"></param>
/// <param name="onError"></param>
void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null);
/// <summary>
/// Parts the currently-joined <see cref="Room"/>.
/// </summary>
void PartRoom();
}
}

View File

@ -24,7 +24,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
@ -51,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
}
[Resolved(canBeNull: true)]
private LoungeSubScreen? lounge { get; set; }
private IOnlinePlayLounge? lounge { get; set; }
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
@ -170,12 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () =>
{
dialogOverlay?.Push(new ClosePlaylistDialog(Room, () =>
{
var request = new ClosePlaylistRequest(Room.RoomID!.Value);
request.Success += () => lounge?.RefreshRooms();
api.Queue(request);
}));
dialogOverlay?.Push(new ClosePlaylistDialog(Room, () => lounge?.Close(Room)));
}));
}
@ -238,7 +232,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private readonly Room room;
[Resolved(canBeNull: true)]
private LoungeSubScreen? lounge { get; set; }
private IOnlinePlayLounge? lounge { get; set; }
public override bool HandleNonPositionalInput => true;

View File

@ -0,0 +1,32 @@
// 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;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
public interface IOnlinePlayLounge
{
/// <summary>
/// Attempts to join the given room.
/// </summary>
/// <param name="room">The room to join.</param>
/// <param name="password">The password.</param>
/// <param name="onSuccess">A delegate to invoke if the user joined the room.</param>
/// <param name="onFailure">A delegate to invoke if the user is not able join the room.</param>
void Join(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onFailure = null);
/// <summary>
/// Copies the given room and opens it as a fresh (not-yet-created) one.
/// </summary>
/// <param name="room">The room to copy.</param>
void OpenCopy(Room room);
/// <summary>
/// Closes the given room.
/// </summary>
/// <param name="room">The room to close.</param>
void Close(Room room);
}
}

View File

@ -33,7 +33,8 @@ using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
[Cached]
public abstract partial class LoungeSubScreen : OnlinePlaySubScreen
[Cached(typeof(IOnlinePlayLounge))]
public abstract partial class LoungeSubScreen : OnlinePlaySubScreen, IOnlinePlayLounge
{
public override string Title => "Lounge";
@ -263,6 +264,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
music.EnsurePlayingSomething();
onReturning();
// Poll for any newly-created rooms (including potentially the user's own).
ListingPollingComponent.PollImmediately();
}
public override bool OnExiting(ScreenExitEvent e)
@ -297,14 +301,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
popoverContainer.HidePopover();
}
public virtual void Join(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onFailure = null) => Schedule(() =>
public void Join(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onFailure = null) => Schedule(() =>
{
if (joiningRoomOperation != null)
return;
joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
RoomManager?.JoinRoom(room, password, _ =>
JoinInternal(room, password, r =>
{
Open(room);
joiningRoomOperation?.Dispose();
@ -318,10 +322,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
});
});
/// <summary>
/// Copies a room and opens it as a fresh (not-yet-created) one.
/// </summary>
/// <param name="room">The room to copy.</param>
protected abstract void JoinInternal(Room room, string? password, Action<Room> onSuccess, Action<string> onFailure);
public void OpenCopy(Room room)
{
Debug.Assert(room.RoomID != null);
@ -358,6 +360,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
api.Queue(req);
}
public abstract void Close(Room room);
/// <summary>
/// Push a room as a new subscreen.
/// </summary>

View File

@ -25,12 +25,13 @@ namespace osu.Game.Screens.OnlinePlay.Match
set => selectedItem.Current = value;
}
public Drawable? ChangeSettingsButton { get; private set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
private readonly BindableWithCurrent<PlaylistItem?> selectedItem = new BindableWithCurrent<PlaylistItem?>();
private readonly bool allowEdit;
private Drawable? editButton;
public DrawableMatchRoom(Room room, bool allowEdit = true)
: base(room)
@ -45,7 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
{
if (allowEdit)
{
ButtonsContainer.Add(editButton = new PurpleRoundedButton
ButtonsContainer.Add(ChangeSettingsButton = new PurpleRoundedButton
{
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
@ -73,8 +74,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
private void updateRoomHost()
{
if (editButton != null)
editButton.Alpha = Room.Host?.Equals(api.LocalUser.Value) == true ? 1 : 0;
if (ChangeSettingsButton != null)
ChangeSettingsButton.Alpha = Room.Host?.Equals(api.LocalUser.Value) == true ? 1 : 0;
}
protected override UpdateableBeatmapBackgroundSprite CreateBackground() => base.CreateBackground().With(d =>

View File

@ -360,7 +360,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (!ensureExitConfirmed())
return true;
RoomManager?.PartRoom();
if (Room.RoomID != null)
PartRoom();
Mods.Value = Array.Empty<Mod>();
onLeaving();
@ -368,6 +370,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
return base.OnExiting(e);
}
/// <summary>
/// Parts from the current room.
/// </summary>
protected abstract void PartRoom();
private bool ensureExitConfirmed()
{
if (ExitConfirmed)

View File

@ -29,12 +29,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
public partial class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay
{
public required Bindable<PlaylistItem?> SelectedItem
{
get => selectedItem;
set => selectedItem.Current = value;
}
protected override OsuButton SubmitButton => settings.ApplyButton;
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
@ -56,7 +50,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y,
SettingsApplied = Hide,
SelectedItem = { BindTarget = SelectedItem }
};
protected partial class MatchSettings : CompositeDrawable
@ -65,7 +58,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
public readonly Bindable<PlaylistItem?> SelectedItem = new Bindable<PlaylistItem?>();
public Action? SettingsApplied;
public OsuTextBox NameField = null!;
@ -86,9 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved]
private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!;
[Resolved]
private IRoomManager manager { get; set; } = null!;
[Resolved]
private MultiplayerClient client { get; set; } = null!;
@ -279,7 +268,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{
RelativeSizeAxes = Axes.X,
Height = DrawableRoomPlaylistItem.HEIGHT,
SelectedItem = { BindTarget = SelectedItem }
},
selectBeatmapButton = new RoundedButton
{
@ -456,7 +444,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
if (!ApplyButton.Enabled.Value)
return;
hideError();
ErrorText.FadeOut(50);
Debug.Assert(applyingSettingsOperation == null);
applyingSettingsOperation = ongoingOperationTracker.BeginOperation();
@ -475,32 +463,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
.ContinueWith(t => Schedule(() =>
{
if (t.IsCompletedSuccessfully)
onSuccess(room);
onSuccess();
else
onError(t.Exception?.AsSingular().Message ?? "Error changing settings.");
onError(t.Exception, "Error changing settings");
}));
}
else
{
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 = max;
client.CreateRoom(room).ContinueWith(t => Schedule(() =>
{
if (t.IsCompletedSuccessfully)
onSuccess();
else
room.MaxParticipants = null;
manager.CreateRoom(room, onSuccess, onError);
onError(t.Exception, "Error creating room");
}));
}
}
private void hideError() => ErrorText.FadeOut(50);
private void onSuccess(Room room) => Schedule(() =>
private void onSuccess() => Schedule(() =>
{
Debug.Assert(applyingSettingsOperation != null);
@ -510,28 +490,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
applyingSettingsOperation = null;
});
private void onError(string text) => Schedule(() =>
private void onError(Exception? exception, string description)
{
if (exception is AggregateException aggregateException)
exception = aggregateException.AsSingular();
string message = exception?.GetHubExceptionMessage() ?? $"{description} ({exception?.Message})";
Schedule(() =>
{
Debug.Assert(applyingSettingsOperation != null);
// see https://github.com/ppy/osu-web/blob/2c97aaeb64fb4ed97c747d8383a35b30f57428c7/app/Models/Multiplayer/PlaylistItem.php#L48.
const string not_found_prefix = "beatmaps not found:";
if (text.StartsWith(not_found_prefix, StringComparison.Ordinal))
if (message.StartsWith(not_found_prefix, StringComparison.Ordinal))
{
ErrorText.Text = "The selected beatmap is not available online.";
SelectedItem.Value?.MarkInvalid();
room.Playlist.SingleOrDefault()?.MarkInvalid();
}
else
{
ErrorText.Text = text;
}
ErrorText.Text = message;
ErrorText.FadeIn(50);
applyingSettingsOperation.Dispose();
applyingSettingsOperation = null;
});
}
protected override void Dispose(bool isDisposing)
{

View File

@ -8,7 +8,6 @@ using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
@ -97,8 +96,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override string ScreenTitle => "Multiplayer";
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
public void Join(Room room, string? password) => Schedule(() => Lounge.Join(room, password));

View File

@ -1,12 +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.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Configuration;
@ -32,19 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private Dropdown<RoomPermissionsFilter> roomAccessTypeDropdown = null!;
private OsuCheckbox showInProgress = null!;
public override void OnResuming(ScreenTransitionEvent e)
{
base.OnResuming(e);
// Upon having left a room, we don't know whether we were the only participant, and whether the room is now closed as a result of leaving it.
// To work around this, temporarily remove the room and trigger an immediate listing poll.
if (e.Last is MultiplayerMatchSubScreen match)
{
RoomManager?.RemoveRoom(match.Room);
ListingPollingComponent.PollImmediately();
}
}
protected override IEnumerable<Drawable> CreateFilterControls()
{
foreach (var control in base.CreateFilterControls())
@ -93,6 +81,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent();
protected override void JoinInternal(Room room, string? password, Action<Room> onSuccess, Action<string> onFailure)
{
client.JoinRoom(room, password).ContinueWith(result =>
{
if (result.IsCompletedSuccessfully)
onSuccess(room);
else
{
const string message = "Failed to join multiplayer room.";
if (result.Exception != null)
Logger.Error(result.Exception, message);
onFailure.Invoke(result.Exception?.AsSingular().Message ?? message);
}
});
}
public override void Close(Room room)
=> throw new NotSupportedException("Cannot close multiplayer rooms.");
protected override void OpenNewRoom(Room room)
{
if (!client.IsConnected.Value)

View File

@ -259,10 +259,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
SelectedItem = SelectedItem
};
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room)
{
SelectedItem = SelectedItem
};
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room);
protected override APIMod[] GetGameplayMods()
{
@ -313,6 +310,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
return base.OnExiting(e);
}
protected override void PartRoom() => client.LeaveRoom();
private ModSettingChangeTracker? modSettingChangeTracker;
private ScheduledDelegate? debouncedModSettingsUpdate;

View File

@ -1,72 +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.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Logging;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public partial class MultiplayerRoomManager : RoomManager
{
[Resolved]
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, onSuccess, onError), onError);
public override void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
if (!multiplayerClient.IsConnected.Value)
{
onError?.Invoke("Not currently connected to the multiplayer server.");
return;
}
// 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.HasEnded)
{
onError?.Invoke("Cannot join an ended room.");
return;
}
base.JoinRoom(room, password, r => joinMultiplayerRoom(r, password, onSuccess, onError), onError);
}
public override void PartRoom()
{
if (JoinedRoom.Value == null)
return;
base.PartRoom();
multiplayerClient.LeaveRoom();
}
private void joinMultiplayerRoom(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
Debug.Assert(room.RoomID != null);
multiplayerClient.JoinRoom(room, password).ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
Schedule(() => onSuccess?.Invoke(room));
else if (t.IsFaulted)
{
const string message = "Failed to join multiplayer room.";
if (t.Exception != null)
Logger.Error(t.Exception, message);
PartRoom();
Schedule(() => onError?.Invoke(t.Exception?.AsSingular().Message ?? message));
}
});
}
}
}

View File

@ -36,12 +36,12 @@ namespace osu.Game.Screens.OnlinePlay
private readonly ScreenStack screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both };
private OnlinePlayScreenWaveContainer waves = null!;
[Cached(Type = typeof(IRoomManager))]
protected RoomManager RoomManager { get; private set; }
[Cached]
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
[Cached(Type = typeof(IRoomManager))]
private readonly RoomManager roomManager = new RoomManager();
[Resolved]
protected IAPIProvider API { get; private set; } = null!;
@ -51,8 +51,6 @@ namespace osu.Game.Screens.OnlinePlay
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING };
RoomManager = CreateRoomManager();
}
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
@ -67,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay
{
screenStack,
new Header(ScreenTitle, screenStack),
RoomManager,
roomManager,
ongoingOperationTracker,
}
};
@ -165,8 +163,6 @@ namespace osu.Game.Screens.OnlinePlay
subScreen.Exit();
}
RoomManager.PartRoom();
waves.Hide();
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
@ -224,8 +220,6 @@ namespace osu.Game.Screens.OnlinePlay
protected abstract string ScreenTitle { get; }
protected virtual RoomManager CreateRoomManager() => new RoomManager();
protected abstract LoungeSubScreen CreateLounge();
ScreenStack IHasSubScreenStack.SubScreenStack => screenStack;

View File

@ -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.Graphics;
using osu.Framework.Screens;
@ -15,9 +14,6 @@ namespace osu.Game.Screens.OnlinePlay
protected sealed override bool PlayExitSound => false;
[Resolved]
protected IRoomManager? RoomManager { get; private set; }
protected OnlinePlaySubScreen()
{
Anchor = Anchor.Centre;

View File

@ -1,14 +1,17 @@
// 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
@ -59,6 +62,29 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
return criteria;
}
protected override void JoinInternal(Room room, string? password, Action<Room> onSuccess, Action<string> onFailure)
{
var joinRoomRequest = new JoinRoomRequest(room, password);
joinRoomRequest.Success += r => onSuccess(r);
joinRoomRequest.Failure += exception =>
{
if (exception is not OperationCanceledException)
onFailure(exception.Message);
};
api.Queue(joinRoomRequest);
}
public override void Close(Room room)
{
Debug.Assert(room.RoomID != null);
var request = new ClosePlaylistRequest(room.RoomID.Value);
request.Success += RefreshRooms;
api.Queue(request);
}
protected override OsuButton CreateNewRoomButton() => new CreatePlaylistsRoomButton();
protected override Room CreateNewRoom()

View File

@ -75,9 +75,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private PurpleRoundedButton editPlaylistButton = null!;
[Resolved]
private IRoomManager? manager { get; set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
@ -440,7 +437,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
if (!ApplyButton.Enabled.Value)
return;
hideError();
ErrorText.FadeOut(50);
room.Name = NameField.Text;
room.Availability = AvailabilityPicker.Current.Value;
@ -449,13 +446,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
room.Duration = DurationField.Current.Value;
loadingLayer.Show();
manager?.CreateRoom(room, onSuccess, onError);
var req = new CreateRoomRequest(room);
req.Success += _ => loadingLayer.Hide();
req.Failure += e => onError(req.Response?.Error ?? e.Message);
api.Queue(req);
}
private void hideError() => ErrorText.FadeOut(50);
private void onSuccess(Room room) => loadingLayer.Hide();
private void onError(string text)
{
// see https://github.com/ppy/osu-web/blob/2c97aaeb64fb4ed97c747d8383a35b30f57428c7/app/Models/Multiplayer/PlaylistItem.php#L48.

View File

@ -344,6 +344,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
}));
}
protected override void PartRoom() => api.Queue(new PartRoomRequest(Room));
protected override Screen CreateGameplayScreen(PlaylistItem selectedItem)
{
return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem)

View File

@ -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.Game.Screens.OnlinePlay;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Tests.Visual.Spectator;
@ -17,11 +16,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
/// </summary>
TestMultiplayerClient MultiplayerClient { get; }
/// <summary>
/// The cached <see cref="IRoomManager"/>.
/// </summary>
new TestMultiplayerRoomManager RoomManager { get; }
/// <summary>
/// The cached <see cref="osu.Game.Online.Spectator.SpectatorClient"/>.
/// </summary>

View File

@ -17,7 +17,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public const int PLAYER_2_ID = 56;
public TestMultiplayerClient MultiplayerClient => OnlinePlayDependencies.MultiplayerClient;
public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager;
public TestSpectatorClient SpectatorClient => OnlinePlayDependencies.SpectatorClient;
protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies;
@ -56,7 +55,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("join room", () =>
{
SelectedRoom.Value = CreateRoom();
RoomManager.CreateRoom(SelectedRoom.Value);
MultiplayerClient.CreateRoom(SelectedRoom.Value).ConfigureAwait(false);
});
AddUntilStep("wait for room join", () => RoomJoined);

View File

@ -3,7 +3,6 @@
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Tests.Visual.Spectator;
@ -16,19 +15,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public TestMultiplayerClient MultiplayerClient { get; }
public TestSpectatorClient SpectatorClient { get; }
public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager;
public MultiplayerTestSceneDependencies()
{
MultiplayerClient = new TestMultiplayerClient(RoomManager);
MultiplayerClient = new TestMultiplayerClient(RequestsHandler);
SpectatorClient = CreateSpectatorClient();
CacheAs<MultiplayerClient>(MultiplayerClient);
CacheAs<SpectatorClient>(SpectatorClient);
}
protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager(RequestsHandler);
protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient();
}
}

View File

@ -10,6 +10,7 @@ using MessagePack;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@ -17,6 +18,7 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -65,15 +67,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved]
private IAPIProvider api { get; set; } = null!;
private readonly TestMultiplayerRoomManager roomManager;
private MultiplayerPlaylistItem? currentItem => ServerRoom?.Playlist[currentIndex];
private int currentIndex;
private long lastPlaylistItemId;
public TestMultiplayerClient(TestMultiplayerRoomManager roomManager)
private readonly TestRoomRequestsHandler apiRequestHandler;
public TestMultiplayerClient(TestRoomRequestsHandler? apiRequestHandler = null)
{
this.roomManager = roomManager;
this.apiRequestHandler = apiRequestHandler ?? new TestRoomRequestsHandler();
}
public void Connect() => isConnected.Value = true;
@ -206,7 +208,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(clone(userId), clone(user.BeatmapAvailability));
}
protected override async Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
protected override async Task<MultiplayerRoom> JoinRoomInternal(long roomId, string? password = null)
{
if (RoomJoined || ServerAPIRoom != null)
throw new InvalidOperationException("Already joined a room");
@ -214,7 +216,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
roomId = clone(roomId);
password = clone(password);
ServerAPIRoom = roomManager.ServerSideRooms.Single(r => r.RoomID == roomId);
ServerAPIRoom = ServerSideRooms.Single(r => r.RoomID == roomId);
if (password != ServerAPIRoom.Password)
throw new InvalidOperationException("Invalid password.");
@ -500,6 +502,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, clone(playlistItemId));
protected override Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room)
{
Room apiRoom = new Room(room)
{
Type = room.Settings.MatchType == MatchType.Playlists
? MatchType.HeadToHead
: room.Settings.MatchType
};
AddServerSideRoom(apiRoom, api.LocalUser.Value);
return JoinRoomInternal(apiRoom.RoomID!.Value, room.Settings.Password);
}
private async Task changeMatchType(MatchType type)
{
Debug.Assert(ServerRoom != null);
@ -692,5 +707,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
isConnected.Value = false;
return Task.CompletedTask;
}
#region API Room Handling
public IReadOnlyList<Room> ServerSideRooms
=> apiRequestHandler.ServerSideRooms;
public void AddServerSideRoom(Room room, APIUser host)
=> apiRequestHandler.AddServerSideRoom(room, host);
public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager beatmapManager)
=> apiRequestHandler.HandleRequest(request, localUser, beatmapManager);
#endregion
}
}

View File

@ -1,42 +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.
using System;
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
/// <summary>
/// A <see cref="RoomManager"/> for use in multiplayer test scenes.
/// Should generally not be used by itself outside of a <see cref="MultiplayerTestScene"/>.
/// </summary>
public partial class TestMultiplayerRoomManager : MultiplayerRoomManager
{
private readonly TestRoomRequestsHandler requestsHandler;
public TestMultiplayerRoomManager(TestRoomRequestsHandler requestsHandler)
{
this.requestsHandler = requestsHandler;
}
public IReadOnlyList<Room> ServerSideRooms => requestsHandler.ServerSideRooms;
public override void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
=> base.CreateRoom(room, r => onSuccess?.Invoke(r), onError);
public override void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
=> base.JoinRoom(room, password, r => onSuccess?.Invoke(r), onError);
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
/// <param name="host">The host.</param>
public void AddServerSideRoom(Room room, APIUser host) => requestsHandler.AddServerSideRoom(room, host);
}
}

View File

@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay
RequestsHandler = new TestRoomRequestsHandler();
OngoingOperationTracker = new OngoingOperationTracker();
AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
RoomManager = CreateRoomManager();
RoomManager = new TestRoomManager();
UserLookupCache = new TestUserLookupCache();
BeatmapLookupCache = new BeatmapLookupCache();
@ -80,7 +80,5 @@ namespace osu.Game.Tests.Visual.OnlinePlay
if (instance is Drawable drawable)
drawableComponents.Add(drawable);
}
protected virtual IRoomManager CreateRoomManager() => new TestRoomManager();
}
}

View File

@ -2,7 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@ -15,18 +17,19 @@ namespace osu.Game.Tests.Visual.OnlinePlay
/// </summary>
public partial class TestRoomManager : RoomManager
{
public Action<Room, string?>? JoinRoomRequested;
private int currentRoomId;
public override void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
JoinRoomRequested?.Invoke(room, password);
base.JoinRoom(room, password, onSuccess, onError);
}
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
public void AddRooms(int count, RulesetInfo? ruleset = null, bool withPassword = false, bool withSpotlightRooms = false)
{
// Can't reference Osu ruleset project here.
ruleset ??= rulesets.GetRuleset(0)!;
for (int i = 0; i < count; i++)
{
AddRoom(new Room
@ -36,12 +39,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay
Duration = TimeSpan.FromSeconds(10),
Category = withSpotlightRooms && i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal,
Password = withPassword ? @"password" : null,
PlaylistItemStats = ruleset == null
? null
: new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] },
Playlist = ruleset == null
? Array.Empty<PlaylistItem>()
: [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }]
PlaylistItemStats = new Room.RoomPlaylistItemStats { RulesetIDs = [ruleset.OnlineID] },
Playlist = [new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() }) { RulesetID = ruleset.OnlineID }]
});
}
}
@ -49,7 +48,11 @@ namespace osu.Game.Tests.Visual.OnlinePlay
public void AddRoom(Room room)
{
room.RoomID = -currentRoomId;
CreateRoom(room);
var req = new CreateRoomRequest(room);
req.Success += AddOrUpdateRoom;
api.Queue(req);
currentRoomId++;
}
}