mirror of
https://github.com/ppy/osu.git
synced 2025-01-31 13:33:20 +08:00
Merge pull request #12321 from smoogipoo/add-spectate-button-and-state
Add multiplayer spectating user state and button
This commit is contained in:
commit
1e23f671fa
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new MultiplayerMatchFooter
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,13 +3,21 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
@ -18,11 +26,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private MultiplayerMatchSubScreen screen;
|
private MultiplayerMatchSubScreen screen;
|
||||||
|
|
||||||
|
private BeatmapManager beatmaps;
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
private BeatmapSetInfo importedSet;
|
||||||
|
|
||||||
public TestSceneMultiplayerMatchSubScreen()
|
public TestSceneMultiplayerMatchSubScreen()
|
||||||
: base(false)
|
: 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]
|
[SetUp]
|
||||||
public new void Setup() => Schedule(() =>
|
public new void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -73,5 +95,44 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddWaitStep("wait", 10);
|
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<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>().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<MultiplayerSpectateButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click ready button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("match started", () => Client.Room?.State == MultiplayerRoomState.WaitingForLoad);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +126,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddUntilStep("ready mark invisible", () => !this.ChildrenOfType<StateDisplay>().Single().IsPresent);
|
AddUntilStep("ready mark invisible", () => !this.ChildrenOfType<StateDisplay>().Single().IsPresent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestToggleSpectateState()
|
||||||
|
{
|
||||||
|
AddStep("make user spectating", () => Client.ChangeState(MultiplayerUserState.Spectating));
|
||||||
|
AddStep("make user idle", () => Client.ChangeState(MultiplayerUserState.Idle));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCrownChangesStateWhenHostTransferred()
|
public void TestCrownChangesStateWhenHostTransferred()
|
||||||
{
|
{
|
||||||
|
@ -209,9 +209,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
|
||||||
|
|
||||||
|
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
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<OsuButton>().Single().Enabled.Value);
|
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.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 osu.Game.Users;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneMultiplayerSpectateButton : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
private MultiplayerSpectateButton spectateButton;
|
||||||
|
private MultiplayerReadyButton readyButton;
|
||||||
|
|
||||||
|
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
|
||||||
|
|
||||||
|
private BeatmapSetInfo importedSet;
|
||||||
|
private BeatmapManager beatmaps;
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
|
||||||
|
private IDisposable readyClickOperation;
|
||||||
|
|
||||||
|
protected override Container<Drawable> 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 OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
|
||||||
|
base.Content.Add(beatmapTracker);
|
||||||
|
Dependencies.Cache(beatmapTracker);
|
||||||
|
|
||||||
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||||
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
|
||||||
|
selectedItem.Value = new PlaylistItem
|
||||||
|
{
|
||||||
|
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
|
||||||
|
Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset },
|
||||||
|
};
|
||||||
|
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEnabledWhenRoomOpen()
|
||||||
|
{
|
||||||
|
assertSpectateButtonEnablement(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(MultiplayerUserState.Idle)]
|
||||||
|
[TestCase(MultiplayerUserState.Ready)]
|
||||||
|
public void TestToggleWhenIdle(MultiplayerUserState initialState)
|
||||||
|
{
|
||||||
|
addClickSpectateButtonStep();
|
||||||
|
AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
|
addClickSpectateButtonStep();
|
||||||
|
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);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
|
||||||
|
=> AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||||
|
|
||||||
|
private void assertReadyButtonEnablement(bool shouldBeEnabled)
|
||||||
|
=> AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,9 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
if (!IsConnected.Value)
|
if (!IsConnected.Value)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (newState == MultiplayerUserState.Spectating)
|
||||||
|
return Task.CompletedTask; // Not supported yet.
|
||||||
|
|
||||||
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState);
|
return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
/// The user is currently viewing results. This is a reserved state, and is set by the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Results,
|
Results,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The user is currently spectating this room.
|
||||||
|
/// </summary>
|
||||||
|
Spectating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,6 +249,33 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the <see cref="LocalUser"/>'s spectating state.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">If a toggle of the spectating state is not valid at this time.</exception>
|
||||||
|
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 TransferHost(int userId);
|
||||||
|
|
||||||
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
|
||||||
|
@ -8,21 +8,28 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||||
{
|
{
|
||||||
public class MultiplayerMatchFooter : CompositeDrawable
|
public class MultiplayerMatchFooter : CompositeDrawable
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 50;
|
public const float HEIGHT = 50;
|
||||||
|
private const float ready_button_width = 600;
|
||||||
|
private const float spectate_button_width = 200;
|
||||||
|
|
||||||
public Action OnReadyClick
|
public Action OnReadyClick
|
||||||
{
|
{
|
||||||
set => readyButton.OnReadyClick = value;
|
set => readyButton.OnReadyClick = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Action OnSpectateClick
|
||||||
|
{
|
||||||
|
set => spectateButton.OnSpectateClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Drawable background;
|
private readonly Drawable background;
|
||||||
private readonly MultiplayerReadyButton readyButton;
|
private readonly MultiplayerReadyButton readyButton;
|
||||||
|
private readonly MultiplayerSpectateButton spectateButton;
|
||||||
|
|
||||||
public MultiplayerMatchFooter()
|
public MultiplayerMatchFooter()
|
||||||
{
|
{
|
||||||
@ -32,11 +39,34 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
null,
|
||||||
|
spectateButton = new MultiplayerSpectateButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
null,
|
||||||
readyButton = new MultiplayerReadyButton
|
readyButton = new MultiplayerReadyButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
},
|
||||||
Size = new Vector2(600, 50),
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(maxSize: spectate_button_width),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 10),
|
||||||
|
new Dimension(maxSize: ready_button_width),
|
||||||
|
new Dimension()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
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)
|
switch (localUser.State)
|
||||||
{
|
{
|
||||||
@ -88,6 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
updateButtonColour(true);
|
updateButtonColour(true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
if (Room?.Host?.Equals(localUser) == true)
|
if (Room?.Host?.Equals(localUser) == true)
|
||||||
{
|
{
|
||||||
@ -103,7 +105,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
break;
|
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 if any users are ready.
|
||||||
|
if (localUser.State == MultiplayerUserState.Spectating)
|
||||||
|
enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0;
|
||||||
|
|
||||||
|
button.Enabled.Value = enableButton;
|
||||||
|
|
||||||
if (newCountReady != countReady)
|
if (newCountReady != countReady)
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.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<bool> 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.BlueDark;
|
||||||
|
button.Triangles.ColourDark = colours.BlueDarker;
|
||||||
|
button.Triangles.ColourLight = colours.Blue;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Spectating:
|
||||||
|
button.Text = "Stop spectating";
|
||||||
|
button.BackgroundColour = colours.Gray4;
|
||||||
|
button.Triangles.ColourDark = colours.Gray5;
|
||||||
|
button.Triangles.ColourLight = colours.Gray6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ButtonWithTrianglesExposed : TriangleButton
|
||||||
|
{
|
||||||
|
public new Triangles Triangles => base.Triangles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -221,7 +221,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
{
|
{
|
||||||
new MultiplayerMatchFooter
|
new MultiplayerMatchFooter
|
||||||
{
|
{
|
||||||
OnReadyClick = onReadyClick
|
OnReadyClick = onReadyClick,
|
||||||
|
OnSpectateClick = onSpectateClick
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -363,7 +364,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
|||||||
Debug.Assert(readyClickOperation == null);
|
Debug.Assert(readyClickOperation == null);
|
||||||
readyClickOperation = ongoingOperationTracker.BeginOperation();
|
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()
|
client.StartMatch()
|
||||||
.ContinueWith(t =>
|
.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()
|
private void onRoomUpdated()
|
||||||
{
|
{
|
||||||
// user mods may have changed.
|
// user mods may have changed.
|
||||||
|
@ -135,6 +135,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
icon.Colour = colours.BlueLighter;
|
icon.Colour = colours.BlueLighter;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Spectating:
|
||||||
|
text.Text = "spectating";
|
||||||
|
icon.Icon = FontAwesome.Solid.Binoculars;
|
||||||
|
icon.Colour = colours.BlueLight;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
public void ChangeUserState(int userId, MultiplayerUserState newState)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
@ -71,6 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
case MultiplayerUserState.Loaded:
|
case MultiplayerUserState.Loaded:
|
||||||
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
||||||
{
|
{
|
||||||
|
ChangeRoomState(MultiplayerRoomState.Playing);
|
||||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
|
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.Loaded))
|
||||||
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
|
ChangeUserState(u.UserID, MultiplayerUserState.Playing);
|
||||||
|
|
||||||
@ -82,6 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
case MultiplayerUserState.FinishedPlay:
|
case MultiplayerUserState.FinishedPlay:
|
||||||
if (Room.Users.All(u => u.State != MultiplayerUserState.Playing))
|
if (Room.Users.All(u => u.State != MultiplayerUserState.Playing))
|
||||||
{
|
{
|
||||||
|
ChangeRoomState(MultiplayerRoomState.Open);
|
||||||
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay))
|
foreach (var u in Room.Users.Where(u => u.State == MultiplayerUserState.FinishedPlay))
|
||||||
ChangeUserState(u.UserID, MultiplayerUserState.Results);
|
ChangeUserState(u.UserID, MultiplayerUserState.Results);
|
||||||
|
|
||||||
@ -173,6 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
|
ChangeRoomState(MultiplayerRoomState.WaitingForLoad);
|
||||||
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
||||||
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user