From 894c31753b1c1737ef2dd7ecdf7440d9ef739cee Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 15:31:06 +0900 Subject: [PATCH 01/11] Add initial support for aborting multiplayer games --- .../Multiplayer/IMultiplayerRoomServer.cs | 5 +++ .../Online/Multiplayer/MultiplayerClient.cs | 2 ++ .../Multiplayer/OnlineMultiplayerClient.cs | 10 ++++++ .../Multiplayer/Match/ConfirmAbortDialog.cs | 33 +++++++++++++++++++ .../Multiplayer/Match/MatchStartControl.cs | 27 +++++++++++++-- .../Match/MultiplayerReadyButton.cs | 31 ++++++++++++----- .../Multiplayer/TestMultiplayerClient.cs | 6 ++++ 7 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index b7a608581c..15a8b42457 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -82,6 +82,11 @@ namespace osu.Game.Online.Multiplayer /// Task AbortGameplay(); + /// + /// Real. + /// + Task AbortGameplayReal(); + /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 79f46c2095..140380d679 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -374,6 +374,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AbortGameplay(); + public abstract Task AbortGameplayReal(); + public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item); diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index e400132693..47f4205dfd 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -226,6 +226,16 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } + public override Task AbortGameplayReal() + { + if (!IsConnected.Value) + return Task.CompletedTask; + + Debug.Assert(connection != null); + + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplayReal)); + } + public override Task AddPlaylistItem(MultiplayerPlaylistItem item) { if (!IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs new file mode 100644 index 0000000000..8aca96a918 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public partial class ConfirmAbortDialog : PopupDialog + { + public ConfirmAbortDialog(Action onConfirm, Action onCancel) + { + HeaderText = "Are you sure you want to go abort the match?"; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = @"Yes", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"No I didn't mean to", + Action = onCancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 44e18dd2bb..d44878f7c3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Threading; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -28,6 +29,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match [CanBeNull] private IDisposable clickOperation; + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + private Sample sampleReady; private Sample sampleReadyAll; private Sample sampleUnready; @@ -109,8 +113,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) - startMatch(); + if (Client.IsHost) + { + if (Room.State == MultiplayerRoomState.Open) + { + if (isReady() && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) + startMatch(); + else + toggleReady(); + } + else + { + if (dialogOverlay == null) + abortMatch(); + else + dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation)); + } + } else toggleReady(); @@ -128,6 +147,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // gameplay was not started due to an exception; unblock button. endOperation(); }); + + void abortMatch() => Client.AbortGameplayReal().FireAndForget(endOperation, _ => endOperation()); } private void startCountdown(TimeSpan duration) @@ -198,6 +219,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (localUser?.State == MultiplayerUserState.Spectating) readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); + readyButton.Enabled.Value = true; + if (newCountReady == countReady) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 1be573bdb8..8a9d027469 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -158,7 +158,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Text = room.Host?.Equals(localUser) == true ? $"Start match {countText}" : $"Waiting for host... {countText}"; + break; + case MultiplayerUserState.Idle: + if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) + { + Text = "Ready"; + break; + } + + Text = "Abort!"; break; } } @@ -204,17 +213,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match setYellow(); break; + + case MultiplayerUserState.Idle: + if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) + { + setGreen(); + break; + } + + setRed(); + break; } - void setYellow() - { - BackgroundColour = colours.YellowDark; - } + void setYellow() => BackgroundColour = colours.YellowDark; - void setGreen() - { - BackgroundColour = colours.Green; - } + void setGreen() => BackgroundColour = colours.Green; + + void setRed() => BackgroundColour = colours.Red; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 577104db45..a73c3a72a2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,6 +396,12 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + public override Task AbortGameplayReal() + { + // Todo: + return Task.CompletedTask; + } + public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) { Debug.Assert(ServerRoom != null); From a94180c8c6cde22814da67de2ff0e78be9285609 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 18:26:59 +0900 Subject: [PATCH 02/11] Rename LoadAborted -> GameplayAborted, AbortGameplayReal -> AbortMatch --- .../Online/Multiplayer/GameplayAbortReason.cs | 11 +++++++++++ .../Online/Multiplayer/IMultiplayerClient.cs | 11 ++++++----- .../Online/Multiplayer/IMultiplayerRoomServer.cs | 10 +++++----- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 10 +++++----- .../Multiplayer/OnlineMultiplayerClient.cs | 6 +++--- .../Multiplayer/Match/MatchStartControl.cs | 2 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 16 +++++++++++++--- .../Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 8 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/GameplayAbortReason.cs diff --git a/osu.Game/Online/Multiplayer/GameplayAbortReason.cs b/osu.Game/Online/Multiplayer/GameplayAbortReason.cs new file mode 100644 index 0000000000..15151ea68b --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameplayAbortReason.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Online.Multiplayer +{ + public enum GameplayAbortReason + { + LoadTookTooLong, + HostAbortedTheMatch + } +} diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs index a5fa49a94b..0452d8b79c 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs @@ -107,17 +107,18 @@ namespace osu.Game.Online.Multiplayer /// Task LoadRequested(); - /// - /// Signals that loading of gameplay is to be aborted. - /// - Task LoadAborted(); - /// /// Signals that gameplay has started. /// All users in the or states should begin gameplay as soon as possible. /// Task GameplayStarted(); + /// + /// Signals that gameplay has been aborted. + /// + /// The reason why gameplay was aborted. + Task GameplayAborted(GameplayAbortReason reason); + /// /// Signals that the match has ended, all players have finished and results are ready to be displayed. /// diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 15a8b42457..55f00b447f 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -77,16 +77,16 @@ namespace osu.Game.Online.Multiplayer /// If an attempt to start the game occurs when the game's (or users') state disallows it. Task StartMatch(); + /// + /// As the host of a room, aborts an on-going match. + /// + Task AbortMatch(); + /// /// Aborts an ongoing gameplay load. /// Task AbortGameplay(); - /// - /// Real. - /// - Task AbortGameplayReal(); - /// /// Adds an item to the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 140380d679..bbf0e3697a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -73,9 +73,9 @@ namespace osu.Game.Online.Multiplayer public virtual event Action? LoadRequested; /// - /// Invoked when the multiplayer server requests loading of play to be aborted. + /// Invoked when the multiplayer server requests gameplay to be aborted. /// - public event Action? LoadAborted; + public event Action? GameplayAborted; /// /// Invoked when the multiplayer server requests gameplay to be started. @@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer public abstract Task AbortGameplay(); - public abstract Task AbortGameplayReal(); + public abstract Task AbortMatch(); public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); @@ -684,14 +684,14 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - Task IMultiplayerClient.LoadAborted() + Task IMultiplayerClient.GameplayAborted(GameplayAbortReason reason) { Scheduler.Add(() => { if (Room == null) return; - LoadAborted?.Invoke(); + GameplayAborted?.Invoke(reason); }, false); return Task.CompletedTask; diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 47f4205dfd..40436d730e 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -58,7 +58,7 @@ namespace osu.Game.Online.Multiplayer connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged); connection.On(nameof(IMultiplayerClient.LoadRequested), ((IMultiplayerClient)this).LoadRequested); connection.On(nameof(IMultiplayerClient.GameplayStarted), ((IMultiplayerClient)this).GameplayStarted); - connection.On(nameof(IMultiplayerClient.LoadAborted), ((IMultiplayerClient)this).LoadAborted); + connection.On(nameof(IMultiplayerClient.GameplayAborted), ((IMultiplayerClient)this).GameplayAborted); connection.On(nameof(IMultiplayerClient.ResultsReady), ((IMultiplayerClient)this).ResultsReady); connection.On>(nameof(IMultiplayerClient.UserModsChanged), ((IMultiplayerClient)this).UserModsChanged); connection.On(nameof(IMultiplayerClient.UserBeatmapAvailabilityChanged), ((IMultiplayerClient)this).UserBeatmapAvailabilityChanged); @@ -226,14 +226,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } - public override Task AbortGameplayReal() + public override Task AbortMatch() { if (!IsConnected.Value) return Task.CompletedTask; Debug.Assert(connection != null); - return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplayReal)); + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortMatch)); } public override Task AddPlaylistItem(MultiplayerPlaylistItem item) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index d44878f7c3..8ca5d61ab4 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match endOperation(); }); - void abortMatch() => Client.AbortGameplayReal().FireAndForget(endOperation, _ => endOperation()); + void abortMatch() => Client.AbortMatch().FireAndForget(endOperation, _ => endOperation()); } private void startCountdown(TimeSpan duration) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index edf5ce276a..7d27725775 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.LoadComplete(); client.RoomUpdated += onRoomUpdated; - client.LoadAborted += onLoadAborted; + client.GameplayAborted += onGameplayAborted; onRoomUpdated(); } @@ -39,12 +39,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer transitionFromResults(); } - private void onLoadAborted() + private void onGameplayAborted(GameplayAbortReason reason) { // If the server aborts gameplay for this user (due to loading too slow), exit gameplay screens. if (!this.IsCurrentScreen()) { - Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important); + switch (reason) + { + case GameplayAbortReason.LoadTookTooLong: + Logger.Log("Gameplay aborted because loading the beatmap took too long.", LoggingTarget.Runtime, LogLevel.Important); + break; + + case GameplayAbortReason.HostAbortedTheMatch: + Logger.Log("The host aborted the match.", LoggingTarget.Runtime, LogLevel.Important); + break; + } + this.MakeCurrent(); } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index a73c3a72a2..3af8f9c5db 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,7 +396,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task AbortGameplayReal() + public override Task AbortMatch() { // Todo: return Task.CompletedTask; From 15c9416244a47e9d5fb713a99ddd9ce77b5403bd Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 18:47:40 +0900 Subject: [PATCH 03/11] Rename method --- .../Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 8ca5d61ab4..a21c85e316 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { RelativeSizeAxes = Axes.Both, Size = Vector2.One, - Action = onReadyClick, + Action = onReadyButtonClick, }, countdownButton = new MultiplayerCountdownButton { @@ -105,7 +105,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match endOperation(); } - private void onReadyClick() + private void onReadyButtonClick() { if (Room == null) return; From 1b0fc8ca9d8740d9c61aa20dcbace2f72357cd96 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 19:02:27 +0900 Subject: [PATCH 04/11] Refactor --- .../Multiplayer/Match/ConfirmAbortDialog.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 24 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs index 8aca96a918..06f2b2c8f6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public ConfirmAbortDialog(Action onConfirm, Action onCancel) { - HeaderText = "Are you sure you want to go abort the match?"; + HeaderText = "Are you sure you want to abort the match?"; Icon = FontAwesome.Solid.ExclamationTriangle; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 8a9d027469..368e5210de 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -155,19 +155,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - Text = room.Host?.Equals(localUser) == true + Text = multiplayerClient.IsHost ? $"Start match {countText}" : $"Waiting for host... {countText}"; break; - case MultiplayerUserState.Idle: - if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) - { - Text = "Ready"; - break; - } - - Text = "Abort!"; + // Show the abort button for the host as long as gameplay is in progress. + case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: + Text = "Abort the match"; break; } } @@ -207,20 +202,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) + if (multiplayerClient.IsHost && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) setGreen(); else setYellow(); break; - case MultiplayerUserState.Idle: - if (room.State == MultiplayerRoomState.Open || room.Host?.Equals(localUser) != true) - { - setGreen(); - break; - } - + // Show the abort button for the host as long as gameplay is in progress. + case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: setRed(); break; } From f3530a79b18817e3ca1fd005933add63ac75f82e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 1 Dec 2023 21:34:20 +0900 Subject: [PATCH 05/11] Add test --- .../Multiplayer/TestSceneMatchStartControl.cs | 25 +++++++++++++++++++ .../Multiplayer/TestMultiplayerClient.cs | 6 ++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 6d309078e6..f8719ba80c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -378,6 +378,31 @@ namespace osu.Game.Tests.Visual.Multiplayer }, users); } + [Test] + public void TestAbortMatch() + { + multiplayerClient.Setup(m => m.StartMatch()) + .Callback(() => + { + multiplayerClient.Raise(m => m.LoadRequested -= null); + multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; + + // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. + changeUserState(localUser.UserID, MultiplayerUserState.Idle); + }); + + multiplayerClient.Setup(m => m.AbortMatch()) + .Callback(() => + { + multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; + raiseRoomUpdated(); + }); + + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + } + private void verifyGameplayStartFlow() { checkLocalUserState(MultiplayerUserState.Ready); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 3af8f9c5db..4c3deac1d7 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -396,10 +396,10 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - public override Task AbortMatch() + public override async Task AbortMatch() { - // Todo: - return Task.CompletedTask; + ChangeUserState(api.LocalUser.Value.Id, MultiplayerUserState.Idle); + await ((IMultiplayerClient)this).GameplayAborted(GameplayAbortReason.HostAbortedTheMatch).ConfigureAwait(false); } public async Task AddUserPlaylistItem(int userId, MultiplayerPlaylistItem item) From c2644a5d5e208214f383714b606c5e442bc04a28 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 10:18:37 +0900 Subject: [PATCH 06/11] Correctly implement button enabled state --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 1 + .../OnlinePlay/Multiplayer/Match/MatchStartControl.cs | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index f8719ba80c..c64dea3f59 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -401,6 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); ClickButtonWhenEnabled(); ClickButtonWhenEnabled(); + AddStep("check abort request received", () => multiplayerClient.Verify(m => m.AbortMatch(), Times.Once)); } private void verifyGameplayStartFlow() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index a21c85e316..e61735fe61 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match dialogOverlay.Push(new ConfirmAbortDialog(abortMatch, endOperation)); } } - else + else if (Room.State != MultiplayerRoomState.Closed) toggleReady(); bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating; @@ -210,7 +210,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } readyButton.Enabled.Value = countdownButton.Enabled.Value = - Room.State == MultiplayerRoomState.Open + Room.State != MultiplayerRoomState.Closed && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; @@ -219,7 +219,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (localUser?.State == MultiplayerUserState.Spectating) readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); - readyButton.Enabled.Value = true; + // When the local user is not the host, the button should only be enabled when no match is in progress. + if (!Client.IsHost) + readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; if (newCountReady == countReady) return; From 9ccd33a1ecc61e684ca92b30781fb55b669a9016 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 10:20:53 +0900 Subject: [PATCH 07/11] Add comments to test --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index c64dea3f59..750968fc75 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -398,8 +398,13 @@ namespace osu.Game.Tests.Visual.Multiplayer raiseRoomUpdated(); }); + // Ready ClickButtonWhenEnabled(); + + // Start match ClickButtonWhenEnabled(); + + // Abort ClickButtonWhenEnabled(); AddStep("check abort request received", () => multiplayerClient.Verify(m => m.AbortMatch(), Times.Once)); } From 8587652869ea92665de36af0f1bc4a9079ab3c8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 4 Dec 2023 11:00:11 +0900 Subject: [PATCH 08/11] Fix countdown button being enabled --- .../Multiplayer/TestSceneMatchStartControl.cs | 32 +++++++++++-------- .../Multiplayer/Match/MatchStartControl.cs | 3 ++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 750968fc75..2d61c26a6b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -381,28 +381,32 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestAbortMatch() { - multiplayerClient.Setup(m => m.StartMatch()) - .Callback(() => - { - multiplayerClient.Raise(m => m.LoadRequested -= null); - multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; + AddStep("setup client", () => + { + multiplayerClient.Setup(m => m.StartMatch()) + .Callback(() => + { + multiplayerClient.Raise(m => m.LoadRequested -= null); + multiplayerClient.Object.Room!.State = MultiplayerRoomState.WaitingForLoad; - // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. - changeUserState(localUser.UserID, MultiplayerUserState.Idle); - }); + // The local user state doesn't really matter, so let's do the same as the base implementation for these tests. + changeUserState(localUser.UserID, MultiplayerUserState.Idle); + }); - multiplayerClient.Setup(m => m.AbortMatch()) - .Callback(() => - { - multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; - raiseRoomUpdated(); - }); + multiplayerClient.Setup(m => m.AbortMatch()) + .Callback(() => + { + multiplayerClient.Object.Room!.State = MultiplayerRoomState.Open; + raiseRoomUpdated(); + }); + }); // Ready ClickButtonWhenEnabled(); // Start match ClickButtonWhenEnabled(); + AddUntilStep("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); // Abort ClickButtonWhenEnabled(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index e61735fe61..99934acaae 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -223,6 +223,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (!Client.IsHost) readyButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + // At all times, the countdown button should only be enabled when no match is in progress. + countdownButton.Enabled.Value &= Room.State == MultiplayerRoomState.Open; + if (newCountReady == countReady) return; From f317e06da14040d2fb5fbbc7375bbf12c729c1e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 16:54:44 +0900 Subject: [PATCH 09/11] Use `DangerousActionDialog` --- .../Overlays/Dialog/DangerousActionDialog.cs | 8 ++++++- .../Multiplayer/Match/ConfirmAbortDialog.cs | 22 ++----------------- .../Multiplayer/Match/MatchStartControl.cs | 12 ++++++++++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index c86570386f..42a3ff827c 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -23,6 +23,11 @@ namespace osu.Game.Overlays.Dialog /// protected Action? DangerousAction { get; set; } + /// + /// The action to perform if cancelled. + /// + protected Action? CancelAction { get; set; } + protected DangerousActionDialog() { HeaderText = DeleteConfirmationDialogStrings.HeaderText; @@ -38,7 +43,8 @@ namespace osu.Game.Overlays.Dialog }, new PopupDialogCancelButton { - Text = DeleteConfirmationDialogStrings.Cancel + Text = DeleteConfirmationDialogStrings.Cancel, + Action = () => CancelAction?.Invoke() } }; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs index 06f2b2c8f6..0793981f41 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs @@ -1,33 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public partial class ConfirmAbortDialog : PopupDialog + public partial class ConfirmAbortDialog : DangerousActionDialog { - public ConfirmAbortDialog(Action onConfirm, Action onCancel) + public ConfirmAbortDialog() { HeaderText = "Are you sure you want to abort the match?"; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogDangerousButton - { - Text = @"Yes", - Action = onConfirm - }, - new PopupDialogCancelButton - { - Text = @"No I didn't mean to", - Action = onCancel - }, - }; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 99934acaae..ba3508b24f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -17,6 +17,7 @@ using osu.Framework.Threading; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -247,5 +248,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match countReady = newCountReady; }); } + + public partial class ConfirmAbortDialog : DangerousActionDialog + { + public ConfirmAbortDialog(Action abortMatch, Action cancel) + { + HeaderText = "Are you sure you want to abort the match?"; + + DangerousAction = abortMatch; + CancelAction = cancel; + } + } } } From 02178d8e611dfc3c8a8335f41c68d6fab5dfbe0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Dec 2023 16:58:16 +0900 Subject: [PATCH 10/11] Remove usage of `case-when` --- .../Match/MultiplayerReadyButton.cs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 368e5210de..7ce3dde7c2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -149,10 +149,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { switch (localUser?.State) { - default: - Text = "Ready"; - break; - case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: Text = multiplayerClient.IsHost @@ -160,9 +156,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match : $"Waiting for host... {countText}"; break; - // Show the abort button for the host as long as gameplay is in progress. - case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: - Text = "Abort the match"; + default: + // Show the abort button for the host as long as gameplay is in progress. + if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open) + Text = "Abort the match"; + else + Text = "Ready"; break; } } @@ -197,7 +196,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match switch (localUser?.State) { default: - setGreen(); + // Show the abort button for the host as long as gameplay is in progress. + if (multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open) + setRed(); + else + setGreen(); break; case MultiplayerUserState.Spectating: @@ -208,11 +211,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match setYellow(); break; - - // Show the abort button for the host as long as gameplay is in progress. - case MultiplayerUserState when multiplayerClient.IsHost && room.State != MultiplayerRoomState.Open: - setRed(); - break; } void setYellow() => BackgroundColour = colours.YellowDark; From 4644c4e7a2c8676df418def1b6630e63253ebd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Dec 2023 12:43:32 +0100 Subject: [PATCH 11/11] Remove unused class --- .../Multiplayer/Match/ConfirmAbortDialog.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs deleted file mode 100644 index 0793981f41..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ConfirmAbortDialog.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Overlays.Dialog; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match -{ - public partial class ConfirmAbortDialog : DangerousActionDialog - { - public ConfirmAbortDialog() - { - HeaderText = "Are you sure you want to abort the match?"; - } - } -}