1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-04 03:53:21 +08:00

Merge pull request #20145 from smoogipoo/multiple-countdowns

Implement support for multiple active countdowns in multiplayer
This commit is contained in:
Dean Herbert 2022-09-08 19:15:30 +09:00 committed by GitHub
commit 13d9b2188b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 124 additions and 56 deletions

View File

@ -91,8 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
break; break;
case StopCountdownRequest: case StopCountdownRequest:
multiplayerRoom.Countdown = null; clearRoomCountdown();
raiseRoomUpdated();
break; break;
} }
}); });
@ -244,14 +243,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely()); AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely());
AddUntilStep("countdown started", () => multiplayerRoom.Countdown != null); AddUntilStep("countdown started", () => multiplayerRoom.ActiveCountdowns.Any());
AddStep("transfer host to local user", () => transferHost(localUser)); AddStep("transfer host to local user", () => transferHost(localUser));
AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true); AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true);
ClickButtonWhenEnabled<MultiplayerReadyButton>(); ClickButtonWhenEnabled<MultiplayerReadyButton>();
checkLocalUserState(MultiplayerUserState.Ready); checkLocalUserState(MultiplayerUserState.Ready);
AddAssert("countdown still active", () => multiplayerRoom.Countdown != null); AddAssert("countdown still active", () => multiplayerRoom.ActiveCountdowns.Any());
} }
[Test] [Test]
@ -392,7 +391,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void setRoomCountdown(TimeSpan duration) private void setRoomCountdown(TimeSpan duration)
{ {
multiplayerRoom.Countdown = new MatchStartCountdown { TimeRemaining = duration }; multiplayerRoom.ActiveCountdowns.Add(new MatchStartCountdown { TimeRemaining = duration });
raiseRoomUpdated();
}
private void clearRoomCountdown()
{
multiplayerRoom.ActiveCountdowns.Clear();
raiseRoomUpdated(); raiseRoomUpdated();
} }

View File

@ -1,20 +0,0 @@
// 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 MessagePack;
namespace osu.Game.Online.Multiplayer.Countdown
{
/// <summary>
/// Indicates a change to the <see cref="MultiplayerRoom"/>'s countdown.
/// </summary>
[MessagePackObject]
public class CountdownChangedEvent : MatchServerEvent
{
/// <summary>
/// The new countdown.
/// </summary>
[Key(0)]
public MultiplayerCountdown? Countdown { get; set; }
}
}

View File

@ -0,0 +1,28 @@
// 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 MessagePack;
using Newtonsoft.Json;
namespace osu.Game.Online.Multiplayer.Countdown
{
/// <summary>
/// Indicates that a countdown started in the <see cref="MultiplayerRoom"/>.
/// </summary>
[MessagePackObject]
public class CountdownStartedEvent : MatchServerEvent
{
/// <summary>
/// The countdown that was started.
/// </summary>
[Key(0)]
public readonly MultiplayerCountdown Countdown;
[JsonConstructor]
[SerializationConstructor]
public CountdownStartedEvent(MultiplayerCountdown countdown)
{
Countdown = countdown;
}
}
}

View File

