diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3563869d8b..8f6ba6375f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -871,6 +871,53 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("queue is empty", () => this.ChildrenOfType().Single().Items.Count == 0); } + [Test] + public void TestGameplayStartsWhileInSpectatorScreen() + { + createRoom(() => new Room + { + Name = { Value = "Test Room" }, + Playlist = + { + new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + } + } + }); + + AddStep("join other user and make host", () => + { + client.AddUser(new APIUser { Id = 1234 }); + client.TransferHost(1234); + }); + + AddStep("set local user spectating", () => client.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("wait for spectating state", () => client.LocalUser?.State == MultiplayerUserState.Spectating); + + runGameplay(); + + AddStep("exit gameplay for other user", () => client.ChangeUserState(1234, MultiplayerUserState.Idle)); + AddUntilStep("wait for room to be idle", () => client.Room?.State == MultiplayerRoomState.Open); + + runGameplay(); + + void runGameplay() + { + AddStep("start match by other user", () => + { + client.ChangeUserState(1234, MultiplayerUserState.Ready); + client.StartMatch(); + }); + + AddUntilStep("wait for loading", () => client.Room?.State == MultiplayerRoomState.WaitingForLoad); + AddStep("set player loaded", () => client.ChangeUserState(1234, MultiplayerUserState.Loaded)); + AddUntilStep("wait for gameplay to start", () => client.Room?.State == MultiplayerRoomState.Playing); + AddUntilStep("wait for local user to enter spectator", () => multiplayerComponents.CurrentScreen is MultiSpectatorScreen); + } + } + private void enterGameplay() { pressReadyButton(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4bd68f2034..a397493bab 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -457,6 +457,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + // The beatmap is queried asynchronously when the selected item changes. + // This is an issue with MultiSpectatorScreen which is effectively in an always "ready" state and receives LoadRequested() callbacks + // even when it is not truly ready (i.e. the beatmap hasn't been selected by the client yet). For the time being, a simple fix to this is to ignore the callback. + // Note that spectator will be entered automatically when the client is capable of doing so via beatmap availability callbacks (see: updateBeatmapAvailability()). + if (client.LocalUser?.State == MultiplayerUserState.Spectating && (SelectedItem.Value == null || Beatmap.IsDefault)) + return; + StartPlay(); readyClickOperation?.Dispose();