From d5ba77b2c2b5984b279c7809a1f58878c92e0936 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 21:22:28 +0900 Subject: [PATCH 01/14] Add spectating user state --- osu.Game/Online/Multiplayer/MultiplayerUserState.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerUserState.cs b/osu.Game/Online/Multiplayer/MultiplayerUserState.cs index e54c71cd85..c467ff84bb 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerUserState.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerUserState.cs @@ -55,5 +55,10 @@ namespace osu.Game.Online.Multiplayer /// The user is currently viewing results. This is a reserved state, and is set by the server. /// Results, + + /// + /// The user is currently spectating this room. + /// + Spectating } } From 6de91d7b6bb38caa25ea455a2868b548b083d5ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 6 Apr 2021 21:37:21 +0900 Subject: [PATCH 02/14] Add spectate button + test --- .../TestSceneMultiplayerSpectateButton.cs | 53 +++++++++++ .../Multiplayer/StatefulMultiplayerClient.cs | 27 ++++++ .../Match/MultiplayerSpectateButton.cs | 92 +++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs new file mode 100644 index 0000000000..730fee2fba --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -0,0 +1,53 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene + { + private MultiplayerSpectateButton button; + private IDisposable readyClickOperation; + + [SetUp] + public new void Setup() => Schedule(() => + { + Child = button = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnSpectateClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + } + }; + }); + + [TestCase(MultiplayerUserState.Idle)] + [TestCase(MultiplayerUserState.Ready)] + public void TestToggleWhenIdle(MultiplayerUserState initialState) + { + addClickButtonStep(); + AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); + + addClickButtonStep(); + AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); + } + + private void addClickButtonStep() => AddStep("click button", () => + { + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + } +} diff --git a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs index 0f7050596f..2ddc10db0f 100644 --- a/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/StatefulMultiplayerClient.cs @@ -249,6 +249,33 @@ namespace osu.Game.Online.Multiplayer } } + /// + /// Toggles the 's spectating state. + /// + /// If a toggle of the spectating state is not valid at this time. + public async Task ToggleSpectate() + { + var localUser = LocalUser; + + if (localUser == null) + return; + + switch (localUser.State) + { + case MultiplayerUserState.Idle: + case MultiplayerUserState.Ready: + await ChangeState(MultiplayerUserState.Spectating).ConfigureAwait(false); + return; + + case MultiplayerUserState.Spectating: + await ChangeState(MultiplayerUserState.Idle).ConfigureAwait(false); + return; + + default: + throw new InvalidOperationException($"Cannot toggle spectate when in {localUser.State}"); + } + } + public abstract Task TransferHost(int userId); public abstract Task ChangeSettings(MultiplayerRoomSettings settings); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs new file mode 100644 index 0000000000..50be7719d9 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -0,0 +1,92 @@ +// 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.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match +{ + public class MultiplayerSpectateButton : MultiplayerRoomComposite + { + public Action OnSpectateClick + { + set => button.Action = value; + } + + [Resolved] + private OngoingOperationTracker ongoingOperationTracker { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + private IBindable operationInProgress; + + private readonly ButtonWithTrianglesExposed button; + + public MultiplayerSpectateButton() + { + InternalChild = button = new ButtonWithTrianglesExposed + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Enabled = { Value = true }, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + operationInProgress = ongoingOperationTracker.InProgress.GetBoundCopy(); + operationInProgress.BindValueChanged(_ => updateState()); + } + + protected override void OnRoomUpdated() + { + base.OnRoomUpdated(); + + updateState(); + } + + private void updateState() + { + var localUser = Client.LocalUser; + + if (localUser == null) + return; + + Debug.Assert(Room != null); + + switch (localUser.State) + { + default: + button.Text = "Spectate"; + button.BackgroundColour = colours.Blue; + button.Triangles.ColourDark = colours.Blue; + button.Triangles.ColourLight = colours.BlueLight; + break; + + case MultiplayerUserState.Spectating: + button.Text = "Stop spectating"; + button.BackgroundColour = colours.Red; + button.Triangles.ColourDark = colours.Red; + button.Triangles.ColourLight = colours.RedLight; + break; + } + + button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + } + + private class ButtonWithTrianglesExposed : TriangleButton + { + public new Triangles Triangles => base.Triangles; + } + } +} From 1f57b6884d0e944907cf4ce6f2cc12c157fc32c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:19:10 +0900 Subject: [PATCH 03/14] Add ready button to test scene --- .../TestSceneMultiplayerSpectateButton.cs | 121 ++++++++++++++++-- 1 file changed, 108 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 730fee2fba..52a1909396 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -2,10 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +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; @@ -13,22 +25,96 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene { - private MultiplayerSpectateButton button; + private MultiplayerSpectateButton spectateButton; + private MultiplayerReadyButton readyButton; + + private readonly Bindable selectedItem = new Bindable(); + + private BeatmapSetInfo importedSet; + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private IDisposable readyClickOperation; + protected override Container Content => content; + private readonly Container content; + + public TestSceneMultiplayerSpectateButton() + { + base.Content.Add(content = new Container + { + RelativeSizeAxes = Axes.Both + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + return dependencies; + } + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + var beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }; + base.Content.Add(beatmapTracker); + Dependencies.Cache(beatmapTracker); + + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + } + [SetUp] public new void Setup() => Schedule(() => { - Child = button = new MultiplayerSpectateButton + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), - OnSpectateClick = async () => + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset }, + }; + + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - readyClickOperation = OngoingOperationTracker.BeginOperation(); - await Client.ToggleSpectate(); - readyClickOperation.Dispose(); + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnSpectateClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + await Client.ToggleSpectate(); + readyClickOperation.Dispose(); + } + }, + readyButton = new MultiplayerReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + OnReadyClick = async () => + { + readyClickOperation = OngoingOperationTracker.BeginOperation(); + + if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready) + { + await Client.StartMatch(); + return; + } + + await Client.ToggleReady(); + readyClickOperation.Dispose(); + } + } } }; }); @@ -37,17 +123,26 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(MultiplayerUserState.Ready)] public void TestToggleWhenIdle(MultiplayerUserState initialState) { - addClickButtonStep(); + addClickSpectateButtonStep(); AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating); - addClickButtonStep(); + addClickSpectateButtonStep(); AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } - private void addClickButtonStep() => AddStep("click button", () => + private void addClickSpectateButtonStep() => AddStep("click spectate button", () => { - InputManager.MoveMouseTo(button); + InputManager.MoveMouseTo(spectateButton); InputManager.Click(MouseButton.Left); }); + + private void addClickReadyButtonStep() => AddStep("click ready button", () => + { + InputManager.MoveMouseTo(readyButton); + InputManager.Click(MouseButton.Left); + }); + + private void assertReadyButtonEnablement(bool shouldBeEnabled) + => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } } From 6be9c9f0f4e8188d4e50b55e26f0025cc2d06292 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:35:36 +0900 Subject: [PATCH 04/14] Link up ready button to spectate state --- .../TestSceneMultiplayerSpectateButton.cs | 51 +++++++++++++++++++ .../Match/MultiplayerReadyButton.cs | 11 +++- .../Multiplayer/TestMultiplayerClient.cs | 6 +++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 52a1909396..631511eac5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -18,6 +18,7 @@ using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Resources; +using osu.Game.Users; using osuTK; using osuTK.Input; @@ -119,6 +120,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); + [Test] + public void TestEnabledWhenRoomOpen() + { + assertSpectateButtonEnablement(true); + } + [TestCase(MultiplayerUserState.Idle)] [TestCase(MultiplayerUserState.Ready)] public void TestToggleWhenIdle(MultiplayerUserState initialState) @@ -130,6 +137,47 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle); } + [TestCase(MultiplayerRoomState.WaitingForLoad)] + [TestCase(MultiplayerRoomState.Playing)] + [TestCase(MultiplayerRoomState.Closed)] + public void TestDisabledDuringGameplayOrClosed(MultiplayerRoomState roomState) + { + AddStep($"change user to {roomState}", () => Client.ChangeRoomState(roomState)); + assertSpectateButtonEnablement(false); + } + + [Test] + public void TestReadyButtonDisabledWhenHostAndNoReadyUsers() + { + addClickSpectateButtonStep(); + assertReadyButtonEnablement(false); + } + + [Test] + public void TestReadyButtonEnabledWhenHostAndUsersReady() + { + AddStep("add user", () => Client.AddUser(new User { Id = 55 })); + AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + + addClickSpectateButtonStep(); + assertReadyButtonEnablement(true); + } + + [Test] + public void TestReadyButtonDisabledWhenNotHostAndUsersReady() + { + AddStep("add user and transfer host", () => + { + Client.AddUser(new User { Id = 55 }); + Client.TransferHost(55); + }); + + AddStep("set user ready", () => Client.ChangeUserState(55, MultiplayerUserState.Ready)); + + addClickSpectateButtonStep(); + assertReadyButtonEnablement(false); + } + private void addClickSpectateButtonStep() => AddStep("click spectate button", () => { InputManager.MoveMouseTo(spectateButton); @@ -142,6 +190,9 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); + private void assertSpectateButtonEnablement(bool shouldBeEnabled) + => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); + private void assertReadyButtonEnablement(bool shouldBeEnabled) => AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index c9fb234ccc..6919be2d56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -78,8 +78,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(Room != null); int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready); + int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating); - string countText = $"({newCountReady} / {Room.Users.Count} ready)"; + string countText = $"({newCountReady} / {newCountTotal} ready)"; switch (localUser.State) { @@ -88,6 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match updateButtonColour(true); break; + case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: if (Room?.Host?.Equals(localUser) == true) { @@ -105,6 +107,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + // When the local user is the host and spectating the match, the "start match" state should be enabled. + if (localUser.State == MultiplayerUserState.Spectating) + { + button.Enabled.Value &= Room?.Host?.Equals(localUser) == true; + button.Enabled.Value &= newCountReady > 0; + } + if (newCountReady != countReady) { countReady = newCountReady; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 09fcc1ff47..4c954c7d27 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -58,6 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + public void ChangeRoomState(MultiplayerRoomState newState) + { + Debug.Assert(Room != null); + ((IMultiplayerClient)this).RoomStateChanged(newState); + } + public void ChangeUserState(int userId, MultiplayerUserState newState) { Debug.Assert(Room != null); From f5667125a017e56d153c388638c2f2a13d624389 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:37:43 +0900 Subject: [PATCH 05/14] Remove unnecessary method --- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 631511eac5..8fe1f99e1d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -184,12 +184,6 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); - private void addClickReadyButtonStep() => AddStep("click ready button", () => - { - InputManager.MoveMouseTo(readyButton); - InputManager.Click(MouseButton.Left); - }); - private void assertSpectateButtonEnablement(bool shouldBeEnabled) => AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType().Single().Enabled.Value == shouldBeEnabled); From c744f77cfafdab402bbc1e34793402baf6c0995c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 16:40:24 +0900 Subject: [PATCH 06/14] Add participant panel state --- .../Multiplayer/TestSceneMultiplayerParticipantsList.cs | 7 +++++++ .../OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e713cff233..7f8f04b718 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -126,6 +126,13 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("ready mark invisible", () => !this.ChildrenOfType().Single().IsPresent); } + [Test] + public void TestToggleSpectateState() + { + AddStep("make user spectating", () => Client.ChangeState(MultiplayerUserState.Spectating)); + AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle)); + } + [Test] public void TestCrownChangesStateWhenHostTransferred() { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index c571b51c83..e6a407acec 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -135,6 +135,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants icon.Colour = colours.BlueLighter; break; + case MultiplayerUserState.Spectating: + text.Text = "spectating"; + icon.Icon = FontAwesome.Solid.Eye; + icon.Colour = colours.BlueLight; + break; + default: throw new ArgumentOutOfRangeException(nameof(state), state, null); } From abd637ffaa5345f71f248823de47a3d5d4e805f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 17:35:18 +0900 Subject: [PATCH 07/14] Add button to footer --- .../TestSceneMultiplayerMatchFooter.cs | 27 +++++++++++++ .../Match/MultiplayerMatchFooter.cs | 40 ++++++++++++++++--- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs new file mode 100644 index 0000000000..08c94b8135 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene + { + [Cached] + private readonly OnlinePlayBeatmapAvailablilityTracker availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker(); + + [BackgroundDependencyLoader] + private void load() + { + Child = new MultiplayerMatchFooter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 50 + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs index fdc1ae9d3c..d4f5428bfb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchFooter.cs @@ -8,21 +8,28 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerMatchFooter : CompositeDrawable { public const float HEIGHT = 50; + private const float ready_button_width = 600; + private const float spectate_button_width = 200; public Action OnReadyClick { set => readyButton.OnReadyClick = value; } + public Action OnSpectateClick + { + set => spectateButton.OnSpectateClick = value; + } + private readonly Drawable background; private readonly MultiplayerReadyButton readyButton; + private readonly MultiplayerSpectateButton spectateButton; public MultiplayerMatchFooter() { @@ -32,11 +39,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match InternalChildren = new[] { background = new Box { RelativeSizeAxes = Axes.Both }, - readyButton = new MultiplayerReadyButton + new GridContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(600, 50), + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + null, + spectateButton = new MultiplayerSpectateButton + { + RelativeSizeAxes = Axes.Both, + }, + null, + readyButton = new MultiplayerReadyButton + { + RelativeSizeAxes = Axes.Both, + }, + null + } + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(maxSize: spectate_button_width), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(maxSize: ready_button_width), + new Dimension() + } } }; } From 93c5935ebc4adc5989122b0f502fab6c44dd5064 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 20:46:30 +0900 Subject: [PATCH 08/14] Add match subscreen support + test --- .../TestSceneMultiplayerMatchSubScreen.cs | 61 +++++++++++++++++++ .../Multiplayer/MultiplayerMatchSubScreen.cs | 19 +++++- .../Multiplayer/TestMultiplayerClient.cs | 3 + 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 8869718fd1..6f8ec7fcfb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -3,13 +3,21 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; +using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -18,11 +26,25 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerMatchSubScreen screen; + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + public TestSceneMultiplayerMatchSubScreen() : base(false) { } + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + } + [SetUp] public new void Setup() => Schedule(() => { @@ -73,5 +95,44 @@ namespace osu.Game.Tests.Visual.Multiplayer AddWaitStep("wait", 10); } + + [Test] + public void TestStartMatchWhileSpectating() + { + AddStep("set playlist", () => + { + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + }); + }); + + AddStep("click create button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("join other user (ready)", () => + { + Client.AddUser(new User { Id = 55 }); + Client.ChangeUserState(55, MultiplayerUserState.Ready); + }); + + AddStep("click spectate button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("click ready button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ceeee67806..90cef0107c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -221,7 +221,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerMatchFooter { - OnReadyClick = onReadyClick + OnReadyClick = onReadyClick, + OnSpectateClick = onSpectateClick } } }, @@ -363,7 +364,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Debug.Assert(readyClickOperation == null); readyClickOperation = ongoingOperationTracker.BeginOperation(); - if (client.IsHost && client.LocalUser?.State == MultiplayerUserState.Ready) + if (client.IsHost && (client.LocalUser?.State == MultiplayerUserState.Ready || client.LocalUser?.State == MultiplayerUserState.Spectating)) { client.StartMatch() .ContinueWith(t => @@ -390,6 +391,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } } + private void onSpectateClick() + { + Debug.Assert(readyClickOperation == null); + readyClickOperation = ongoingOperationTracker.BeginOperation(); + + client.ToggleSpectate().ContinueWith(t => endOperation()); + + void endOperation() + { + readyClickOperation?.Dispose(); + readyClickOperation = null; + } + } + private void onRoomUpdated() { // user mods may have changed. diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4c954c7d27..b5cd3dad02 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -77,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case MultiplayerUserState.Loaded: if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad)) { + ChangeRoomState(MultiplayerRoomState.Playing); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded)) ChangeUserState(u.UserID, MultiplayerUserState.Playing); @@ -88,6 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer case MultiplayerUserState.FinishedPlay: if (Room.Users.All(u => u.State != MultiplayerUserState.Playing)) { + ChangeRoomState(MultiplayerRoomState.Open); foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay)) ChangeUserState(u.UserID, MultiplayerUserState.Results); @@ -179,6 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { Debug.Assert(Room != null); + ChangeRoomState(MultiplayerRoomState.WaitingForLoad); foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready)) ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad); From 2791d454d22a8d61e254531635d7a6d7c8bb746e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:21:22 +0900 Subject: [PATCH 09/14] Don't send spectating user state yet --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4529dfd0a7..37e11cc576 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -96,6 +96,9 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + if (newState == MultiplayerUserState.Spectating) + return Task.CompletedTask; // Not supported yet. + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); } From 214813154b775f1fa8c3a075e55690ce8ef2ee0a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 7 Apr 2021 22:28:22 +0900 Subject: [PATCH 10/14] Fix class name --- .../Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index 08c94b8135..6b03b53b4b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { [Cached] - private readonly OnlinePlayBeatmapAvailablilityTracker availablilityTracker = new OnlinePlayBeatmapAvailablilityTracker(); + private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 8fe1f99e1d..e65e4a68a7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); - var beatmapTracker = new OnlinePlayBeatmapAvailablilityTracker { SelectedItem = { BindTarget = selectedItem } }; + var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }; base.Content.Add(beatmapTracker); Dependencies.Cache(beatmapTracker); From b73860cb5f6de153df50f3dc64b067dd611a6f40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:47:55 +0900 Subject: [PATCH 11/14] Slightly alter button colour scheme to make text more legible and reduce saturation --- .../Multiplayer/Match/MultiplayerSpectateButton.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs index 50be7719d9..4b3fb5d00f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerSpectateButton.cs @@ -68,16 +68,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { default: button.Text = "Spectate"; - button.BackgroundColour = colours.Blue; - button.Triangles.ColourDark = colours.Blue; - button.Triangles.ColourLight = colours.BlueLight; + button.BackgroundColour = colours.BlueDark; + button.Triangles.ColourDark = colours.BlueDarker; + button.Triangles.ColourLight = colours.Blue; break; case MultiplayerUserState.Spectating: button.Text = "Stop spectating"; - button.BackgroundColour = colours.Red; - button.Triangles.ColourDark = colours.Red; - button.Triangles.ColourLight = colours.RedLight; + button.BackgroundColour = colours.Gray4; + button.Triangles.ColourDark = colours.Gray5; + button.Triangles.ColourLight = colours.Gray6; break; } From a55e62188ebe87648bcc1ffe9f6af93f167072a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Apr 2021 15:54:58 +0900 Subject: [PATCH 12/14] Change state icon to binoculars so the eye isn't staring at me --- .../Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs index e6a407acec..2616b07c1f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/StateDisplay.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants case MultiplayerUserState.Spectating: text.Text = "spectating"; - icon.Icon = FontAwesome.Solid.Eye; + icon.Icon = FontAwesome.Solid.Binoculars; icon.Colour = colours.BlueLight; break; From b38d332268bf678cda1746bb264a7adb32d7a7e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 16:29:11 +0900 Subject: [PATCH 13/14] Fix broken test --- .../Multiplayer/TestSceneMultiplayerReadyButton.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs index dad1237991..dfb4306e67 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs @@ -209,9 +209,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { addClickButtonStep(); AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad); - AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); + AddAssert("ready button disabled", () => !button.ChildrenOfType().Single().Enabled.Value); AddStep("transitioned to gameplay", () => readyClickOperation.Dispose()); + + AddStep("finish gameplay", () => + { + Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded); + Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay); + }); + AddAssert("ready button enabled", () => button.ChildrenOfType().Single().Enabled.Value); } } From fd2a14a0bf07fd5e8b940f2f666455fc4eb923ba Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 8 Apr 2021 16:30:48 +0900 Subject: [PATCH 14/14] Only set button state once --- .../Multiplayer/Match/MultiplayerReadyButton.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 6919be2d56..f2dd9a6f25 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -105,14 +105,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match break; } - button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; + bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value; - // When the local user is the host and spectating the match, the "start match" state should be enabled. + // 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) - { - button.Enabled.Value &= Room?.Host?.Equals(localUser) == true; - button.Enabled.Value &= newCountReady > 0; - } + enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0; + + button.Enabled.Value = enableButton; if (newCountReady != countReady) {