@ -0,0 +1,28 @@
// 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 MessagePack;
using Newtonsoft.Json;
namespace osu.Game.Online.Multiplayer.Countdown
{
/// <summary>
/// Indicates that a countdown was stopped in the <see cref="MultiplayerRoom"/>.
/// </summary>
[MessagePackObject]
public class CountdownStoppedEvent : MatchServerEvent
{
/// <summary>
/// The identifier of the countdown that was stopped.
/// </summary>
[Key(0)]
public readonly int ID;
[JsonConstructor]
[SerializationConstructor]
public CountdownStoppedEvent(int id)
{
ID = id;
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using MessagePack; using MessagePack;
using Newtonsoft.Json;
namespace osu.Game.Online.Multiplayer.Countdown namespace osu.Game.Online.Multiplayer.Countdown
{ {
@ -11,5 +12,14 @@ namespace osu.Game.Online.Multiplayer.Countdown
[MessagePackObject] [MessagePackObject]
public class StopCountdownRequest : MatchUserRequest public class StopCountdownRequest : MatchUserRequest
{ {
[Key(0)]
public readonly int ID;
[JsonConstructor]
[SerializationConstructor]
public StopCountdownRequest(int id)
{
ID = id;
}
} }
} }

View File

@ -13,7 +13,7 @@ namespace osu.Game.Online.Multiplayer
/// and forcing progression of any clients that are blocking load due to user interaction. /// and forcing progression of any clients that are blocking load due to user interaction.
/// </summary> /// </summary>
[MessagePackObject] [MessagePackObject]
public class ForceGameplayStartCountdown : MultiplayerCountdown public sealed class ForceGameplayStartCountdown : MultiplayerCountdown
{ {
} }
} }

View File

@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer
[Serializable] [Serializable]
[MessagePackObject] [MessagePackObject]
// IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
[Union(0, typeof(CountdownChangedEvent))] [Union(0, typeof(CountdownStartedEvent))]
[Union(1, typeof(CountdownStoppedEvent))]
public abstract class MatchServerEvent public abstract class MatchServerEvent
{ {
} }

View File

@ -9,7 +9,7 @@ namespace osu.Game.Online.Multiplayer
/// A <see cref="MultiplayerCountdown"/> which will start the match after ending. /// A <see cref="MultiplayerCountdown"/> which will start the match after ending.
/// </summary> /// </summary>
[MessagePackObject] [MessagePackObject]
public class MatchStartCountdown : MultiplayerCountdown public sealed class MatchStartCountdown : MultiplayerCountdown
{ {
} }
} }

View File

@ -552,8 +552,14 @@ namespace osu.Game.Online.Multiplayer
switch (e) switch (e)
{ {
case CountdownChangedEvent countdownChangedEvent: case CountdownStartedEvent countdownStartedEvent:
Room.Countdown = countdownChangedEvent.Countdown; Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown);
break;
case CountdownStoppedEvent countdownStoppedEvent:
MultiplayerCountdown? countdown = Room.ActiveCountdowns.FirstOrDefault(countdown => countdown.ID == countdownStoppedEvent.ID);
if (countdown != null)
Room.ActiveCountdowns.Remove(countdown);
break; break;
} }

View File

@ -15,13 +15,24 @@ namespace osu.Game.Online.Multiplayer
[Union(1, typeof(ForceGameplayStartCountdown))] [Union(1, typeof(ForceGameplayStartCountdown))]
public abstract class MultiplayerCountdown public abstract class MultiplayerCountdown
{ {
/// <summary>
/// A unique identifier for this countdown.
/// </summary>
[Key(0)]
public int ID { get; set; }
/// <summary> /// <summary>
/// The amount of time remaining in the countdown. /// The amount of time remaining in the countdown.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This is only sent once from the server upon initial retrieval of the <see cref="MultiplayerRoom"/> or via a <see cref="CountdownChangedEvent"/>. /// This is only sent once from the server upon initial retrieval of the <see cref="MultiplayerRoom"/> or via a <see cref="CountdownStartedEvent"/>.
/// </remarks> /// </remarks>
[Key(0)] [Key(1)]
public TimeSpan TimeRemaining { get; set; } public TimeSpan TimeRemaining { get; set; }
/// <summary>
/// Whether only a single instance of this <see cref="MultiplayerCountdown"/> type may be active at any one time.
/// </summary>
public virtual bool IsExclusive => true;
} }
} }

View File

@ -53,10 +53,10 @@ namespace osu.Game.Online.Multiplayer
public IList<MultiplayerPlaylistItem> Playlist { get; set; } = new List<MultiplayerPlaylistItem>(); public IList<MultiplayerPlaylistItem> Playlist { get; set; } = new List<MultiplayerPlaylistItem>();
/// <summary> /// <summary>
/// The currently-running countdown. /// The currently running countdowns.
/// </summary> /// </summary>
[Key(7)] [Key(7)]
public MultiplayerCountdown? Countdown { get; set; } public IList<MultiplayerCountdown> ActiveCountdowns { get; set; } = new List<MultiplayerCountdown>();
[JsonConstructor] [JsonConstructor]
[SerializationConstructor] [SerializationConstructor]

