diff --git a/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
new file mode 100644
index 0000000000..a01aac3099
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
@@ -0,0 +1,111 @@
+using System.Linq;
+using Content.Server.GameTicking;
+using Content.Server.Shuttles.Components;
+using Content.Server.Shuttles.Systems;
+using Content.Server.Station.Components;
+using Content.Shared.CCVar;
+using Content.Shared.Shuttles.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map.Components;
+
+namespace Content.IntegrationTests.Tests.Station;
+
+[TestFixture]
+[TestOf(typeof(EmergencyShuttleSystem))]
+public sealed class EvacShuttleTest
+{
+ ///
+ /// Ensure that the emergency shuttle can be called, and that it will travel to centcomm
+ ///
+ [Test]
+ public async Task EmergencyEvacTest()
+ {
+ await using var pair = await PoolManager.GetServerClient(new PoolSettings { DummyTicker = true, Dirty = true });
+ var server = pair.Server;
+ var entMan = server.EntMan;
+ var ticker = server.System();
+
+ var shuttleEnabled = pair.Server.CfgMan.GetCVar(CCVars.EmergencyShuttleEnabled);
+ pair.Server.CfgMan.SetCVar(CCVars.GameMap, "Saltern");
+ pair.Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false);
+ pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, true);
+
+ await server.WaitPost(() => ticker.RestartRound());
+ await pair.RunTicksSync(25);
+ Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
+
+ // Find the station, centcomm, and shuttle, and ftl map.
+
+ Assert.That(entMan.Count(), Is.EqualTo(1));
+ Assert.That(entMan.Count(), Is.EqualTo(1));
+ Assert.That(entMan.Count(), Is.EqualTo(1));
+ Assert.That(entMan.Count(), Is.EqualTo(1));
+ Assert.That(entMan.Count(), Is.EqualTo(0));
+
+ var station = (Entity) entMan.AllComponentsList().Single();
+ var data = entMan.GetComponent(station);
+ var shuttleData = entMan.GetComponent(station);
+
+ var saltern = data.Grids.Single();
+ Assert.That(entMan.HasComponent(saltern));
+
+ var shuttle = shuttleData.EmergencyShuttle!.Value;
+ Assert.That(entMan.HasComponent(shuttle));
+ Assert.That(entMan.HasComponent(shuttle));
+
+ var centcomm = station.Comp.Entity!.Value;
+ Assert.That(entMan.HasComponent(centcomm));
+
+ var centcommMap = station.Comp.MapEntity!.Value;
+ Assert.That(entMan.HasComponent(centcommMap));
+ Assert.That(server.Transform(centcomm).MapUid, Is.EqualTo(centcommMap));
+
+ var salternXform = server.Transform(saltern);
+ Assert.That(salternXform.MapUid, Is.Not.Null);
+ Assert.That(salternXform.MapUid, Is.Not.EqualTo(centcommMap));
+
+ var shuttleXform = server.Transform(shuttle);
+ Assert.That(shuttleXform.MapUid, Is.Not.Null);
+ Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap));
+
+ // Set up shuttle timing
+ var evacSys = server.System();
+ evacSys.TransitTime = ShuttleSystem.DefaultTravelTime; // Absolute minimum transit time, so the test has to run for at least this long
+ // TODO SHUTTLE fix spaghetti
+
+ var dockTime = server.CfgMan.GetCVar(CCVars.EmergencyShuttleDockTime);
+ server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, 2);
+ async Task RunSeconds(float seconds)
+ {
+ await pair.RunTicksSync((int) Math.Ceiling(seconds / server.Timing.TickPeriod.TotalSeconds));
+ }
+
+ // Call evac shuttle.
+ await pair.WaitCommand("callshuttle 0:02");
+ await RunSeconds(3);
+
+ // Shuttle should have arrived on the station
+ Assert.That(shuttleXform.MapUid, Is.EqualTo(salternXform.MapUid));
+
+ await RunSeconds(2);
+
+ // Shuttle should be FTLing back to centcomm
+ Assert.That(entMan.Count(), Is.EqualTo(1));
+ var ftl = (Entity) entMan.AllComponentsList().Single();
+ Assert.That(entMan.HasComponent(ftl));
+ Assert.That(ftl.Owner, Is.Not.EqualTo(centcommMap));
+ Assert.That(ftl.Owner, Is.Not.EqualTo(salternXform.MapUid));
+ Assert.That(shuttleXform.MapUid, Is.EqualTo(ftl.Owner));
+
+ // Shuttle should have arrived at centcomm
+ await RunSeconds(ShuttleSystem.DefaultTravelTime);
+ Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap));
+
+ // Round should be ending now
+ Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PostRound));
+
+ server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, dockTime);
+ pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, shuttleEnabled);
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs
index 758e2e3146..42783f163b 100644
--- a/Content.Server/RoundEnd/RoundEndSystem.cs
+++ b/Content.Server/RoundEnd/RoundEndSystem.cs
@@ -192,6 +192,8 @@ namespace Content.Server.RoundEnd
LastCountdownStart = _gameTiming.CurTime;
ExpectedCountdownEnd = _gameTiming.CurTime + countdownTime;
+
+ // TODO full game saves
Timer.Spawn(countdownTime, _shuttle.CallEmergencyShuttle, _countdownTokenSource.Token);
ActivateCooldown();
@@ -337,6 +339,8 @@ namespace Content.Server.RoundEnd
{
_cooldownTokenSource?.Cancel();
_cooldownTokenSource = new();
+
+ // TODO full game saves
Timer.Spawn(DefaultCooldownDuration, () =>
{
_cooldownTokenSource.Cancel();
diff --git a/Content.Server/Shuttles/Components/FTLComponent.cs b/Content.Server/Shuttles/Components/FTLComponent.cs
index a3da4855f7..edcf25981b 100644
--- a/Content.Server/Shuttles/Components/FTLComponent.cs
+++ b/Content.Server/Shuttles/Components/FTLComponent.cs
@@ -14,6 +14,8 @@ namespace Content.Server.Shuttles.Components;
[RegisterComponent]
public sealed partial class FTLComponent : Component
{
+ // TODO Full game save / add datafields
+
[ViewVariables]
public FTLState State = FTLState.Available;
@@ -23,6 +25,7 @@ public sealed partial class FTLComponent : Component
[ViewVariables(VVAccess.ReadWrite)]
public float StartupTime = 0f;
+ // Because of sphagetti, actual travel time is Math.Max(TravelTime, DefaultArrivalTime)
[ViewVariables(VVAccess.ReadWrite)]
public float TravelTime = 0f;
diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs
index aeb2ebdbba..803aa963f3 100644
--- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs
+++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs
@@ -19,6 +19,8 @@ using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Shuttles.Systems;
+// TODO full game saves
+// Move state data into the emergency shuttle component
public sealed partial class EmergencyShuttleSystem
{
/*
@@ -55,7 +57,7 @@ public sealed partial class EmergencyShuttleSystem
///
/// How long it will take for the emergency shuttle to arrive at CentComm.
///
- public float TransitTime { get; private set; }
+ public float TransitTime;
///
///
@@ -132,6 +134,14 @@ public sealed partial class EmergencyShuttleSystem
var minTime = -(TransitTime - (ShuttleSystem.DefaultStartupTime + ShuttleSystem.DefaultTravelTime + 1f));
// TODO: I know this is shit but I already just cleaned up a billion things.
+
+ // This is very cursed spaghetti code. I don't even know what the fuck this is doing or why it exists.
+ // But I think it needs to be less than or equal to zero or the shuttle might never leave???
+ // TODO Shuttle AAAAAAAAAAAAAAAAAAAAAAAAA
+ // Clean this up, just have a single timer with some state system.
+ // I.e., dont infer state from the current interval that the accumulator is in???
+ minTime = Math.Min(0, minTime); // ????
+
if (_consoleAccumulator < minTime)
{
return;
diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
index 5128869103..b2b6473693 100644
--- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
+++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
@@ -40,6 +40,10 @@ public sealed partial class ShuttleSystem
public const float FTLMassLimit = 300f;
// I'm too lazy to make CVars.
+ // >:(
+ // Confusingly, some of them already are cvars?
+ // I.e., shuttle transit time???
+ // TODO Shuttle: fix spaghetti
private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg")
{
diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs
index 2ab33b62a5..2fa2671b19 100644
--- a/Content.Server/Station/Systems/StationSystem.cs
+++ b/Content.Server/Station/Systems/StationSystem.cs
@@ -112,26 +112,12 @@ public sealed class StationSystem : EntitySystem
{
var dict = new Dictionary>();
- void AddGrid(string station, EntityUid grid)
- {
- if (dict.ContainsKey(station))
- {
- dict[station].Add(grid);
- }
- else
- {
- dict[station] = new List {grid};
- }
- }
-
// Iterate over all BecomesStation
foreach (var grid in ev.Grids)
{
// We still setup the grid
- if (!TryComp(grid, out var becomesStation))
- continue;
-
- AddGrid(becomesStation.Id, grid);
+ if (TryComp(grid, out var becomesStation))
+ dict.GetOrNew(becomesStation.Id).Add(grid);
}
if (!dict.Any())
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 94f9e218b8..3e1d3c2b10 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -707,7 +707,7 @@ namespace Content.Shared.CCVar
public static readonly CVarDef CombatModeIndicatorsPointShow =
CVarDef.Create("hud.combat_mode_indicators_point_show", true, CVar.ARCHIVE | CVar.CLIENTONLY);
-
+
public static readonly CVarDef LoocAboveHeadShow =
CVarDef.Create("hud.show_looc_above_head", true, CVar.ARCHIVE | CVar.CLIENTONLY);
@@ -1213,7 +1213,7 @@ namespace Content.Shared.CCVar
///
public static readonly CVarDef OocEnableDuringRound =
CVarDef.Create("ooc.enable_during_round", false, CVar.NOTIFY | CVar.REPLICATED | CVar.SERVER);
-
+
public static readonly CVarDef ShowOocPatronColor =
CVarDef.Create("ooc.show_ooc_patron_color", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.CLIENT);
@@ -1446,6 +1446,7 @@ namespace Content.Shared.CCVar
///
/// The minimum time for the emergency shuttle to arrive at centcomm.
+ /// Actual minimum travel time cannot be less than
///
public static readonly CVarDef EmergencyShuttleMinTransitTime =
CVarDef.Create("shuttle.emergency_transit_time_min", 60f, CVar.SERVERONLY);