mirror of
https://github.com/osukey/osukey.git
synced 2025-08-04 07:06:35 +09:00
Merge pull request #12321 from smoogipoo/add-spectate-button-and-state
Add multiplayer spectating user state and button
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
/// </summary>
|
||||
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 ChangeSettings(MultiplayerRoomSettings settings);
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
@ -103,7 +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 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)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
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.
|
||||
|
@ -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.Binoculars;
|
||||
icon.Colour = colours.BlueLight;
|
||||
break;
|
||||
|
||||
default:
|
||||
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)
|
||||
{
|
||||
Debug.Assert(Room != null);
|
||||
@ -71,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);
|
||||
|
||||
@ -82,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);
|
||||
|
||||
@ -173,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);
|
||||
|
||||
|
Reference in New Issue
Block a user