View File

@ -23,7 +23,8 @@ namespace osu.Game.Online
(typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(ChangeTeamRequest), typeof(MatchUserRequest)),
(typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)),
(typeof(StopCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)),
(typeof(CountdownChangedEvent), typeof(MatchServerEvent)), (typeof(CountdownStartedEvent), typeof(MatchServerEvent)),
(typeof(CountdownStoppedEvent), typeof(MatchServerEvent)),
(typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)),
(typeof(TeamVersusUserState), typeof(MatchUserState)), (typeof(TeamVersusUserState), typeof(MatchUserState)),
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),

View File

@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
Debug.Assert(clickOperation == null); Debug.Assert(clickOperation == null);
clickOperation = ongoingOperationTracker.BeginOperation(); clickOperation = ongoingOperationTracker.BeginOperation();
if (isReady() && Client.IsHost && Room.Countdown == null) if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
startMatch(); startMatch();
else else
toggleReady(); toggleReady();
@ -140,10 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void cancelCountdown() private void cancelCountdown()
{ {
if (Client.Room == null)
return;
Debug.Assert(clickOperation == null); Debug.Assert(clickOperation == null);
clickOperation = ongoingOperationTracker.BeginOperation(); clickOperation = ongoingOperationTracker.BeginOperation();
Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation()); MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown);
Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation());
} }
private void endOperation() private void endOperation()
@ -192,7 +196,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
// When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready. // When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready.
if (localUser?.State == MultiplayerUserState.Spectating) if (localUser?.State == MultiplayerUserState.Spectating)
readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && Room.Countdown == null; readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown);
if (newCountReady == countReady) if (newCountReady == countReady)
return; return;

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System; using System;
using System.Linq;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions; using osu.Framework.Extensions;
@ -79,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void onRoomUpdated() => Scheduler.AddOnce(() => private void onRoomUpdated() => Scheduler.AddOnce(() =>
{ {
bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown; bool countdownActive = multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true;
if (countdownActive) if (countdownActive)
{ {
@ -121,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
}); });
} }
if (multiplayerClient.Room?.Countdown != null && multiplayerClient.IsHost) if (multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true && multiplayerClient.IsHost)
{ {
flow.Add(new OsuButton flow.Add(new OsuButton
{ {

View File

@ -57,23 +57,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
private void onRoomUpdated() => Scheduler.AddOnce(() => private void onRoomUpdated() => Scheduler.AddOnce(() =>
{ {
MultiplayerCountdown newCountdown; MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown);
switch (room?.Countdown)
{
case MatchStartCountdown:
newCountdown = room.Countdown;
break;
// Clear the countdown with any other (including non-null) countdown values.
default:
newCountdown = null;
break;
}
if (newCountdown != countdown) if (newCountdown != countdown)
{ {
countdown = room?.Countdown; countdown = newCountdown;
countdownChangeTime = Time.Current; countdownChangeTime = Time.Current;
} }
@ -213,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
case MultiplayerUserState.Spectating: case MultiplayerUserState.Spectating:
case MultiplayerUserState.Ready: case MultiplayerUserState.Ready:
if (room?.Host?.Equals(localUser) == true && room.Countdown == null) if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown))
setGreen(); setGreen();
else else
setYellow(); setYellow();
@ -248,8 +236,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
{ {
get get
{ {
if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready && !room.Settings.AutoStartEnabled) if (room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true
&& multiplayerClient.IsHost
&& multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready
&& !room.Settings.AutoStartEnabled)
{
return "Cancel countdown"; return "Cancel countdown";
}
return base.TooltipText; return base.TooltipText;
} }