mirror of
https://github.com/osukey/osukey.git
synced 2025-07-02 08:49:59 +09:00
Add support for starting/stopping countdowns
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -16,11 +17,13 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
@ -68,6 +71,139 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStartWithCountdown()
|
||||||
|
{
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.CountdownButton>();
|
||||||
|
AddStep("click the first countdown button", () =>
|
||||||
|
{
|
||||||
|
var popoverButton = this.ChildrenOfType<MultiplayerReadyButton.CountdownButton.PopoverButton>().First();
|
||||||
|
InputManager.MoveMouseTo(popoverButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("countdown button not visible", () => !this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().Single().IsPresent);
|
||||||
|
AddStep("finish countdown", () => MultiplayerClient.FinishCountDown());
|
||||||
|
AddUntilStep("match started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCancelCountdown()
|
||||||
|
{
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.CountdownButton>();
|
||||||
|
AddStep("click the first countdown button", () =>
|
||||||
|
{
|
||||||
|
var popoverButton = this.ChildrenOfType<MultiplayerReadyButton.CountdownButton.PopoverButton>().First();
|
||||||
|
InputManager.MoveMouseTo(popoverButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
|
||||||
|
AddStep("finish countdown", () => MultiplayerClient.FinishCountDown());
|
||||||
|
AddUntilStep("match not started", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReadyAndUnReadyDuringCountdown()
|
||||||
|
{
|
||||||
|
AddStep("add second user as host", () =>
|
||||||
|
{
|
||||||
|
MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" });
|
||||||
|
MultiplayerClient.TransferHost(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start with countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(2) }));
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("user is ready", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Ready);
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("user is idle", () => MultiplayerClient.Room?.Users[0].State == MultiplayerUserState.Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCountdownButtonEnablementAndVisibilityWhileSpectating()
|
||||||
|
{
|
||||||
|
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||||
|
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
|
AddAssert("countdown button is visible", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().Single().IsPresent);
|
||||||
|
AddAssert("countdown button disabled", () => !this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }));
|
||||||
|
AddAssert("countdown button disabled", () => !this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready));
|
||||||
|
AddAssert("countdown button enabled", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().Single().Enabled.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpectatingDuringCountdownWithNoReadyUsersCancelsCountdown()
|
||||||
|
{
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.CountdownButton>();
|
||||||
|
AddStep("click the first countdown button", () =>
|
||||||
|
{
|
||||||
|
var popoverButton = this.ChildrenOfType<MultiplayerReadyButton.CountdownButton.PopoverButton>().First();
|
||||||
|
InputManager.MoveMouseTo(popoverButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||||
|
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
|
AddStep("finish countdown", () => MultiplayerClient.FinishCountDown());
|
||||||
|
AddUntilStep("match not started", () => MultiplayerClient.Room?.State == MultiplayerRoomState.Open);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReadyButtonEnabledWhileSpectatingDuringCountdown()
|
||||||
|
{
|
||||||
|
AddStep("add second user", () => MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" }));
|
||||||
|
AddStep("set second user ready", () => MultiplayerClient.ChangeUserState(2, MultiplayerUserState.Ready));
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("countdown button shown", () => this.ChildrenOfType<MultiplayerReadyButton.CountdownButton>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.CountdownButton>();
|
||||||
|
AddStep("click the first countdown button", () =>
|
||||||
|
{
|
||||||
|
var popoverButton = this.ChildrenOfType<MultiplayerReadyButton.CountdownButton.PopoverButton>().First();
|
||||||
|
InputManager.MoveMouseTo(popoverButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set spectating", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Spectating));
|
||||||
|
AddUntilStep("local user is spectating", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
|
AddAssert("ready button enabled", () => this.ChildrenOfType<MultiplayerReadyButton.ReadyButton>().Single().Enabled.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBecomeHostDuringCountdownAndReady()
|
||||||
|
{
|
||||||
|
AddStep("add second user as host", () =>
|
||||||
|
{
|
||||||
|
MultiplayerClient.AddUser(new APIUser { Id = 2, Username = "Another user" });
|
||||||
|
MultiplayerClient.TransferHost(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("start countdown", () => MultiplayerClient.SendMatchRequest(new MatchStartCountdownRequest { Delay = TimeSpan.FromMinutes(1) }));
|
||||||
|
AddUntilStep("countdown started", () => MultiplayerClient.Room?.Countdown != null);
|
||||||
|
|
||||||
|
AddStep("transfer host to local user", () => MultiplayerClient.TransferHost(API.LocalUser.Value.OnlineID));
|
||||||
|
AddUntilStep("local user is host", () => MultiplayerClient.Room?.Host?.Equals(MultiplayerClient.LocalUser) == true);
|
||||||
|
|
||||||
|
ClickButtonWhenEnabled<MultiplayerReadyButton.ReadyButton>();
|
||||||
|
AddUntilStep("local user became ready", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Ready);
|
||||||
|
AddAssert("countdown still active", () => MultiplayerClient.Room?.Countdown != null);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDeletedBeatmapDisableReady()
|
public void TestDeletedBeatmapDisableReady()
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Online.Rooms.RoomStatuses;
|
using osu.Game.Online.Rooms.RoomStatuses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -534,7 +535,24 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
public Task MatchEvent(MatchServerEvent e)
|
public Task MatchEvent(MatchServerEvent e)
|
||||||
{
|
{
|
||||||
// not used by any match types just yet.
|
if (Room == null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
if (Room == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (e)
|
||||||
|
{
|
||||||
|
case CountdownChangedEvent countdownChangedEvent:
|
||||||
|
Room.Countdown = countdownChangedEvent.Countdown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomUpdated?.Invoke();
|
||||||
|
}, false);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,20 +14,18 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
public abstract class ReadyButton : TriangleButton, IHasTooltip
|
public abstract class ReadyButton : TriangleButton, IHasTooltip
|
||||||
{
|
{
|
||||||
public new readonly BindableBool Enabled = new BindableBool();
|
public new readonly BindableBool Enabled = new BindableBool();
|
||||||
|
protected readonly IBindable<BeatmapAvailability> Availability = new Bindable<BeatmapAvailability>();
|
||||||
private IBindable<BeatmapAvailability> availability;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker)
|
private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker)
|
||||||
{
|
{
|
||||||
availability = beatmapTracker.Availability.GetBoundCopy();
|
Availability.BindTo(beatmapTracker.Availability);
|
||||||
|
Availability.BindValueChanged(_ => updateState());
|
||||||
availability.BindValueChanged(_ => updateState());
|
|
||||||
Enabled.BindValueChanged(_ => updateState(), true);
|
Enabled.BindValueChanged(_ => updateState(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateState() =>
|
private void updateState() =>
|
||||||
base.Enabled.Value = availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
|
base.Enabled.Value = Availability.Value.State == DownloadState.LocallyAvailable && Enabled.Value;
|
||||||
|
|
||||||
public virtual LocalisableString TooltipText
|
public virtual LocalisableString TooltipText
|
||||||
{
|
{
|
||||||
@ -36,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
if (Enabled.Value)
|
if (Enabled.Value)
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
if (availability.Value.State != DownloadState.LocallyAvailable)
|
if (Availability.Value.State != DownloadState.LocallyAvailable)
|
||||||
return "Beatmap not downloaded";
|
return "Beatmap not downloaded";
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
@ -17,12 +17,14 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||||
@ -124,6 +126,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local user is the room host and is in a ready state.
|
||||||
|
// The only action they can take is to stop a countdown if one's currently running.
|
||||||
|
if (Room.Countdown != null)
|
||||||
|
{
|
||||||
|
stopCountdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// And if a countdown isn't running, start the match.
|
// And if a countdown isn't running, start the match.
|
||||||
startMatch();
|
startMatch();
|
||||||
|
|
||||||
@ -131,6 +141,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
|
void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation());
|
||||||
|
|
||||||
|
void stopCountdown() => Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation());
|
||||||
|
|
||||||
void startMatch() => Client.StartMatch().ContinueWith(t =>
|
void startMatch() => Client.StartMatch().ContinueWith(t =>
|
||||||
{
|
{
|
||||||
// accessing Exception here silences any potential errors from the antecedent task
|
// accessing Exception here silences any potential errors from the antecedent task
|
||||||
@ -146,6 +158,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
private void startCountdown(TimeSpan duration)
|
private void startCountdown(TimeSpan duration)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(clickOperation == null);
|
||||||
|
clickOperation = ongoingOperationTracker.BeginOperation();
|
||||||
|
|
||||||
|
Client.SendMatchRequest(new MatchStartCountdownRequest { Delay = duration }).ContinueWith(_ => endOperation());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endOperation()
|
private void endOperation()
|
||||||
@ -167,16 +183,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
int newCountReady = Room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
||||||
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
int newCountTotal = Room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
switch (localUser?.State)
|
if (Room.Countdown != null)
|
||||||
|
countdownButton.Alpha = 0;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
default:
|
switch (localUser?.State)
|
||||||
countdownButton.Alpha = 0;
|
{
|
||||||
break;
|
default:
|
||||||
|
countdownButton.Alpha = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0;
|
countdownButton.Alpha = Room.Host?.Equals(localUser) == true ? 1 : 0;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled.Value =
|
enabled.Value =
|
||||||
@ -232,6 +253,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
onRoomUpdated();
|
onRoomUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (room?.Countdown != null)
|
||||||
|
{
|
||||||
|
// Update the countdown timer.
|
||||||
|
onRoomUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onRoomUpdated()
|
private void onRoomUpdated()
|
||||||
{
|
{
|
||||||
updateButtonText();
|
updateButtonText();
|
||||||
@ -251,21 +283,39 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
int countReady = room.Users.Count(u => u.State == MultiplayerUserState.Ready);
|
||||||
int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
int countTotal = room.Users.Count(u => u.State != MultiplayerUserState.Spectating);
|
||||||
|
|
||||||
|
string countdownText = room.Countdown == null ? string.Empty : $"Starting in {room.Countdown.EndTime - DateTimeOffset.Now:mm\\:ss}";
|
||||||
string countText = $"({countReady} / {countTotal} ready)";
|
string countText = $"({countReady} / {countTotal} ready)";
|
||||||
|
|
||||||
switch (localUser?.State)
|
if (room.Countdown != null)
|
||||||
{
|
{
|
||||||
default:
|
switch (localUser?.State)
|
||||||
Text = "Ready";
|
{
|
||||||
break;
|
default:
|
||||||
|
Text = $"Ready ({countdownText.ToLowerInvariant()})";
|
||||||
|
break;
|
||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
case MultiplayerUserState.Ready:
|
case MultiplayerUserState.Ready:
|
||||||
Text = room.Host?.Equals(localUser) == true
|
Text = $"{countdownText} {countText}";
|
||||||
? $"Start match {countText}"
|
break;
|
||||||
: $"Waiting for host... {countText}";
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (localUser?.State)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
Text = "Ready";
|
||||||
|
break;
|
||||||
|
|
||||||
break;
|
case MultiplayerUserState.Spectating:
|
||||||
|
case MultiplayerUserState.Ready:
|
||||||
|
Text = room.Host?.Equals(localUser) == true
|
||||||
|
? $"Start match {countText}"
|
||||||
|
: $"Waiting for host... {countText}";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,20 +329,37 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
|
|
||||||
var localUser = multiplayerClient.LocalUser;
|
var localUser = multiplayerClient.LocalUser;
|
||||||
|
|
||||||
switch (localUser?.State)
|
if (room.Countdown != null)
|
||||||
{
|
{
|
||||||
default:
|
switch (localUser?.State)
|
||||||
setGreen();
|
{
|
||||||
break;
|
default:
|
||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
|
||||||
case MultiplayerUserState.Ready:
|
|
||||||
if (room?.Host?.Equals(localUser) == true)
|
|
||||||
setGreen();
|
setGreen();
|
||||||
else
|
break;
|
||||||
setYellow();
|
|
||||||
|
|
||||||
break;
|
case MultiplayerUserState.Spectating:
|
||||||
|
case MultiplayerUserState.Ready:
|
||||||
|
setYellow();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (localUser?.State)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
setGreen();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultiplayerUserState.Spectating:
|
||||||
|
case MultiplayerUserState.Ready:
|
||||||
|
if (room?.Host?.Equals(localUser) == true)
|
||||||
|
setGreen();
|
||||||
|
else
|
||||||
|
setYellow();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setYellow()
|
void setYellow()
|
||||||
@ -317,6 +384,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
if (multiplayerClient != null)
|
if (multiplayerClient != null)
|
||||||
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready)
|
||||||
|
return "Cancel countdown";
|
||||||
|
|
||||||
|
return base.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CountdownButton : IconButton, IHasPopover
|
public class CountdownButton : IconButton, IHasPopover
|
||||||
|
@ -7,12 +7,14 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Multiplayer.Countdown;
|
||||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -114,12 +116,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public void ChangeUserState(int userId, MultiplayerUserState newState)
|
public void ChangeUserState(int userId, MultiplayerUserState newState)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
((IMultiplayerClient)this).UserStateChanged(userId, newState);
|
((IMultiplayerClient)this).UserStateChanged(userId, newState);
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
switch (Room.State)
|
switch (Room.State)
|
||||||
{
|
{
|
||||||
|
case MultiplayerRoomState.Open:
|
||||||
|
// If there are no remaining ready users or the host is not ready, stop any existing countdown.
|
||||||
|
// Todo: When we have an "automatic start" mode, this should also start a new countdown if any users _are_ ready.
|
||||||
|
// Todo: This doesn't yet support non-match-start countdowns.
|
||||||
|
bool shouldStopCountdown = Room.Users.All(u => u.State != MultiplayerUserState.Ready);
|
||||||
|
shouldStopCountdown |= Room.Host?.State != MultiplayerUserState.Ready && Room.Host?.State != MultiplayerUserState.Spectating;
|
||||||
|
|
||||||
|
if (shouldStopCountdown)
|
||||||
|
countdownStopSource?.Cancel();
|
||||||
|
break;
|
||||||
|
|
||||||
case MultiplayerRoomState.WaitingForLoad:
|
case MultiplayerRoomState.WaitingForLoad:
|
||||||
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
if (Room.Users.All(u => u.State != MultiplayerUserState.WaitingForLoad))
|
||||||
{
|
{
|
||||||
@ -282,6 +296,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource? countdownFinishSource;
|
||||||
|
private CancellationTokenSource? countdownStopSource;
|
||||||
|
private Task countdownTask = Task.CompletedTask;
|
||||||
|
|
||||||
|
public void FinishCountDown() => countdownFinishSource?.Cancel();
|
||||||
|
|
||||||
public override async Task SendMatchRequest(MatchUserRequest request)
|
public override async Task SendMatchRequest(MatchUserRequest request)
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
@ -289,6 +309,71 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
switch (request)
|
switch (request)
|
||||||
{
|
{
|
||||||
|
case MatchStartCountdownRequest matchCountdownRequest:
|
||||||
|
countdownStopSource?.Cancel();
|
||||||
|
|
||||||
|
var stopSource = countdownStopSource = new CancellationTokenSource();
|
||||||
|
var finishSource = countdownFinishSource = new CancellationTokenSource();
|
||||||
|
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(stopSource.Token, finishSource.Token);
|
||||||
|
var countdown = new MatchStartCountdown { EndTime = DateTimeOffset.Now + matchCountdownRequest.Delay };
|
||||||
|
|
||||||
|
Task lastCountdownTask = countdownTask;
|
||||||
|
countdownTask = start();
|
||||||
|
|
||||||
|
async Task start()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await lastCountdownTask;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (stopSource.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Room.Countdown = countdown;
|
||||||
|
MatchEvent(new CountdownChangedEvent { Countdown = countdown });
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(matchCountdownRequest.Delay, cancellationSource.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (Room.Countdown != countdown)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Room.Countdown = null;
|
||||||
|
MatchEvent(new CountdownChangedEvent { Countdown = null });
|
||||||
|
|
||||||
|
using (cancellationSource)
|
||||||
|
{
|
||||||
|
if (stopSource.Token.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartMatch().WaitSafely();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopCountdownRequest _:
|
||||||
|
countdownStopSource?.Cancel();
|
||||||
|
|
||||||
|
Room.Countdown = null;
|
||||||
|
await MatchEvent(new CountdownChangedEvent { Countdown = Room.Countdown });
|
||||||
|
break;
|
||||||
|
|
||||||
case ChangeTeamRequest changeTeam:
|
case ChangeTeamRequest changeTeam:
|
||||||
|
|
||||||
TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!;
|
TeamVersusRoomState roomState = (TeamVersusRoomState)Room.MatchState!;
|
||||||
@ -307,7 +392,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task StartMatch()
|
public override async Task StartMatch()
|
||||||
{
|
{
|
||||||
Debug.Assert(Room != null);
|
Debug.Assert(Room != null);
|
||||||
|
|
||||||
@ -315,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
foreach (var user in Room.Users.Where(u => u.State == MultiplayerUserState.Ready))
|
||||||
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
ChangeUserState(user.UserID, MultiplayerUserState.WaitingForLoad);
|
||||||
|
|
||||||
return ((IMultiplayerClient)this).LoadRequested();
|
await ((IMultiplayerClient)this).LoadRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task AbortGameplay()
|
public override Task AbortGameplay()
|
||||||
|
Reference in New Issue
Block a user