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);