diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml
new file mode 100644
index 0000000000..316c79ddf1
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml.cs
new file mode 100644
index 0000000000..be81895f4d
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminShuttleWindow.xaml.cs
@@ -0,0 +1,31 @@
+using System;
+using Content.Shared.Localizations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+
+namespace Content.Client.Administration.UI.Tabs.AdminTab
+{
+ [GenerateTypedNameReferences]
+ public partial class AdminShuttleWindow : SS14Window
+ {
+ public AdminShuttleWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _callShuttleTime.OnTextChanged += CallShuttleTimeOnOnTextChanged;
+ }
+
+ private void CallShuttleTimeOnOnTextChanged(LineEdit.LineEditEventArgs obj)
+ {
+ var loc = IoCManager.Resolve();
+ _callShuttleButton.Disabled = !TimeSpan.TryParseExact(obj.Text, Localization.TimeSpanMinutesFormats, loc.DefaultCulture, out _);
+ _callShuttleButton.Command = $"callshuttle {obj.Text}";
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
index da899fe580..c6660a64e8 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
@@ -6,13 +6,14 @@
Margin="4"
MinSize="50 50">
-
+
+
diff --git a/Content.Server/Administration/Commands/ShuttleCommands.cs b/Content.Server/Administration/Commands/ShuttleCommands.cs
new file mode 100644
index 0000000000..6e409eced1
--- /dev/null
+++ b/Content.Server/Administration/Commands/ShuttleCommands.cs
@@ -0,0 +1,51 @@
+using System;
+using Content.Server.RoundEnd;
+using Content.Shared.Administration;
+using Content.Shared.Localizations;
+using Robust.Shared.Console;
+using Robust.Shared.GameObjects;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+
+namespace Content.Server.Administration.Commands
+{
+ [AdminCommand(AdminFlags.Server)]
+ public class CallShuttleCommand : IConsoleCommand
+ {
+ public string Command => "callshuttle";
+ public string Description => Loc.GetString("call-shuttle-command-description");
+ public string Help => Loc.GetString("call-shuttle-command-help-text", ("command",Command));
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var loc = IoCManager.Resolve();
+
+ // ReSharper disable once ConvertIfStatementToSwitchStatement
+ if (args.Length == 1 && TimeSpan.TryParseExact(args[0], Localization.TimeSpanMinutesFormats, loc.DefaultCulture, out var timeSpan))
+ {
+ EntitySystem.Get().RequestRoundEnd(timeSpan, false);
+ }
+ else if (args.Length == 1)
+ {
+ shell.WriteLine(Loc.GetString("shell-timespan-minutes-must-be-correct"));
+ }
+ else
+ {
+ EntitySystem.Get().RequestRoundEnd(false);
+ }
+ }
+ }
+
+ [AdminCommand(AdminFlags.Server)]
+ public class RecallShuttleCommand : IConsoleCommand
+ {
+ public string Command => "recallshuttle";
+ public string Description => Loc.GetString("recall-shuttle-command-description");
+ public string Help => Loc.GetString("recall-shuttle-command-help-text", ("command",Command));
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ EntitySystem.Get().CancelRoundEndCountdown(false);
+ }
+ }
+}
diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs
index c1164ac85f..1486c1779a 100644
--- a/Content.Server/RoundEnd/RoundEndSystem.cs
+++ b/Content.Server/RoundEnd/RoundEndSystem.cs
@@ -74,36 +74,41 @@ namespace Content.Server.RoundEnd
Timer.Spawn(CallCooldown, () => OnCallCooldownEnded?.Invoke(), _callCooldownEndedTokenSource.Token);
}
- public void RequestRoundEnd()
+ public void RequestRoundEnd(bool checkCooldown = true)
+ {
+ RequestRoundEnd(RoundEndCountdownTime, checkCooldown);
+ }
+
+ public void RequestRoundEnd(TimeSpan countdownTime, bool checkCooldown = true)
{
if (IsRoundEndCountdownStarted)
return;
- if (!CanCall())
+ if (checkCooldown && !CanCall())
{
return;
}
IsRoundEndCountdownStarted = true;
- _chatManager.DispatchStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",("minutes", RoundEndCountdownTime.Minutes)), Loc.GetString("Station"));
+ _chatManager.DispatchStationAnnouncement(Loc.GetString("round-end-system-shuttle-called-announcement",("minutes", countdownTime.Minutes)), Loc.GetString("Station"));
SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/shuttlecalled.ogg");
- ExpectedCountdownEnd = _gameTiming.CurTime + RoundEndCountdownTime;
- Timer.Spawn(RoundEndCountdownTime, EndRound, _roundEndCancellationTokenSource.Token);
+ ExpectedCountdownEnd = _gameTiming.CurTime + countdownTime;
+ Timer.Spawn(countdownTime, EndRound, _roundEndCancellationTokenSource.Token);
ActivateCooldown();
OnRoundEndCountdownStarted?.Invoke();
}
- public void CancelRoundEndCountdown()
+ public void CancelRoundEndCountdown( bool checkCooldown = true)
{
if (!IsRoundEndCountdownStarted)
return;
- if (!CanCall())
+ if (checkCooldown && !CanCall())
{
return;
}
diff --git a/Content.Shared/Localizations/Localization.cs b/Content.Shared/Localizations/Localization.cs
index bca3172c47..cbdbd0868b 100644
--- a/Content.Shared/Localizations/Localization.cs
+++ b/Content.Shared/Localizations/Localization.cs
@@ -11,6 +11,17 @@ namespace Content.Shared.Localizations
// If you want to change your codebase's language, do it here.
private const string Culture = "en-US";
+ ///
+ /// Custom format strings used for parsing and displaying minutes:seconds timespans.
+ ///
+ public static readonly string[] TimeSpanMinutesFormats = new[]
+ {
+ @"m\:ss",
+ @"mm\:ss",
+ @"%m",
+ @"mm"
+ };
+
public static void Init()
{
var loc = IoCManager.Resolve();
diff --git a/Resources/Locale/en-US/administration/commands/call-shuttle-command.ftl b/Resources/Locale/en-US/administration/commands/call-shuttle-command.ftl
new file mode 100644
index 0000000000..672c68fd7c
--- /dev/null
+++ b/Resources/Locale/en-US/administration/commands/call-shuttle-command.ftl
@@ -0,0 +1,4 @@
+call-shuttle-command-description = Calls the emergency shuttle with an optionally provided arrival time.
+call-shuttle-command-help-text = Usage: {$command} [m:ss]
+recall-shuttle-command-description = Recalls the emergency shuttle.
+recall-shuttle-command-help-text = Usage: {$command}
diff --git a/Resources/Locale/en-US/administration/ui/tabs/admin-tab/call-shuttle-window.ftl b/Resources/Locale/en-US/administration/ui/tabs/admin-tab/call-shuttle-window.ftl
new file mode 100644
index 0000000000..14a790f81a
--- /dev/null
+++ b/Resources/Locale/en-US/administration/ui/tabs/admin-tab/call-shuttle-window.ftl
@@ -0,0 +1 @@
+admin-shuttle-title = (Re)call shuttle
diff --git a/Resources/Locale/en-US/shell.ftl b/Resources/Locale/en-US/shell.ftl
index b8fe21d26b..ee3d6d7fff 100644
--- a/Resources/Locale/en-US/shell.ftl
+++ b/Resources/Locale/en-US/shell.ftl
@@ -6,7 +6,7 @@ shell-server-cannot = Server cannot do this.
shell-command-success = Command successful
shell-invalid-command = Invalid command.
shell-invalid-command-specific = Invalid {$commandName} command.
-shell-cannot-run-command-from-server = You cannot run this command from the server.
+shell-cannot-run-command-from-server = You cannot run this command from the server.
shell-only-players-can-run-this-command = Only players can run this command.
## Arguments
@@ -29,5 +29,4 @@ shell-entity-with-uid-lacks-component = Entity with uid {$uid} doesn't have a {$
shell-invalid-color-hex = Invalid color hex!
shell-target-player-does-not-exist = Target player does not exist!
shell-target-entity-does-not-have-message = Target entity does not have a(n) {$missing}!
-
-
+shell-timespan-minutes-must-be-correct = {$span} is not a valid minutes timespan.