diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs new file mode 100644 index 0000000000..3ca3b0c6a7 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading.Tasks; +using Content.Server.GameTicking; +using Content.Server.GameTicking.GameRules; +using Content.Server.Interfaces.GameTicking; +using NUnit.Framework; +using Robust.Shared.Interfaces.Timing; + +namespace Content.IntegrationTests.Tests.GameRules +{ + [TestFixture] + [TestOf(typeof(RuleMaxTimeRestart))] + public class RuleMaxTimeRestartTest : ContentIntegrationTest + { + [Test] + public async Task RestartTest() + { + var options = new ServerContentIntegrationOption + { + CVarOverrides = + { + ["game.lobbyenabled"] = "true" + } + }; + var server = StartServer(options); + + await server.WaitIdleAsync(); + + var sGameTicker = server.ResolveDependency(); + var sGameTiming = server.ResolveDependency(); + + RuleMaxTimeRestart maxTimeRule = null; + + await server.WaitAssertion(() => + { + Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + + maxTimeRule = sGameTicker.AddGameRule(); + maxTimeRule.RoundMaxTime = TimeSpan.FromSeconds(3); + + sGameTicker.StartRound(); + }); + + await server.WaitAssertion(() => + { + Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + }); + + var ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeRule.RoundMaxTime.TotalSeconds * 1.1f); + await server.WaitRunTicks(ticks); + + await server.WaitAssertion(() => + { + Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PostRound)); + }); + + ticks = sGameTiming.TickRate * (int) Math.Ceiling(maxTimeRule.RoundEndDelay.TotalSeconds * 1.1f); + await server.WaitRunTicks(ticks); + + await server.WaitAssertion(() => + { + Assert.That(sGameTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + }); + } + } +} diff --git a/Content.Server/GameTicking/GameRules/RuleMaxTimeRestart.cs b/Content.Server/GameTicking/GameRules/RuleMaxTimeRestart.cs new file mode 100644 index 0000000000..93d2b0dffc --- /dev/null +++ b/Content.Server/GameTicking/GameRules/RuleMaxTimeRestart.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameTicking; +using Robust.Shared.IoC; +using Robust.Shared.Localization; +using Timer = Robust.Shared.Timers.Timer; + +namespace Content.Server.GameTicking.GameRules +{ + public sealed class RuleMaxTimeRestart : GameRule + { + [Dependency] private readonly IGameTicker _gameTicker = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + + private CancellationTokenSource _timerCancel = new(); + + public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromMinutes(5); + public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10); + + public override void Added() + { + base.Added(); + + _gameTicker.OnRunLevelChanged += RunLevelChanged; + } + + public override void Removed() + { + base.Removed(); + + _gameTicker.OnRunLevelChanged -= RunLevelChanged; + StopTimer(); + } + + public void RestartTimer() + { + _timerCancel.Cancel(); + _timerCancel = new CancellationTokenSource(); + Timer.Spawn(RoundMaxTime, TimerFired, _timerCancel.Token); + } + + public void StopTimer() + { + _timerCancel.Cancel(); + } + + private void TimerFired() + { + _gameTicker.EndRound("Time has run out!"); + + _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds)); + + Timer.Spawn(RoundEndDelay, () => _gameTicker.RestartRound()); + } + + private void RunLevelChanged(GameRunLevelChangedEventArgs args) + { + switch (args.NewRunLevel) + { + case GameRunLevel.InRound: + RestartTimer(); + break; + case GameRunLevel.PreRoundLobby: + case GameRunLevel.PostRound: + StopTimer(); + break; + } + } + } +}