mirror of
https://github.com/ppy/osu.git
synced 2024-11-12 08:27:26 +08:00
Merge branch 'master' into fix-catch-clamp
This commit is contained in:
commit
32e4cf6ccb
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -10,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
@ -424,6 +426,14 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddRepeatStep("send barrage", sendBarrage, 10);
|
AddRepeatStep("send barrage", sendBarrage, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestServerShuttingDownNotification()
|
||||||
|
{
|
||||||
|
AddStep("post with 5 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(5))));
|
||||||
|
AddStep("post with 30 seconds", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromSeconds(30))));
|
||||||
|
AddStep("post with 6 hours", () => notificationOverlay.Post(new ServerShutdownNotification(TimeSpan.FromHours(6))));
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Multiplayer.Countdown;
|
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.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
@ -26,6 +27,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer
|
public abstract class MultiplayerClient : Component, IMultiplayerClient, IMultiplayerRoomServer
|
||||||
{
|
{
|
||||||
|
public Action<Notification>? PostNotification { protected get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when any change occurs to the multiplayer room.
|
/// Invoked when any change occurs to the multiplayer room.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -207,6 +210,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
updateLocalRoomSettings(joinedRoom.Settings);
|
updateLocalRoomSettings(joinedRoom.Settings);
|
||||||
|
|
||||||
|
postServerShuttingDownNotification();
|
||||||
|
|
||||||
OnRoomJoined();
|
OnRoomJoined();
|
||||||
}, cancellationSource.Token).ConfigureAwait(false);
|
}, cancellationSource.Token).ConfigureAwait(false);
|
||||||
}, cancellationSource.Token).ConfigureAwait(false);
|
}, cancellationSource.Token).ConfigureAwait(false);
|
||||||
@ -554,6 +559,14 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
case CountdownStartedEvent countdownStartedEvent:
|
case CountdownStartedEvent countdownStartedEvent:
|
||||||
Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown);
|
Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown);
|
||||||
|
|
||||||
|
switch (countdownStartedEvent.Countdown)
|
||||||
|
{
|
||||||
|
case ServerShuttingDownCountdown:
|
||||||
|
postServerShuttingDownNotification();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CountdownStoppedEvent countdownStoppedEvent:
|
case CountdownStoppedEvent countdownStoppedEvent:
|
||||||
@ -569,6 +582,16 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void postServerShuttingDownNotification()
|
||||||
|
{
|
||||||
|
ServerShuttingDownCountdown? countdown = room?.ActiveCountdowns.OfType<ServerShuttingDownCountdown>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (countdown == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PostNotification?.Invoke(new ServerShutdownNotification(countdown.TimeRemaining));
|
||||||
|
}
|
||||||
|
|
||||||
Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability)
|
Task IMultiplayerClient.UserBeatmapAvailabilityChanged(int userId, BeatmapAvailability beatmapAvailability)
|
||||||
{
|
{
|
||||||
Scheduler.Add(() =>
|
Scheduler.Add(() =>
|
||||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
[Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
[Union(0, typeof(MatchStartCountdown))] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types.
|
||||||
[Union(1, typeof(ForceGameplayStartCountdown))]
|
[Union(1, typeof(ForceGameplayStartCountdown))]
|
||||||
|
[Union(2, typeof(ServerShuttingDownCountdown))]
|
||||||
public abstract class MultiplayerCountdown
|
public abstract class MultiplayerCountdown
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
62
osu.Game/Online/Multiplayer/ServerShutdownNotification.cs
Normal file
62
osu.Game/Online/Multiplayer/ServerShutdownNotification.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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 Humanizer.Localisation;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Multiplayer
|
||||||
|
{
|
||||||
|
public class ServerShutdownNotification : SimpleNotification
|
||||||
|
{
|
||||||
|
private readonly DateTimeOffset endDate;
|
||||||
|
private ScheduledDelegate? updateDelegate;
|
||||||
|
|
||||||
|
public ServerShutdownNotification(TimeSpan duration)
|
||||||
|
{
|
||||||
|
endDate = DateTimeOffset.UtcNow + duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
updateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateDelegate = Scheduler.Add(updateTimeWithReschedule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimeWithReschedule()
|
||||||
|
{
|
||||||
|
updateTime();
|
||||||
|
|
||||||
|
// The remaining time on a countdown may be at a fractional portion between two seconds.
|
||||||
|
// We want to align certain audio/visual cues to the point at which integer seconds change.
|
||||||
|
// To do so, we schedule to the next whole second. Note that scheduler invocation isn't
|
||||||
|
// guaranteed to be accurate, so this may still occur slightly late, but even in such a case
|
||||||
|
// the next invocation will be roughly correct.
|
||||||
|
double timeToNextSecond = endDate.Subtract(DateTimeOffset.UtcNow).TotalMilliseconds % 1000;
|
||||||
|
|
||||||
|
updateDelegate = Scheduler.AddDelayed(updateTimeWithReschedule, timeToNextSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTime()
|
||||||
|
{
|
||||||
|
TimeSpan remaining = endDate.Subtract(DateTimeOffset.Now);
|
||||||
|
|
||||||
|
if (remaining.TotalSeconds <= 5)
|
||||||
|
{
|
||||||
|
updateDelegate?.Cancel();
|
||||||
|
Text = "The multiplayer server will be right back...";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Text = $"The multiplayer server is restarting in {HumanizerUtils.Humanize(remaining, precision: 3, minUnit: TimeUnit.Second)}.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs
Normal file
15
osu.Game/Online/Multiplayer/ServerShuttingDownCountdown.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A countdown that indicates the current multiplayer server is shutting down.
|
||||||
|
/// </summary>
|
||||||
|
[MessagePackObject]
|
||||||
|
public class ServerShuttingDownCountdown : MultiplayerCountdown
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,8 @@ namespace osu.Game.Online
|
|||||||
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
|
(typeof(TeamVersusRoomState), typeof(MatchRoomState)),
|
||||||
(typeof(TeamVersusUserState), typeof(MatchUserState)),
|
(typeof(TeamVersusUserState), typeof(MatchUserState)),
|
||||||
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
|
(typeof(MatchStartCountdown), typeof(MultiplayerCountdown)),
|
||||||
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown))
|
(typeof(ForceGameplayStartCountdown), typeof(MultiplayerCountdown)),
|
||||||
|
(typeof(ServerShuttingDownCountdown), typeof(MultiplayerCountdown)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,6 +716,8 @@ namespace osu.Game
|
|||||||
ScoreManager.PostNotification = n => Notifications.Post(n);
|
ScoreManager.PostNotification = n => Notifications.Post(n);
|
||||||
ScoreManager.PresentImport = items => PresentScore(items.First().Value);
|
ScoreManager.PresentImport = items => PresentScore(items.First().Value);
|
||||||
|
|
||||||
|
MultiplayerClient.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
// make config aware of how to lookup skins for on-screen display purposes.
|
// make config aware of how to lookup skins for on-screen display purposes.
|
||||||
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
|
||||||
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
LocalConfig.LookupSkinName = id => SkinManager.Query(s => s.ID == id)?.ToString() ?? "Unknown";
|
||||||
|
@ -179,7 +179,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
private SpectatorClient spectatorClient;
|
private SpectatorClient spectatorClient;
|
||||||
|
|
||||||
private MultiplayerClient multiplayerClient;
|
protected MultiplayerClient MultiplayerClient { get; private set; }
|
||||||
|
|
||||||
private MetadataClient metadataClient;
|
private MetadataClient metadataClient;
|
||||||
|
|
||||||
@ -284,7 +284,7 @@ namespace osu.Game
|
|||||||
// TODO: OsuGame or OsuGameBase?
|
// TODO: OsuGame or OsuGameBase?
|
||||||
dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage));
|
dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage));
|
||||||
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints));
|
||||||
dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));
|
dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints));
|
||||||
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints));
|
||||||
|
|
||||||
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
|
AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient));
|
||||||
@ -329,7 +329,7 @@ namespace osu.Game
|
|||||||
AddInternal(apiAccess);
|
AddInternal(apiAccess);
|
||||||
|
|
||||||
AddInternal(spectatorClient);
|
AddInternal(spectatorClient);
|
||||||
AddInternal(multiplayerClient);
|
AddInternal(MultiplayerClient);
|
||||||
AddInternal(metadataClient);
|
AddInternal(metadataClient);
|
||||||
|
|
||||||
AddInternal(rulesetConfigCache);
|
AddInternal(rulesetConfigCache);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using Humanizer.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Utils
|
namespace osu.Game.Utils
|
||||||
{
|
{
|
||||||
@ -26,5 +27,27 @@ namespace osu.Game.Utils
|
|||||||
return input.Humanize(culture: new CultureInfo("en-US"));
|
return input.Humanize(culture: new CultureInfo("en-US"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns the current or provided timespan into a human readable sentence
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The date to be humanized</param>
|
||||||
|
/// <param name="precision">The maximum number of time units to return. Defaulted is 1 which means the largest unit is returned</param>
|
||||||
|
/// <param name="maxUnit">The maximum unit of time to output. The default value is <see cref="TimeUnit.Week"/>. The time units <see cref="TimeUnit.Month"/> and <see cref="TimeUnit.Year"/> will give approximations for time spans bigger 30 days by calculating with 365.2425 days a year and 30.4369 days a month.</param>
|
||||||
|
/// <param name="minUnit">The minimum unit of time to output.</param>
|
||||||
|
/// <param name="toWords">Uses words instead of numbers if true. E.g. one day.</param>
|
||||||
|
/// <returns>distance of time in words</returns>
|
||||||
|
public static string Humanize(TimeSpan input, int precision = 1, TimeUnit maxUnit = TimeUnit.Week, TimeUnit minUnit = TimeUnit.Millisecond, bool toWords = false)
|
||||||
|
{
|
||||||
|
// this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return input.Humanize(precision: precision, maxUnit: maxUnit, minUnit: minUnit);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
return input.Humanize(culture: new CultureInfo("en-US"), precision: precision, maxUnit: maxUnit, minUnit: minUnit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user