From b76a87e6f886401cdc34245e41b706f4a8d6595f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 18:58:09 +0900 Subject: [PATCH 01/24] Split ready button visual logic into button itself --- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneAllPlayersQueueMode.cs | 8 +- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerReadyButton.cs | 16 +- .../Match/MultiplayerReadyButton.cs | 172 ++++++++++++------ 5 files changed, 132 insertions(+), 70 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index bafc579134..df8f63f6ed 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -95,10 +95,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 0785315b26..266ac60168 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -102,10 +102,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); @@ -119,10 +119,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 057032c413..a51d4678fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for spectating user state", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index f34f7c6c91..7f7a4c9c5f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -92,10 +92,10 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddStep("make user host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0)); verifyGameplayStartFlow(); @@ -141,12 +141,12 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); AddStep("transfer host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[1].UserID ?? 0)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (!isHost) AddStep("transfer host", () => MultiplayerClient.TransferHost(2)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddRepeatStep("change user ready state", () => { @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void verifyGameplayStartFlow() { AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user waiting for load", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddStep("finish gameplay", () => diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 0c80f6ef5b..920e23eaa1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -14,16 +14,12 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.Multiplayer; -using osu.Game.Screens.OnlinePlay.Components; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerReadyButton : MultiplayerRoomComposite { - [Resolved] - private OsuColour colours { get; set; } - [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } @@ -34,14 +30,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private Sample sampleReadyAll; private Sample sampleUnready; - private readonly ButtonWithTrianglesExposed button; + private readonly ReadyButton readyButton; private int countReady; private ScheduledDelegate readySampleDelegate; private IBindable operationInProgress; public MultiplayerReadyButton() { - InternalChild = button = new ButtonWithTrianglesExposed + InternalChild = readyButton = new ReadyButton { RelativeSizeAxes = Axes.Both, Size = Vector2.One, @@ -123,47 +119,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void updateState() { - var localUser = Client.LocalUser; - - int newCountReady = Room?.Users.Count(u => u.State == MultiplayerUserState.Ready) ?? 0; - int newCountTotal = Room?.Users.Count(u => u.State != MultiplayerUserState.Spectating) ?? 0; - - switch (localUser?.State) + if (Room == null) { - default: - button.Text = "Ready"; - updateButtonColour(true); - break; - - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - string countText = $"({newCountReady} / {newCountTotal} ready)"; - - if (Room?.Host?.Equals(localUser) == true) - { - button.Text = $"Start match {countText}"; - updateButtonColour(true); - } - else - { - button.Text = $"Waiting for host... {countText}"; - updateButtonColour(false); - } - - break; + readyButton.Enabled.Value = false; + return; } - bool enableButton = - Room?.State == MultiplayerRoomState.Open + var localUser = Client.LocalUser; + + int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); + + readyButton.Enabled.Value = + Room.State == MultiplayerRoomState.Open && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) - enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0; - - button.Enabled.Value = enableButton; + readyButton.Enabled.Value &= Room.Host?.Equals(localUser) == true && newCountReady > 0; if (newCountReady == countReady) return; @@ -187,25 +162,112 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } - private void updateButtonColour(bool green) - { - if (green) - { - button.BackgroundColour = colours.Green; - button.Triangles.ColourDark = colours.Green; - button.Triangles.ColourLight = colours.GreenLight; - } - else - { - button.BackgroundColour = colours.YellowDark; - button.Triangles.ColourDark = colours.YellowDark; - button.Triangles.ColourLight = colours.Yellow; - } - } - - private class ButtonWithTrianglesExposed : ReadyButton + public class ReadyButton : Components.ReadyButton { public new Triangles Triangles => base.Triangles; + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + [CanBeNull] + private MultiplayerRoom room => multiplayerClient.Room; + + protected override void LoadComplete() + { + base.LoadComplete(); + + multiplayerClient.RoomUpdated += () => Scheduler.AddOnce(onRoomUpdated); + onRoomUpdated(); + } + + private void onRoomUpdated() + { + updateButtonText(); + updateButtonColour(); + } + + private void updateButtonText() + { + if (room == null) + { + Text = "Ready"; + return; + } + + var localUser = multiplayerClient.LocalUser; + + int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); + + string countText = $"({countReady} / {countTotal} ready)"; + + switch (localUser?.State) + { + default: + Text = "Ready"; + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + Text = room.Host?.Equals(localUser) == true + ? $"Start match {countText}" + : $"Waiting for host... {countText}"; + + break; + } + } + + private void updateButtonColour() + { + if (room == null) + { + setGreen(); + return; + } + + var localUser = multiplayerClient.LocalUser; + + switch (localUser?.State) + { + default: + setGreen(); + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + if (room?.Host?.Equals(localUser) == true) + setGreen(); + else + setYellow(); + + break; + } + + void setYellow() + { + BackgroundColour = colours.YellowDark; + Triangles.ColourDark = colours.YellowDark; + Triangles.ColourLight = colours.Yellow; + } + + void setGreen() + { + BackgroundColour = colours.Green; + Triangles.ColourDark = colours.Green; + Triangles.ColourLight = colours.GreenLight; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (multiplayerClient != null) + multiplayerClient.RoomUpdated -= onRoomUpdated; + } } } } From efce471f0baacc69239aecdea135faf99586f974 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 19:05:28 +0900 Subject: [PATCH 02/24] Add countdown button + popover --- .../TestSceneMultiplayerReadyButton.cs | 18 +- .../TestSceneMultiplayerSpectateButton.cs | 33 ++-- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 181 +++++++++--------- .../Match/MultiplayerReadyButton.cs | 132 ++++++++++++- 4 files changed, 246 insertions(+), 118 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 7f7a4c9c5f..12b3b90fc4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -55,15 +56,16 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID }; - if (button != null) - Remove(button); - - Add(button = new MultiplayerReadyButton + Child = new PopoverContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), - }); + RelativeSizeAxes = Axes.Both, + Child = button = new MultiplayerReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + } + }; }); [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 33ad0fd1de..13c9e021db 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -56,23 +57,27 @@ namespace osu.Game.Tests.Visual.Multiplayer RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, }; - Child = new FillFlowContainer + Child = new PopoverContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - spectateButton = new MultiplayerSpectateButton + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), - }, - readyButton = new MultiplayerReadyButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + }, + readyButton = new MultiplayerReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + } } } }; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e297c90491..a382f65d84 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Audio; @@ -100,122 +101,126 @@ namespace osu.Game.Screens.OnlinePlay.Match { sampleStart = audio.Samples.Get(@"SongSelect/confirm-selection"); - InternalChildren = new Drawable[] + InternalChild = new PopoverContainer { - beatmapAvailabilityTracker, - new MultiplayerRoomSounds(), - new GridContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + beatmapAvailabilityTracker, + new MultiplayerRoomSounds(), + new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 50) - }, - Content = new[] - { - // Padded main content (drawable room + main content) - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 50) + }, + Content = new[] + { + // Padded main content (drawable room + main content) + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + new Container { - Horizontal = WaveOverlayContainer.WIDTH_PADDING, - Bottom = 30 - }, - Children = new[] - { - mainContent = new GridContainer + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Horizontal = WaveOverlayContainer.WIDTH_PADDING, + Bottom = 30 + }, + Children = new[] + { + mainContent = new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 10) - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new DrawableMatchRoom(Room, allowEdit) - { - OnEdit = () => settingsOverlay.Show(), - SelectedItem = { BindTarget = SelectedItem } - } + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10) }, - null, - new Drawable[] + Content = new[] { - new Container + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new[] + new DrawableMatchRoom(Room, allowEdit) { - new Container + OnEdit = () => settingsOverlay.Show(), + SelectedItem = { BindTarget = SelectedItem } + } + }, + null, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Child = new Box + new Container { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary. + Masking = true, + CornerRadius = 10, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary. + }, }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Child = CreateMainContent(), - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = userModsSelectOverlay = new UserModSelectOverlay + new Container { - SelectedMods = { BindTarget = UserMods }, - IsValidMod = _ => false - } - }, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = CreateMainContent(), + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = userModsSelectOverlay = new UserModSelectOverlay + { + SelectedMods = { BindTarget = UserMods }, + IsValidMod = _ => false + } + }, + } } } } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + // Resolves 1px masking errors between the settings overlay and the room panel. + Padding = new MarginPadding(-1), + Child = settingsOverlay = CreateRoomSettingsOverlay(Room) } }, - new Container - { - RelativeSizeAxes = Axes.Both, - // Resolves 1px masking errors between the settings overlay and the room panel. - Padding = new MarginPadding(-1), - Child = settingsOverlay = CreateRoomSettingsOverlay(Room) - } }, }, - }, - // Footer - new Drawable[] - { - new Container + // Footer + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - new Box + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"28242d") // Temporary. - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), - Child = CreateFooter() - }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(@"28242d") // Temporary. + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(5), + Child = CreateFooter() + }, + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 920e23eaa1..4e53b40075 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -4,15 +4,24 @@ using System; using System.Diagnostics; using System.Linq; +using Humanizer; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.Multiplayer; using osuTK; @@ -30,19 +39,43 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private Sample sampleReadyAll; private Sample sampleUnready; - private readonly ReadyButton readyButton; + private readonly BindableBool enabled = new BindableBool(); + private readonly CountdownButton countdownButton; private int countReady; private ScheduledDelegate readySampleDelegate; private IBindable operationInProgress; public MultiplayerReadyButton() { - InternalChild = readyButton = new ReadyButton + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, - Size = Vector2.One, - Action = onReadyClick, - Enabled = { Value = true }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new ReadyButton + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Action = onReadyClick, + Enabled = { BindTarget = enabled }, + }, + countdownButton = new CountdownButton + { + RelativeSizeAxes = Axes.Y, + Size = new Vector2(40, 1), + Alpha = 0, + Action = startCountdown, + Enabled = { BindTarget = enabled } + } + } + } }; } @@ -111,6 +144,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } + private void startCountdown(TimeSpan duration) + { + } + private void endOperation() { clickOperation?.Dispose(); @@ -121,7 +158,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { if (Room == null) { - readyButton.Enabled.Value = false; + enabled.Value = false; return; } @@ -130,7 +167,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - readyButton.Enabled.Value = + switch (localUser?.State) + { + default: + countdownButton.Alpha = 0; + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0; + break; + } + + enabled.Value = Room.State == MultiplayerRoomState.Open && CurrentPlaylistItem.Value?.ID == Room.Settings.PlaylistItemId && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired @@ -138,7 +187,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) - readyButton.Enabled.Value &= Room.Host?.Equals(localUser) == true && newCountReady > 0; + enabled.Value &= Room.Host?.Equals(localUser) == true && newCountReady > 0; if (newCountReady == countReady) return; @@ -269,5 +318,72 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match multiplayerClient.RoomUpdated -= onRoomUpdated; } } + + public class CountdownButton : IconButton, IHasPopover + { + private static readonly TimeSpan[] available_delays = + { + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30), + TimeSpan.FromMinutes(1), + TimeSpan.FromMinutes(2) + }; + + public new Action Action; + + private readonly Drawable background; + + public CountdownButton() + { + Icon = FontAwesome.Solid.CaretDown; + IconScale = new Vector2(0.6f); + + Add(background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + + base.Action = this.ShowPopover; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Green; + } + + public Popover GetPopover() + { + var flow = new FillFlowContainer + { + Width = 200, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + }; + + foreach (var duration in available_delays) + { + flow.Add(new PopoverButton + { + RelativeSizeAxes = Axes.X, + Text = $"Start match in {duration.Humanize()}", + BackgroundColour = background.Colour, + Action = () => + { + Action(duration); + this.HidePopover(); + } + }); + } + + return new OsuPopover { Child = flow }; + } + + public class PopoverButton : OsuButton + { + } + } } } From 3b938865a1d3fd4ee4d9972dd37a36f2365ae7df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 19:14:46 +0900 Subject: [PATCH 03/24] Add room structure for countdown timers --- .../Countdown/CountdownChangedEvent.cs | 22 +++++++++++++++++ .../Countdown/MatchStartCountdownRequest.cs | 23 ++++++++++++++++++ .../Countdown/StopCountdownRequest.cs | 17 +++++++++++++ .../Online/Multiplayer/MatchServerEvent.cs | 5 ++++ .../Online/Multiplayer/MatchStartCountdown.cs | 17 +++++++++++++ .../TeamVersus/ChangeTeamRequest.cs | 1 + .../Online/Multiplayer/MatchUserRequest.cs | 6 ++++- .../Multiplayer/MultiplayerCountdown.cs | 24 +++++++++++++++++++ .../Online/Multiplayer/MultiplayerRoom.cs | 6 +++++ osu.Game/Online/SignalRWorkaroundTypes.cs | 5 ++++ 10 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs create mode 100644 osu.Game/Online/Multiplayer/MatchStartCountdown.cs create mode 100644 osu.Game/Online/Multiplayer/MultiplayerCountdown.cs diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs new file mode 100644 index 0000000000..b067f3b235 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using MessagePack; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Indicates a change to the 's countdown. + /// + [MessagePackObject] + public class CountdownChangedEvent : MatchServerEvent + { + /// + /// The new countdown. + /// + [Key(0)] + public MultiplayerCountdown? Countdown { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs new file mode 100644 index 0000000000..04e7f506c2 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs @@ -0,0 +1,23 @@ +// 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 MessagePack; + +#nullable enable + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// A request for a countdown to start the match. + /// + [MessagePackObject] + public class MatchStartCountdownRequest : MatchUserRequest + { + /// + /// How long the countdown should last. + /// + [Key(0)] + public TimeSpan Delay { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs new file mode 100644 index 0000000000..20a0e32734 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using MessagePack; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Request to stop the current countdown. + /// + [MessagePackObject] + public class StopCountdownRequest : MatchUserRequest + { + } +} diff --git a/osu.Game/Online/Multiplayer/MatchServerEvent.cs b/osu.Game/Online/Multiplayer/MatchServerEvent.cs index 891fb2cc3b..4ce55e424d 100644 --- a/osu.Game/Online/Multiplayer/MatchServerEvent.cs +++ b/osu.Game/Online/Multiplayer/MatchServerEvent.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using MessagePack; +using osu.Game.Online.Multiplayer.Countdown; namespace osu.Game.Online.Multiplayer { @@ -11,6 +14,8 @@ namespace osu.Game.Online.Multiplayer /// [Serializable] [MessagePackObject] + // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. + [Union(0, typeof(CountdownChangedEvent))] public abstract class MatchServerEvent { } diff --git a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs new file mode 100644 index 0000000000..6c1cdd97d3 --- /dev/null +++ b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using MessagePack; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A which will start the match after ending. + /// + [MessagePackObject] + public class MatchStartCountdown : MultiplayerCountdown + { + } +} diff --git a/osu.Game/Online/Multiplayer/MatchTypes/TeamVersus/ChangeTeamRequest.cs b/osu.Game/Online/Multiplayer/MatchTypes/TeamVersus/ChangeTeamRequest.cs index 9c3b07049c..a26a2b3fc2 100644 --- a/osu.Game/Online/Multiplayer/MatchTypes/TeamVersus/ChangeTeamRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchTypes/TeamVersus/ChangeTeamRequest.cs @@ -7,6 +7,7 @@ using MessagePack; namespace osu.Game.Online.Multiplayer.MatchTypes.TeamVersus { + [MessagePackObject] public class ChangeTeamRequest : MatchUserRequest { [Key(0)] diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs index 8c6809e7f3..fa7bdd8afe 100644 --- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs @@ -3,6 +3,7 @@ using System; using MessagePack; +using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; namespace osu.Game.Online.Multiplayer @@ -12,7 +13,10 @@ namespace osu.Game.Online.Multiplayer /// [Serializable] [MessagePackObject] - [Union(0, typeof(ChangeTeamRequest))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. + // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. + [Union(0, typeof(ChangeTeamRequest))] + [Union(1, typeof(MatchStartCountdownRequest))] + [Union(2, typeof(StopCountdownRequest))] public abstract class MatchUserRequest { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs new file mode 100644 index 0000000000..63bb47b295 --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using MessagePack; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// Describes the current countdown in a . + /// + [MessagePackObject] + [Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. + public abstract class MultiplayerCountdown + { + /// + /// The time at which the countdown will end. + /// + [Key(0)] + public DateTimeOffset EndTime { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index a60e70dab3..e215498ff9 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -54,6 +54,12 @@ namespace osu.Game.Online.Multiplayer [Key(6)] public IList Playlist { get; set; } = new List(); + /// + /// The currently-running countdown. + /// + [Key(7)] + public MultiplayerCountdown? Countdown { get; set; } + [JsonConstructor] [SerializationConstructor] public MultiplayerRoom(long roomId) diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index f69d23d81c..3e2b697337 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; namespace osu.Game.Online @@ -18,8 +19,12 @@ namespace osu.Game.Online internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[] { (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), + (typeof(MatchStartCountdownRequest), typeof(MatchUserRequest)), + (typeof(StopCountdownRequest), typeof(MatchUserRequest)), + (typeof(CountdownChangedEvent), typeof(MatchServerEvent)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusUserState), typeof(MatchUserState)), + (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)) }; } } From 72843a679783d6c762117398f36c6dfc26a71d2a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 17 Mar 2022 19:26:42 +0900 Subject: [PATCH 04/24] Add support for starting/stopping countdowns --- .../TestSceneMultiplayerReadyButton.cs | 136 ++++++++++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 20 ++- .../OnlinePlay/Components/ReadyButton.cs | 12 +- .../Match/MultiplayerReadyButton.cs | 136 ++++++++++++++---- .../Multiplayer/TestMultiplayerClient.cs | 89 +++++++++++- 5 files changed, 354 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 12b3b90fc4..b4cbc40403 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -1,6 +1,7 @@ // 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 System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -16,11 +17,13 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -68,6 +71,139 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); + [Test] + public void TestStartWithCountdown() + { + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); + AddStep("click the first countdown button", () => + { + var popoverButton = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(popoverButton); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); + AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad); + } + + [Test] + public void TestCancelCountdown() + { + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); + AddStep("click the first countdown button", () => + { + var popoverButton = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(popoverButton); + InputManager.Click(MouseButton.Left); + }); + + ClickButtonWhenEnabled(); + + AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); + } + + [Test] + public void TestReadyAndUnReadyDuringCountdown() + { + AddStep("add second user as host", () => + { + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.TransferHost(2); + }); + + AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(2) })); + + ClickButtonWhenEnabled(); + AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); + + ClickButtonWhenEnabled(); + AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); + } + + [Test] + public void TestCountdownButtonEnablementAndVisibilityWhileSpectating() + { + AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); + + AddAssert("countdown button is visible", () => this.ChildrenOfType().Single().IsPresent); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + + AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + + AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); + AddAssert("countdown button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + } + + [Test] + public void TestSpectatingDuringCountdownWithNoReadyUsersCancelsCountdown() + { + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); + AddStep("click the first countdown button", () => + { + var popoverButton = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(popoverButton); + InputManager.Click(MouseButton.Left); + }); + + AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); + + AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddUntilStep("match not started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.Open); + } + + [Test] + public void TestReadyButtonEnabledWhileSpectatingDuringCountdown() + { + AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); + AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); + + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); + AddStep("click the first countdown button", () => + { + var popoverButton = this.ChildrenOfType().First(); + InputManager.MoveMouseTo(popoverButton); + InputManager.Click(MouseButton.Left); + }); + + AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); + AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); + + AddAssert("ready button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + } + + [Test] + public void TestBecomeHostDuringCountdownAndReady() + { + AddStep("add second user as host", () => + { + MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); + MultiplayerClient.TransferHost(2); + }); + + AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(1) })); + AddUntilStep("countdown started", () => MultiplayerClient.Room?.Countdown != null); + + AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); + AddUntilStep("local user is host", () => MultiplayerClient.Room?.Host?.Equals(MultiplayerClient.LocalUser) == true); + + ClickButtonWhenEnabled(); + AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); + AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null); + } + [Test] public void TestDeletedBeatmapDisableReady() { diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index a56cc7f8d6..2d5496c5c1 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -16,6 +16,7 @@ using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; @@ -534,7 +535,24 @@ namespace osu.Game.Online.Multiplayer public Task MatchEvent(MatchServerEvent e) { - // not used by any match types just yet. + if (Room == null) + return Task.CompletedTask; + + Scheduler.Add(() => + { + if (Room == null) + return; + + switch (e) + { + case CountdownChangedEvent countdownChangedEvent: + Room.Countdown = countdownChangedEvent.Countdown; + break; + } + + RoomUpdated?.Invoke(); + }, false); + return Task.CompletedTask; } diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 9822ceaaf6..79cf5c7236 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -14,20 +14,18 @@ namespace osu.Game.Screens.OnlinePlay.Components public abstract class ReadyButton : TriangleButton, IHasTooltip { public new readonly BindableBool Enabled = new BindableBool(); - - private IBindable availability; + protected readonly IBindable Availability = new Bindable(); [BackgroundDependencyLoader] private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker) { - availability = beatmapTracker.Availability.GetBoundCopy(); - - availability.BindValueChanged(_ => updateState()); + Availability.BindTo(beatmapTracker.Availability); + Availability.BindValueChanged(_ => updateState()); Enabled.BindValueChanged(_ => updateState(), true); } private void updateState() => - base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; + base.Enabled.Value = Availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; public virtual LocalisableString TooltipText { @@ -36,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Components if (Enabled.Value) return string.Empty; - if (availability.Value.State != DownloadState.LocallyAvailable) + if (Availability.Value.State != DownloadState.LocallyAvailable) return "Beatmap not downloaded"; return string.Empty; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 4e53b40075..f9f070f17a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -17,12 +17,14 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Countdown; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match @@ -124,6 +126,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match return; } + // Local user is the room host and is in a ready state. + // The only action they can take is to stop a countdown if one's currently running. + if (Room.Countdown != null) + { + stopCountdown(); + return; + } + // And if a countdown isn't running, start the match. startMatch(); @@ -131,6 +141,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation()); + void stopCountdown() => Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation()); + void startMatch() => Client.StartMatch().ContinueWith(t => { // accessing Exception here silences any potential errors from the antecedent task @@ -146,6 +158,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void startCountdown(TimeSpan duration) { + Debug.Assert(clickOperation == null); + clickOperation = ongoingOperationTracker.BeginOperation(); + + Client.SendMatchRequest(new MatchStartCountdownRequest { Delay = duration }).ContinueWith(_ => endOperation()); } private void endOperation() @@ -167,16 +183,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - switch (localUser?.State) + if (Room.Countdown != null) + countdownButton.Alpha = 0; + else { - default: - countdownButton.Alpha = 0; - break; + switch (localUser?.State) + { + default: + countdownButton.Alpha = 0; + break; - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0; - break; + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0; + break; + } } enabled.Value = @@ -232,6 +253,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match onRoomUpdated(); } + protected override void Update() + { + base.Update(); + + if (room?.Countdown != null) + { + // Update the countdown timer. + onRoomUpdated(); + } + } + private void onRoomUpdated() { updateButtonText(); @@ -251,21 +283,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready); int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); + string countdownText = room.Countdown == null ? string.Empty : $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; string countText = $"({countReady} / {countTotal} ready)"; - switch (localUser?.State) + if (room.Countdown != null) { - default: - Text = "Ready"; - break; + switch (localUser?.State) + { + default: + Text = $"Ready ({countdownText.ToLowerInvariant()})"; + break; - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - Text = room.Host?.Equals(localUser) == true - ? $"Start match {countText}" - : $"Waiting for host... {countText}"; + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + Text = $"{countdownText} {countText}"; + break; + } + } + else + { + switch (localUser?.State) + { + default: + Text = "Ready"; + break; - break; + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + Text = room.Host?.Equals(localUser) == true + ? $"Start match {countText}" + : $"Waiting for host... {countText}"; + + break; + } } } @@ -279,20 +329,37 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match var localUser = multiplayerClient.LocalUser; - switch (localUser?.State) + if (room.Countdown != null) { - default: - setGreen(); - break; - - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true) + switch (localUser?.State) + { + default: setGreen(); - else - setYellow(); + break; - break; + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + setYellow(); + break; + } + } + else + { + switch (localUser?.State) + { + default: + setGreen(); + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + if (room?.Host?.Equals(localUser) == true) + setGreen(); + else + setYellow(); + + break; + } } void setYellow() @@ -317,6 +384,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match if (multiplayerClient != null) multiplayerClient.RoomUpdated -= onRoomUpdated; } + + public override LocalisableString TooltipText + { + get + { + if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready) + return "Cancel countdown"; + + return base.TooltipText; + } + } } public class CountdownButton : IconButton, IHasPopover diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6dc5159b6f..a1ae1aa171 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -7,12 +7,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Mods; @@ -114,12 +116,24 @@ namespace osu.Game.Tests.Visual.Multiplayer public void ChangeUserState(int userId, MultiplayerUserState newState) { Debug.Assert(Room != null); + ((IMultiplayerClient)this).UserStateChanged(userId, newState); Schedule(() => { switch (Room.State) { + case MultiplayerRoomState.Open: + // If there are no remaining ready users or the host is not ready, stop any existing countdown. + // Todo: When we have an "automatic start" mode, this should also start a new countdown if any users _are_ ready. + // Todo: This doesn't yet support non-match-start countdowns. + bool shouldStopCountdown = Room.Users.All(u => u.State != MultiplayerUserState.Ready); + shouldStopCountdown |= Room.Host?.State != MultiplayerUserState.Ready && Room.Host?.State != MultiplayerUserState.Spectating; + + if (shouldStopCountdown) + countdownStopSource?.Cancel(); + break; + case MultiplayerRoomState.WaitingForLoad: if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { @@ -282,6 +296,12 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } + private CancellationTokenSource? countdownFinishSource; + private CancellationTokenSource? countdownStopSource; + private Task countdownTask = Task.CompletedTask; + + public void FinishCountDown() => countdownFinishSource?.Cancel(); + public override async Task SendMatchRequest(MatchUserRequest request) { Debug.Assert(Room != null); @@ -289,6 +309,71 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (request) { + case MatchStartCountdownRequest matchCountdownRequest: + countdownStopSource?.Cancel(); + + var stopSource = countdownStopSource = new CancellationTokenSource(); + var finishSource = countdownFinishSource = new CancellationTokenSource(); + var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, finishSource.Token); + var countdown = new MatchStartCountdown { EndTime = DateTimeOffset.Now + matchCountdownRequest.Delay }; + + Task lastCountdownTask = countdownTask; + countdownTask = start(); + + async Task start() + { + try + { + await lastCountdownTask; + } + catch (OperationCanceledException) + { + } + + Schedule(() => + { + if (stopSource.IsCancellationRequested) + return; + + Room.Countdown = countdown; + MatchEvent(new CountdownChangedEvent { Countdown = countdown }); + }); + + try + { + await Task.Delay(matchCountdownRequest.Delay, cancellationSource.Token).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } + + Schedule(() => + { + if (Room.Countdown != countdown) + return; + + Room.Countdown = null; + MatchEvent(new CountdownChangedEvent { Countdown = null }); + + using (cancellationSource) + { + if (stopSource.Token.IsCancellationRequested) + return; + } + + StartMatch().WaitSafely(); + }); + } + + break; + + case StopCountdownRequest _: + countdownStopSource?.Cancel(); + + Room.Countdown = null; + await MatchEvent(new CountdownChangedEvent { Countdown = Room.Countdown }); + break; + case ChangeTeamRequest changeTeam: TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!; @@ -307,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } } - public override Task StartMatch() + public override async Task StartMatch() { Debug.Assert(Room != null); @@ -315,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); - return ((IMultiplayerClient)this).LoadRequested(); + await ((IMultiplayerClient)this).LoadRequested(); } public override Task AbortGameplay() From 04f4e81852b846caee2b70e9cee1b47ac64dd7bc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 18 Mar 2022 21:05:19 +0900 Subject: [PATCH 05/24] Rename start countdown request --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 4 ++-- ...StartCountdownRequest.cs => StartMatchCountdownRequest.cs} | 2 +- osu.Game/Online/Multiplayer/MatchUserRequest.cs | 2 +- osu.Game/Online/SignalRWorkaroundTypes.cs | 2 +- .../OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Online/Multiplayer/Countdown/{MatchStartCountdownRequest.cs => StartMatchCountdownRequest.cs} (89%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index b4cbc40403..fc47861576 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(2) })); + AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(2) })); ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(1) })); + AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(1) })); AddUntilStep("countdown started", () => MultiplayerClient.Room?.Countdown != null); AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); diff --git a/osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs similarity index 89% rename from osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs rename to osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs index 04e7f506c2..9e6967af9d 100644 --- a/osu.Game/Online/Multiplayer/Countdown/MatchStartCountdownRequest.cs +++ b/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Online.Multiplayer.Countdown /// A request for a countdown to start the match. /// [MessagePackObject] - public class MatchStartCountdownRequest : MatchUserRequest + public class StartMatchCountdownRequest : MatchUserRequest { /// /// How long the countdown should last. diff --git a/osu.Game/Online/Multiplayer/MatchUserRequest.cs b/osu.Game/Online/Multiplayer/MatchUserRequest.cs index fa7bdd8afe..888b55e428 100644 --- a/osu.Game/Online/Multiplayer/MatchUserRequest.cs +++ b/osu.Game/Online/Multiplayer/MatchUserRequest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. [Union(0, typeof(ChangeTeamRequest))] - [Union(1, typeof(MatchStartCountdownRequest))] + [Union(1, typeof(StartMatchCountdownRequest))] [Union(2, typeof(StopCountdownRequest))] public abstract class MatchUserRequest { diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index 3e2b697337..156f916cef 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online internal static readonly IReadOnlyList<(Type derivedType, Type baseType)> BASE_TYPE_MAPPING = new[] { (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), - (typeof(MatchStartCountdownRequest), typeof(MatchUserRequest)), + (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)), (typeof(CountdownChangedEvent), typeof(MatchServerEvent)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index f9f070f17a..381c5ea712 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - Client.SendMatchRequest(new MatchStartCountdownRequest { Delay = duration }).ContinueWith(_ => endOperation()); + Client.SendMatchRequest(new StartMatchCountdownRequest { Delay = duration }).ContinueWith(_ => endOperation()); } private void endOperation() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index a1ae1aa171..4066cd0c4a 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -309,7 +309,7 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (request) { - case MatchStartCountdownRequest matchCountdownRequest: + case StartMatchCountdownRequest matchCountdownRequest: countdownStopSource?.Cancel(); var stopSource = countdownStopSource = new CancellationTokenSource(); From 4630aa15cc06a1efd1df0fe1486777c50d6999db Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 22 Mar 2022 12:54:10 +0900 Subject: [PATCH 06/24] Apply refactorings according to reviews --- .../Match/MultiplayerReadyButton.cs | 41 ++++++------------- .../Multiplayer/TestMultiplayerClient.cs | 8 +--- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 381c5ea712..4c4cc87f6d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -282,12 +282,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready); int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - - string countdownText = room.Countdown == null ? string.Empty : $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; string countText = $"({countReady} / {countTotal} ready)"; if (room.Countdown != null) { + string countdownText = $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; + switch (localUser?.State) { default: @@ -329,37 +329,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match var localUser = multiplayerClient.LocalUser; - if (room.Countdown != null) + switch (localUser?.State) { - switch (localUser?.State) - { - default: - setGreen(); - break; + default: + setGreen(); + break; - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + if (room?.Host?.Equals(localUser) == true && room.Countdown == null) + setGreen(); + else setYellow(); - break; - } - } - else - { - switch (localUser?.State) - { - default: - setGreen(); - break; - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true) - setGreen(); - else - setYellow(); - - break; - } + break; } void setYellow() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4066cd0c4a..309ca6ca58 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -322,13 +322,7 @@ namespace osu.Game.Tests.Visual.Multiplayer async Task start() { - try - { - await lastCountdownTask; - } - catch (OperationCanceledException) - { - } + await lastCountdownTask; Schedule(() => { From 2c4a6c246592cb8cb142aeb8a0704d34715cd30d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Mar 2022 16:46:42 +0900 Subject: [PATCH 07/24] Add missing async safeties to new tests --- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 12 ++++++------ .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index e38da96bd5..6300dfa381 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -424,7 +424,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("Beatmap doesn't match current item", () => Beatmap.Value.BeatmapInfo.OnlineID != multiplayerClient.Room?.Playlist.First().BeatmapID); - AddStep("start match externally", () => multiplayerClient.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); @@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != multiplayerClient.Room?.Playlist.First().RulesetID); - AddStep("start match externally", () => multiplayerClient.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); @@ -500,7 +500,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym))); - AddStep("start match externally", () => multiplayerClient.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player); @@ -535,7 +535,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for spectating user state", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("start match externally", () => multiplayerClient.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddAssert("play not started", () => multiplayerComponents.IsCurrentScreen()); } @@ -568,7 +568,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for spectating user state", () => multiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("start match externally", () => multiplayerClient.StartMatch()); + AddStep("start match externally", () => multiplayerClient.StartMatch().WaitSafely()); AddStep("restore beatmap", () => { @@ -883,7 +883,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("start match by other user", () => { multiplayerClient.ChangeUserState(1234, MultiplayerUserState.Ready); - multiplayerClient.StartMatch(); + multiplayerClient.StartMatch().WaitSafely(); }); AddUntilStep("wait for loading", () => multiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index fc47861576..0315e5dfe4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(2) })); + AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(2) }).WaitSafely()); ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(1) })); + AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(1) }).WaitSafely()); AddUntilStep("countdown started", () => MultiplayerClient.Room?.Countdown != null); AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); From 483fb84b56bda3e3d6633c63d6daf2d74cd4e5ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Mar 2022 16:50:13 +0900 Subject: [PATCH 08/24] Fix typo in `FinishCountdown` method --- .../Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs | 6 +++--- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index 0315e5dfe4..7bf03caa88 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); - AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad); } @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); } @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("finish countdown", () => MultiplayerClient.FinishCountDown()); + AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match not started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.Open); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 309ca6ca58..2b03017905 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -300,7 +300,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private CancellationTokenSource? countdownStopSource; private Task countdownTask = Task.CompletedTask; - public void FinishCountDown() => countdownFinishSource?.Cancel(); + public void FinishCountdown() => countdownFinishSource?.Cancel(); public override async Task SendMatchRequest(MatchUserRequest request) { From 9138aaf78039b39a7fcba9c2b9ada333c5346ae5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Mar 2022 10:37:53 +0900 Subject: [PATCH 09/24] Split MultiplayerReadyButton --- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneAllPlayersQueueMode.cs | 8 +- ...utton.cs => TestSceneMatchStartControl.cs} | 80 +++--- .../Multiplayer/TestSceneMultiplayer.cs | 1 + .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 6 +- .../Multiplayer/Match/CountdownButton.cs | 87 +++++++ ...yerReadyButton.cs => MatchStartControl.cs} | 230 +----------------- .../Match/MultiplayerMatchFooter.cs | 2 +- .../Multiplayer/Match/ReadyButton.cs | 162 ++++++++++++ 10 files changed, 303 insertions(+), 279 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestSceneMultiplayerReadyButton.cs => TestSceneMatchStartControl.cs} (79%) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs rename osu.Game/Screens/OnlinePlay/Multiplayer/Match/{MultiplayerReadyButton.cs => MatchStartControl.cs} (51%) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index df8f63f6ed..fd43674b3b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -95,10 +95,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 266ac60168..582dacb332 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -102,10 +102,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); @@ -119,10 +119,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs similarity index 79% rename from osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 7bf03caa88..4e54740a69 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -27,9 +27,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneMultiplayerReadyButton : MultiplayerTestScene + public class TestSceneMatchStartControl : MultiplayerTestScene { - private MultiplayerReadyButton button; + private MatchStartControl control; private BeatmapSetInfo importedSet; private readonly Bindable selectedItem = new Bindable(); @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = button = new MultiplayerReadyButton + Child = control = new MatchStartControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -74,17 +74,17 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestStartWithCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); - AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); + AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad); } @@ -92,17 +92,17 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCancelCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); @@ -119,10 +119,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(2) }).WaitSafely()); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } @@ -132,25 +132,25 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddAssert("countdown button is visible", () => this.ChildrenOfType().Single().IsPresent); - AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button is visible", () => this.ChildrenOfType().Single().IsPresent); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); - AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); - AddAssert("countdown button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } [Test] public void TestSpectatingDuringCountdownWithNoReadyUsersCancelsCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -168,12 +168,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddAssert("ready button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + AddAssert("ready button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } [Test] @@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); AddUntilStep("local user is host", () => MultiplayerClient.Room?.Host?.Equals(MultiplayerClient.LocalUser) == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null); } @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("ensure ready button enabled", () => { - readyButton = button.ChildrenOfType().Single(); + readyButton = control.ChildrenOfType().Single(); return readyButton.Enabled.Value; }); @@ -230,10 +230,10 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } @@ -249,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); @@ -264,7 +264,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddStep("make user host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0)); verifyGameplayStartFlow(); @@ -279,14 +279,14 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); AddStep("transfer host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[1].UserID ?? 0)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); - AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); + AddAssert("ready button enabled", () => control.ChildrenOfType().Single().Enabled.Value); } [TestCase(true)] @@ -304,7 +304,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (!isHost) AddStep("transfer host", () => MultiplayerClient.TransferHost(2)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddRepeatStep("change user ready state", () => { @@ -322,7 +322,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void verifyGameplayStartFlow() { AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user waiting for load", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddStep("finish gameplay", () => @@ -331,7 +331,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.ChangeUserState(MultiplayerClient.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); }); - AddUntilStep("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("ready button enabled", () => control.ChildrenOfType().Single().Enabled.Value); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 6300dfa381..d0765fc4b3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -41,6 +41,7 @@ using osu.Game.Screens.Ranking; using osu.Game.Screens.Spectate; using osu.Game.Tests.Resources; using osuTK.Input; +using ReadyButton = osu.Game.Screens.OnlinePlay.Components.ReadyButton; namespace osu.Game.Tests.Visual.Multiplayer { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index a51d4678fe..850a115f4c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for spectating user state", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 13c9e021db..07ac580276 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { private MultiplayerSpectateButton spectateButton; - private MultiplayerReadyButton readyButton; + private MatchStartControl startControl; private readonly Bindable selectedItem = new Bindable(); @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Origin = Anchor.Centre, Size = new Vector2(200, 50), }, - readyButton = new MultiplayerReadyButton + startControl = new MatchStartControl { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -146,6 +146,6 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); private void assertReadyButtonEnablement(bool shouldBeEnabled) - => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => startControl.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs new file mode 100644 index 0000000000..e598f6670c --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs @@ -0,0 +1,87 @@ +// 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 Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public class CountdownButton : IconButton, IHasPopover + { + private static readonly TimeSpan[] available_delays = + { + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30), + TimeSpan.FromMinutes(1), + TimeSpan.FromMinutes(2) + }; + + public new Action Action; + + private readonly Drawable background; + + public CountdownButton() + { + Icon = FontAwesome.Solid.CaretDown; + IconScale = new Vector2(0.6f); + + Add(background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }); + + base.Action = this.ShowPopover; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Green; + } + + public Popover GetPopover() + { + var flow = new FillFlowContainer + { + Width = 200, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + }; + + foreach (var duration in available_delays) + { + flow.Add(new PopoverButton + { + RelativeSizeAxes = Axes.X, + Text = $"Start match in {duration.Humanize()}", + BackgroundColour = background.Colour, + Action = () => + { + Action(duration); + this.HidePopover(); + } + }); + } + + return new OsuPopover { Child = flow }; + } + + public class PopoverButton : OsuButton + { + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs similarity index 51% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 4c4cc87f6d..d97ac601d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -4,32 +4,21 @@ using System; using System.Diagnostics; using System.Linq; -using Humanizer; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Localisation; using osu.Framework.Threading; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.Countdown; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public class MultiplayerReadyButton : MultiplayerRoomComposite + public class MatchStartControl : MultiplayerRoomComposite { [Resolved] private OngoingOperationTracker ongoingOperationTracker { get; set; } @@ -47,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private ScheduledDelegate readySampleDelegate; private IBindable operationInProgress; - public MultiplayerReadyButton() + public MatchStartControl() { InternalChild = new GridContainer { @@ -231,220 +220,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match countReady = newCountReady; }); } - - public class ReadyButton : Components.ReadyButton - { - public new Triangles Triangles => base.Triangles; - - [Resolved] - private MultiplayerClient multiplayerClient { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - - [CanBeNull] - private MultiplayerRoom room => multiplayerClient.Room; - - protected override void LoadComplete() - { - base.LoadComplete(); - - multiplayerClient.RoomUpdated += () => Scheduler.AddOnce(onRoomUpdated); - onRoomUpdated(); - } - - protected override void Update() - { - base.Update(); - - if (room?.Countdown != null) - { - // Update the countdown timer. - onRoomUpdated(); - } - } - - private void onRoomUpdated() - { - updateButtonText(); - updateButtonColour(); - } - - private void updateButtonText() - { - if (room == null) - { - Text = "Ready"; - return; - } - - var localUser = multiplayerClient.LocalUser; - - int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready); - int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - string countText = $"({countReady} / {countTotal} ready)"; - - if (room.Countdown != null) - { - string countdownText = $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; - - switch (localUser?.State) - { - default: - Text = $"Ready ({countdownText.ToLowerInvariant()})"; - break; - - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - Text = $"{countdownText} {countText}"; - break; - } - } - else - { - switch (localUser?.State) - { - default: - Text = "Ready"; - break; - - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - Text = room.Host?.Equals(localUser) == true - ? $"Start match {countText}" - : $"Waiting for host... {countText}"; - - break; - } - } - } - - private void updateButtonColour() - { - if (room == null) - { - setGreen(); - return; - } - - var localUser = multiplayerClient.LocalUser; - - switch (localUser?.State) - { - default: - setGreen(); - break; - - case MultiplayerUserState.Spectating: - case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true && room.Countdown == null) - setGreen(); - else - setYellow(); - - break; - } - - void setYellow() - { - BackgroundColour = colours.YellowDark; - Triangles.ColourDark = colours.YellowDark; - Triangles.ColourLight = colours.Yellow; - } - - void setGreen() - { - BackgroundColour = colours.Green; - Triangles.ColourDark = colours.Green; - Triangles.ColourLight = colours.GreenLight; - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (multiplayerClient != null) - multiplayerClient.RoomUpdated -= onRoomUpdated; - } - - public override LocalisableString TooltipText - { - get - { - if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready) - return "Cancel countdown"; - - return base.TooltipText; - } - } - } - - public class CountdownButton : IconButton, IHasPopover - { - private static readonly TimeSpan[] available_delays = - { - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30), - TimeSpan.FromMinutes(1), - TimeSpan.FromMinutes(2) - }; - - public new Action Action; - - private readonly Drawable background; - - public CountdownButton() - { - Icon = FontAwesome.Solid.CaretDown; - IconScale = new Vector2(0.6f); - - Add(background = new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue - }); - - base.Action = this.ShowPopover; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Green; - } - - public Popover GetPopover() - { - var flow = new FillFlowContainer - { - Width = 200, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2), - }; - - foreach (var duration in available_delays) - { - flow.Add(new PopoverButton - { - RelativeSizeAxes = Axes.X, - Text = $"Start match in {duration.Humanize()}", - BackgroundColour = background.Colour, - Action = () => - { - Action(duration); - this.HidePopover(); - } - }); - } - - return new OsuPopover { Child = flow }; - } - - public class PopoverButton : OsuButton - { - } - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index b4fce5903b..a07c95bca8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match RelativeSizeAxes = Axes.Both, }, null, - new MultiplayerReadyButton + new MatchStartControl { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs new file mode 100644 index 0000000000..8e3a9f9349 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs @@ -0,0 +1,162 @@ +// 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 System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public class ReadyButton : Components.ReadyButton + { + public new Triangles Triangles => base.Triangles; + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + [CanBeNull] + private MultiplayerRoom room => multiplayerClient.Room; + + protected override void LoadComplete() + { + base.LoadComplete(); + + multiplayerClient.RoomUpdated += () => Scheduler.AddOnce(onRoomUpdated); + onRoomUpdated(); + } + + protected override void Update() + { + base.Update(); + + if (room?.Countdown != null) + { + // Update the countdown timer. + onRoomUpdated(); + } + } + + private void onRoomUpdated() + { + updateButtonText(); + updateButtonColour(); + } + + private void updateButtonText() + { + if (room == null) + { + Text = "Ready"; + return; + } + + var localUser = multiplayerClient.LocalUser; + + int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); + string countText = $"({countReady} / {countTotal} ready)"; + + if (room.Countdown != null) + { + string countdownText = $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; + + switch (localUser?.State) + { + default: + Text = $"Ready ({countdownText.ToLowerInvariant()})"; + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + Text = $"{countdownText} {countText}"; + break; + } + } + else + { + switch (localUser?.State) + { + default: + Text = "Ready"; + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + Text = room.Host?.Equals(localUser) == true + ? $"Start match {countText}" + : $"Waiting for host... {countText}"; + + break; + } + } + } + + private void updateButtonColour() + { + if (room == null) + { + setGreen(); + return; + } + + var localUser = multiplayerClient.LocalUser; + + switch (localUser?.State) + { + default: + setGreen(); + break; + + case MultiplayerUserState.Spectating: + case MultiplayerUserState.Ready: + if (room?.Host?.Equals(localUser) == true && room.Countdown == null) + setGreen(); + else + setYellow(); + + break; + } + + void setYellow() + { + BackgroundColour = colours.YellowDark; + Triangles.ColourDark = colours.YellowDark; + Triangles.ColourLight = colours.Yellow; + } + + void setGreen() + { + BackgroundColour = colours.Green; + Triangles.ColourDark = colours.Green; + Triangles.ColourLight = colours.GreenLight; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (multiplayerClient != null) + multiplayerClient.RoomUpdated -= onRoomUpdated; + } + + public override LocalisableString TooltipText + { + get + { + if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready) + return "Cancel countdown"; + + return base.TooltipText; + } + } + } +} From 6b712be97d08de140bd6d3b0cb81d8d90412194b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Mar 2022 10:40:05 +0900 Subject: [PATCH 10/24] Remove PopoverButton class --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 8 ++++---- .../OnlinePlay/Multiplayer/Match/CountdownButton.cs | 6 +----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 4e54740a69..b98676e737 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs index e598f6670c..e37168bf25 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match foreach (var duration in available_delays) { - flow.Add(new PopoverButton + flow.Add(new OsuButton { RelativeSizeAxes = Axes.X, Text = $"Start match in {duration.Humanize()}", @@ -79,9 +79,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match return new OsuPopover { Child = flow }; } - - public class PopoverButton : OsuButton - { - } } } From d4ad4ac9db8c76be1cd9545944dca12e582d41f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Mar 2022 10:50:05 +0900 Subject: [PATCH 11/24] Limit countdown updates to once per second --- .../Multiplayer/Match/ReadyButton.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs index 8e3a9f9349..b37d990466 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs @@ -6,6 +6,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.Multiplayer; @@ -33,21 +34,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match onRoomUpdated(); } - protected override void Update() - { - base.Update(); - - if (room?.Countdown != null) - { - // Update the countdown timer. - onRoomUpdated(); - } - } + private ScheduledDelegate countdownUpdateDelegate; private void onRoomUpdated() { updateButtonText(); updateButtonColour(); + + if (room?.Countdown != null) + countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 1000, true); + else + { + countdownUpdateDelegate?.Cancel(); + countdownUpdateDelegate = null; + } } private void updateButtonText() From f7c004720648ae8e296f3c1d2b2f49612a29f285 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Mar 2022 15:19:43 +0900 Subject: [PATCH 12/24] Send time remaining in countdowns instead --- .../Multiplayer/MultiplayerCountdown.cs | 8 +++++-- .../Multiplayer/Match/ReadyButton.cs | 23 +++++++++++++++---- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 63bb47b295..81190e64c9 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -5,6 +5,7 @@ using System; using MessagePack; +using osu.Game.Online.Multiplayer.Countdown; namespace osu.Game.Online.Multiplayer { @@ -16,9 +17,12 @@ namespace osu.Game.Online.Multiplayer public abstract class MultiplayerCountdown { /// - /// The time at which the countdown will end. + /// The amount of time remaining in the countdown. /// + /// + /// This is only sent once from the server upon initial retrieval of the or via a . + /// [Key(0)] - public DateTimeOffset EndTime { get; set; } + public TimeSpan TimeRemaining { get; set; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs index b37d990466..007e055d8c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs @@ -34,12 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match onRoomUpdated(); } + private MultiplayerCountdown countdown; + private DateTimeOffset countdownReceivedTime; private ScheduledDelegate countdownUpdateDelegate; private void onRoomUpdated() { - updateButtonText(); - updateButtonColour(); + if (countdown == null && room?.Countdown != null) + countdownReceivedTime = DateTimeOffset.Now; + + countdown = room?.Countdown; if (room?.Countdown != null) countdownUpdateDelegate ??= Scheduler.AddDelayed(updateButtonText, 1000, true); @@ -48,6 +52,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match countdownUpdateDelegate?.Cancel(); countdownUpdateDelegate = null; } + + updateButtonText(); + updateButtonColour(); } private void updateButtonText() @@ -64,9 +71,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating); string countText = $"({countReady} / {countTotal} ready)"; - if (room.Countdown != null) + if (countdown != null) { - string countdownText = $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}"; + TimeSpan timeElapsed = DateTimeOffset.Now - countdownReceivedTime; + TimeSpan countdownRemaining; + + if (timeElapsed > countdown.TimeRemaining) + countdownRemaining = TimeSpan.Zero; + else + countdownRemaining = countdown.TimeRemaining - timeElapsed; + + string countdownText = $"Starting in {countdownRemaining:mm\\:ss}"; switch (localUser?.State) { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 2b03017905..938147e05e 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -315,7 +315,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var stopSource = countdownStopSource = new CancellationTokenSource(); var finishSource = countdownFinishSource = new CancellationTokenSource(); var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, finishSource.Token); - var countdown = new MatchStartCountdown { EndTime = DateTimeOffset.Now + matchCountdownRequest.Delay }; + var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Delay }; Task lastCountdownTask = countdownTask; countdownTask = start(); From a83a90e675f39a1646b8091c6da4c7eaeefe1d9d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 23 Mar 2022 15:21:16 +0900 Subject: [PATCH 13/24] Rename countdown Delay -> Duration --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 4 ++-- .../Multiplayer/Countdown/StartMatchCountdownRequest.cs | 2 +- .../Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index b98676e737..7ef9505d8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(2) }).WaitSafely()); + AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(2) }).WaitSafely()); ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Delay = TimeSpan.FromMinutes(1) }).WaitSafely()); + AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely()); AddUntilStep("countdown started", () => MultiplayerClient.Room?.Countdown != null); AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); diff --git a/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs index 9e6967af9d..08eab26090 100644 --- a/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs +++ b/osu.Game/Online/Multiplayer/Countdown/StartMatchCountdownRequest.cs @@ -18,6 +18,6 @@ namespace osu.Game.Online.Multiplayer.Countdown /// How long the countdown should last. /// [Key(0)] - public TimeSpan Delay { get; set; } + public TimeSpan Duration { get; set; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index d97ac601d5..9470fb1d68 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - Client.SendMatchRequest(new StartMatchCountdownRequest { Delay = duration }).ContinueWith(_ => endOperation()); + Client.SendMatchRequest(new StartMatchCountdownRequest { Duration = duration }).ContinueWith(_ => endOperation()); } private void endOperation() diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 938147e05e..1f20292437 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -315,7 +315,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var stopSource = countdownStopSource = new CancellationTokenSource(); var finishSource = countdownFinishSource = new CancellationTokenSource(); var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, finishSource.Token); - var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Delay }; + var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Duration }; Task lastCountdownTask = countdownTask; countdownTask = start(); @@ -335,7 +335,7 @@ namespace osu.Game.Tests.Visual.Multiplayer try { - await Task.Delay(matchCountdownRequest.Delay, cancellationSource.Token).ConfigureAwait(false); + await Task.Delay(matchCountdownRequest.Duration, cancellationSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { From 547418e47e2296e71fb6567441afb183af2152d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 11:15:51 +0900 Subject: [PATCH 14/24] Revert "Remove PopoverButton class" This reverts commit 6b712be97d08de140bd6d3b0cb81d8d90412194b. --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 8 ++++---- .../OnlinePlay/Multiplayer/Match/CountdownButton.cs | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index cd1eee49ec..b09fd306b7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs index e37168bf25..e598f6670c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match foreach (var duration in available_delays) { - flow.Add(new OsuButton + flow.Add(new PopoverButton { RelativeSizeAxes = Axes.X, Text = $"Start match in {duration.Humanize()}", @@ -79,5 +79,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match return new OsuPopover { Child = flow }; } + + public class PopoverButton : OsuButton + { + } } } From 90c7945bca3c413824618dc3ae430d072e2d7a85 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 14:26:31 +0900 Subject: [PATCH 15/24] Re-remove PopoverButton class with better test fix --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 9 +++++---- .../OnlinePlay/Multiplayer/Match/CountdownButton.cs | 6 +----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index b09fd306b7..02a61208d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -97,7 +98,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -150,7 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); @@ -173,7 +174,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { - var popoverButton = this.ChildrenOfType().First(); + var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); InputManager.MoveMouseTo(popoverButton); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs index e598f6670c..e37168bf25 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match foreach (var duration in available_delays) { - flow.Add(new PopoverButton + flow.Add(new OsuButton { RelativeSizeAxes = Axes.X, Text = $"Start match in {duration.Humanize()}", @@ -79,9 +79,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match return new OsuPopover { Child = flow }; } - - public class PopoverButton : OsuButton - { - } } } From 96a447f68bf753b9c9f914def65504c627c9a27c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 14:28:38 +0900 Subject: [PATCH 16/24] Rename Multiplayer prefix to button classes --- .../Visual/Multiplayer/QueueModeTestScene.cs | 4 +- .../TestSceneAllPlayersQueueMode.cs | 8 +-- .../Multiplayer/TestSceneMatchStartControl.cs | 60 +++++++++---------- .../TestSceneMultiplayerMatchSubScreen.cs | 2 +- .../TestSceneMultiplayerSpectateButton.cs | 2 +- .../Multiplayer/Match/MatchStartControl.cs | 6 +- ...utton.cs => MultiplayerCountdownButton.cs} | 4 +- ...adyButton.cs => MultiplayerReadyButton.cs} | 3 +- 8 files changed, 45 insertions(+), 44 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Match/{CountdownButton.cs => MultiplayerCountdownButton.cs} (95%) rename osu.Game/Screens/OnlinePlay/Multiplayer/Match/{ReadyButton.cs => MultiplayerReadyButton.cs} (98%) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index fd43674b3b..bafc579134 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -95,10 +95,10 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 582dacb332..0785315b26 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -102,10 +102,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); @@ -119,10 +119,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded); AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any()); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 02a61208d4..4fd6fd5d70 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -75,9 +75,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestStartWithCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); + AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad); } @@ -93,9 +93,9 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCancelCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); @@ -120,10 +120,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(2) }).WaitSafely()); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } @@ -133,22 +133,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddAssert("countdown button is visible", () => this.ChildrenOfType().Single().IsPresent); - AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button is visible", () => this.ChildrenOfType().Single().IsPresent); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); - AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); - AddAssert("countdown button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + AddAssert("countdown button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } [Test] public void TestSpectatingDuringCountdownWithNoReadyUsersCancelsCountdown() { - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); @@ -169,9 +169,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" })); AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready)); - ClickButtonWhenEnabled(); - AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); + AddUntilStep("countdown button shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent == true); + ClickButtonWhenEnabled(); AddStep("click the first countdown button", () => { var popoverButton = this.ChildrenOfType().Single().ChildrenOfType().First(); @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddAssert("ready button enabled", () => this.ChildrenOfType().Single().Enabled.Value); + AddAssert("ready button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } [Test] @@ -200,7 +200,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID)); AddUntilStep("local user is host", () => MultiplayerClient.Room?.Host?.Equals(MultiplayerClient.LocalUser) == true); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null); } @@ -231,10 +231,10 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); } @@ -250,7 +250,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); verifyGameplayStartFlow(); @@ -265,7 +265,7 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.TransferHost(2); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddStep("make user host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[0].UserID ?? 0)); verifyGameplayStartFlow(); @@ -280,12 +280,12 @@ namespace osu.Game.Tests.Visual.Multiplayer MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }); }); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); AddStep("transfer host", () => MultiplayerClient.TransferHost(MultiplayerClient.Room?.Users[1].UserID ?? 0)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user is idle (match not started)", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle); AddUntilStep("ready button enabled", () => control.ChildrenOfType().Single().Enabled.Value); } @@ -305,7 +305,7 @@ namespace osu.Game.Tests.Visual.Multiplayer if (!isHost) AddStep("transfer host", () => MultiplayerClient.TransferHost(2)); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddRepeatStep("change user ready state", () => { @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void verifyGameplayStartFlow() { AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("user waiting for load", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); AddStep("finish gameplay", () => diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 850a115f4c..057032c413 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for spectating user state", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - ClickButtonWhenEnabled(); + ClickButtonWhenEnabled(); AddUntilStep("match started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.WaitingForLoad); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 07ac580276..13917f4eb0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -146,6 +146,6 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); private void assertReadyButtonEnablement(bool shouldBeEnabled) - => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => startControl.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + => AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => startControl.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 9470fb1d68..af7ed9b9e2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private Sample sampleUnready; private readonly BindableBool enabled = new BindableBool(); - private readonly CountdownButton countdownButton; + private readonly MultiplayerCountdownButton countdownButton; private int countReady; private ScheduledDelegate readySampleDelegate; private IBindable operationInProgress; @@ -50,14 +50,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { new Drawable[] { - new ReadyButton + new MultiplayerReadyButton { RelativeSizeAxes = Axes.Both, Size = Vector2.One, Action = onReadyClick, Enabled = { BindTarget = enabled }, }, - countdownButton = new CountdownButton + countdownButton = new MultiplayerCountdownButton { RelativeSizeAxes = Axes.Y, Size = new Vector2(40, 1), diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs similarity index 95% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs index e37168bf25..3bf7e91a55 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/CountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs @@ -18,7 +18,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public class CountdownButton : IconButton, IHasPopover + public class MultiplayerCountdownButton : IconButton, IHasPopover { private static readonly TimeSpan[] available_delays = { @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private readonly Drawable background; - public CountdownButton() + public MultiplayerCountdownButton() { Icon = FontAwesome.Solid.CaretDown; IconScale = new Vector2(0.6f); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs similarity index 98% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 007e055d8c..6ff717d5c3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -10,10 +10,11 @@ using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { - public class ReadyButton : Components.ReadyButton + public class MultiplayerReadyButton : ReadyButton { public new Triangles Triangles => base.Triangles; From d36944ac950d86761931befc4a7e020040d9ce1e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 14:35:03 +0900 Subject: [PATCH 17/24] Dispose token manually Cover more branches with cancellation source disposal --- .../Visual/Multiplayer/TestMultiplayerClient.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 1f20292437..5fa2ca8890 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -343,19 +343,19 @@ namespace osu.Game.Tests.Visual.Multiplayer Schedule(() => { - if (Room.Countdown != countdown) - return; - - Room.Countdown = null; - MatchEvent(new CountdownChangedEvent { Countdown = null }); - using (cancellationSource) { + if (Room.Countdown != countdown) + return; + + Room.Countdown = null; + MatchEvent(new CountdownChangedEvent { Countdown = null }); + if (stopSource.Token.IsCancellationRequested) return; - } - StartMatch().WaitSafely(); + StartMatch().WaitSafely(); + } }); } From 8f3a4df70adc38f9ed4fb89e31b3367012eb8e45 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 14:44:27 +0900 Subject: [PATCH 18/24] Add explanation for try-catch --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 5fa2ca8890..f96ba8ba24 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -339,6 +339,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } catch (OperationCanceledException) { + // Clients need to be notified of cancellations in the following code. } Schedule(() => From d2ecc100e5390460a4216ac5e4ccc54ce62f89aa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 15:07:01 +0900 Subject: [PATCH 19/24] Revert unnecessary async change --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f96ba8ba24..7562b1d0e2 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } } - public override async Task StartMatch() + public override Task StartMatch() { Debug.Assert(Room != null); @@ -395,7 +395,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); - await ((IMultiplayerClient)this).LoadRequested(); + return ((IMultiplayerClient)this).LoadRequested(); } public override Task AbortGameplay() From f0d132b16e04e85bb84c8b13d42d15559d2ce4e5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 15:21:46 +0900 Subject: [PATCH 20/24] Rename FinishCountdown() -> SkipToEndOfCountdown() --- .../Visual/Multiplayer/TestSceneMatchStartControl.cs | 6 +++--- .../Visual/Multiplayer/TestMultiplayerClient.cs | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index 4fd6fd5d70..a374488306 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddAssert("countdown button not visible", () => !this.ChildrenOfType().Single().IsPresent); - AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); + AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown()); AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad); } @@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer ClickButtonWhenEnabled(); - AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); + AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown()); AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready); } @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating)); AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating); - AddStep("finish countdown", () => MultiplayerClient.FinishCountdown()); + AddStep("finish countdown", () => MultiplayerClient.SkipToEndOfCountdown()); AddUntilStep("match not started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.Open); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 7562b1d0e2..ea991af914 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -296,11 +296,15 @@ namespace osu.Game.Tests.Visual.Multiplayer return Task.CompletedTask; } - private CancellationTokenSource? countdownFinishSource; + private CancellationTokenSource? countdownSkipSource; private CancellationTokenSource? countdownStopSource; private Task countdownTask = Task.CompletedTask; - public void FinishCountdown() => countdownFinishSource?.Cancel(); + /// + /// Skips to the end of the currently-running countdown, if one is running, + /// and runs the callback (e.g. to start the match) as soon as possible unless the countdown has been cancelled. + /// + public void SkipToEndOfCountdown() => countdownSkipSource?.Cancel(); public override async Task SendMatchRequest(MatchUserRequest request) { @@ -313,8 +317,8 @@ namespace osu.Game.Tests.Visual.Multiplayer countdownStopSource?.Cancel(); var stopSource = countdownStopSource = new CancellationTokenSource(); - var finishSource = countdownFinishSource = new CancellationTokenSource(); - var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, finishSource.Token); + var skipSource = countdownSkipSource = new CancellationTokenSource(); + var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, skipSource.Token); var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Duration }; Task lastCountdownTask = countdownTask; From 4c0d76573c10a0ae54463d491367ce43c0e3a691 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 15:51:30 +0900 Subject: [PATCH 21/24] Asserate code is running on update thread --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index ea991af914..6f57d818a4 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -314,6 +315,8 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (request) { case StartMatchCountdownRequest matchCountdownRequest: + Debug.Assert(ThreadSafety.IsUpdateThread); + countdownStopSource?.Cancel(); var stopSource = countdownStopSource = new CancellationTokenSource(); From dfa076c16983fe5badebd5bc3887cdf81d2fe2fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 16:29:59 +0900 Subject: [PATCH 22/24] Refactor cancellation logic --- .../Multiplayer/TestMultiplayerClient.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 6f57d818a4..9be1b18062 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -319,9 +319,10 @@ namespace osu.Game.Tests.Visual.Multiplayer countdownStopSource?.Cancel(); + // Note that this will leak CTSs, however this is a test method and we haven't noticed foregoing disposal of non-linked CTSs to be detrimental. + // If necessary, this can be moved into the final schedule below, and the class-level fields be nulled out accordingly. var stopSource = countdownStopSource = new CancellationTokenSource(); var skipSource = countdownSkipSource = new CancellationTokenSource(); - var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, skipSource.Token); var countdown = new MatchStartCountdown { TimeRemaining = matchCountdownRequest.Duration }; Task lastCountdownTask = countdownTask; @@ -342,7 +343,8 @@ namespace osu.Game.Tests.Visual.Multiplayer try { - await Task.Delay(matchCountdownRequest.Duration, cancellationSource.Token).ConfigureAwait(false); + using (var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, skipSource.Token)) + await Task.Delay(matchCountdownRequest.Duration, cancellationSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -351,19 +353,16 @@ namespace osu.Game.Tests.Visual.Multiplayer Schedule(() => { - using (cancellationSource) - { - if (Room.Countdown != countdown) - return; + if (Room.Countdown != countdown) + return; - Room.Countdown = null; - MatchEvent(new CountdownChangedEvent { Countdown = null }); + Room.Countdown = null; + MatchEvent(new CountdownChangedEvent { Countdown = null }); - if (stopSource.Token.IsCancellationRequested) - return; + if (stopSource.IsCancellationRequested) + return; - StartMatch().WaitSafely(); - } + StartMatch().WaitSafely(); }); } From 528ffea38db1b368b49a5c203fb744d33cbb6568 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 24 Mar 2022 17:11:08 +0900 Subject: [PATCH 23/24] Fix incorrect event binding --- .../OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 6ff717d5c3..746e4257f1 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { base.LoadComplete(); - multiplayerClient.RoomUpdated += () => Scheduler.AddOnce(onRoomUpdated); + multiplayerClient.RoomUpdated += onRoomUpdated; onRoomUpdated(); } @@ -39,7 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private DateTimeOffset countdownReceivedTime; private ScheduledDelegate countdownUpdateDelegate; - private void onRoomUpdated() + private void onRoomUpdated() => Scheduler.AddOnce(() => { if (countdown == null && room?.Countdown != null) countdownReceivedTime = DateTimeOffset.Now; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateButtonText(); updateButtonColour(); - } + }); private void updateButtonText() { From e3f8bc05883e126e176d6b12e54e0df39c85003a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 24 Mar 2022 17:14:51 +0900 Subject: [PATCH 24/24] Revert `Availability` to `private` --- .../Screens/OnlinePlay/Components/ReadyButton.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 79cf5c7236..cdaa39d2be 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -14,18 +14,20 @@ namespace osu.Game.Screens.OnlinePlay.Components public abstract class ReadyButton : TriangleButton, IHasTooltip { public new readonly BindableBool Enabled = new BindableBool(); - protected readonly IBindable Availability = new Bindable(); + + private readonly IBindable availability = new Bindable(); [BackgroundDependencyLoader] private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker) { - Availability.BindTo(beatmapTracker.Availability); - Availability.BindValueChanged(_ => updateState()); + availability.BindTo(beatmapTracker.Availability); + + availability.BindValueChanged(_ => updateState()); Enabled.BindValueChanged(_ => updateState(), true); } private void updateState() => - base.Enabled.Value = Availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; + base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value; public virtual LocalisableString TooltipText { @@ -34,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Components if (Enabled.Value) return string.Empty; - if (Availability.Value.State != DownloadState.LocallyAvailable) + if (availability.Value.State != DownloadState.LocallyAvailable) return "Beatmap not downloaded"; return string.Empty;