]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Escape pods (#14809)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Fri, 24 Mar 2023 01:54:41 +0000 (12:54 +1100)
committerGitHub <noreply@github.com>
Fri, 24 Mar 2023 01:54:41 +0000 (19:54 -0600)
* Namespace adjustments for days

* pod

* thanks rider

* Fix the oop launch

* Fixes

* Fix stuff

eeeeeeeee

* Fix

* access

* map

* forgor

* thing

* Genericise escape pod fill

28 files changed:
Content.IntegrationTests/Tests/PostMapInitTest.cs
Content.Server/Communications/CommunicationsConsoleSystem.cs
Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
Content.Server/RoundEnd/RoundEndSystem.cs
Content.Server/Shuttles/Commands/DelayShuttleRoundEndCommand.cs
Content.Server/Shuttles/Commands/DockCommand.cs [moved from Content.Server/Shuttles/DockCommand.cs with 98% similarity]
Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs
Content.Server/Shuttles/Commands/LaunchEmergencyShuttleCommand.cs
Content.Server/Shuttles/Components/ArrivalsShuttleComponent.cs
Content.Server/Shuttles/Components/ArrivalsSourceComponent.cs
Content.Server/Shuttles/Components/EscapePodComponent.cs [new file with mode: 0644]
Content.Server/Shuttles/Components/GridFillComponent.cs [new file with mode: 0644]
Content.Server/Shuttles/Components/StationArrivalsComponent.cs
Content.Server/Shuttles/DockingConfig.cs [new file with mode: 0644]
Content.Server/Shuttles/Systems/ArrivalsSystem.cs
Content.Server/Shuttles/Systems/DockingSystem.AutoDock.cs
Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs [new file with mode: 0644]
Content.Server/Shuttles/Systems/DockingSystem.cs
Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs [moved from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs with 81% similarity]
Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs [moved from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyShuttle.cs with 61% similarity]
Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs
Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs [new file with mode: 0644]
Content.Server/Shuttles/Systems/ShuttleSystem.cs
Content.Server/Station/Components/StationDataComponent.cs
Content.Shared/Popups/SharedPopupSystem.cs
Resources/Maps/Shuttles/escape_pod_small.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml

index e8cc0f3fd8368868f9e97da2d456a8b82eb4b308..c1d7c2ac48168faec33032564e30394f09fcb0e6 100644 (file)
@@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests
                 Assert.IsNotNull(stationConfig, $"{entManager.ToPrettyString(station)} had null StationConfig.");
                 var shuttlePath = stationConfig.EmergencyShuttlePath.ToString();
                 var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath);
-                Assert.That(shuttle != null && shuttleSystem.TryFTLDock(entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
+                Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
 
                 mapManager.DeleteMap(shuttleMap);
 
index 6e2844e2713ecb9ed693e7cc942ca02a42442c02..3a1df97e18197754ba904a4d0992b1a154071e8a 100644 (file)
@@ -30,10 +30,10 @@ namespace Content.Server.Communications
         [Dependency] private readonly InteractionSystem _interaction = default!;
         [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
         [Dependency] private readonly ChatSystem _chatSystem = default!;
+        [Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
         [Dependency] private readonly IdCardSystem _idCardSystem = default!;
         [Dependency] private readonly PopupSystem _popupSystem = default!;
         [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
-        [Dependency] private readonly ShuttleSystem _shuttle = default!;
         [Dependency] private readonly StationSystem _stationSystem = default!;
         [Dependency] private readonly IConfigurationManager _cfg = default!;
         [Dependency] private readonly IAdminLogManager _adminLogger = default!;
@@ -186,7 +186,7 @@ namespace Content.Server.Communications
         private bool CanCallOrRecall(CommunicationsConsoleComponent comp)
         {
             // Defer to what the round end system thinks we should be able to do.
-            if (_shuttle.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
+            if (_emergency.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
                 return false;
 
             // Calling shuttle checks
index ced5715de12b7a5ed25055405c2946c7d738267b..bf2c7946db7d69a9b6e918c4afbb5388b9bb0eff 100644 (file)
@@ -43,15 +43,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
     [Dependency] private readonly IChatManager _chatManager = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPlayerManager _playerSystem = default!;
+    [Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
     [Dependency] private readonly FactionSystem _faction = default!;
     [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
     [Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
     [Dependency] private readonly StationSystem _stationSystem = default!;
-    [Dependency] private readonly ShuttleSystem _shuttleSystem = default!;
     [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
     [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
     [Dependency] private readonly GameTicker _ticker = default!;
     [Dependency] private readonly MapLoaderSystem _map = default!;
+    [Dependency] private readonly ShuttleSystem _shuttle = default!;
 
 
     private enum WinType
@@ -277,7 +278,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
             }
 
             // UH OH
-            if (nukeTransform.MapID == _shuttleSystem.CentComMap)
+            if (nukeTransform.MapID == _emergency.CentComMap)
             {
                 _winConditions.Add(WinCondition.NukeActiveAtCentCom);
                 RuleWinType = WinType.OpsMajor;
@@ -334,7 +335,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
         foreach (var (_, transform) in EntityManager.EntityQuery<NukeDiskComponent, TransformComponent>())
         {
             var diskMapId = transform.MapID;
-            diskAtCentCom = _shuttleSystem.CentComMap == diskMapId;
+            diskAtCentCom = _emergency.CentComMap == diskMapId;
 
             // TODO: The target station should be stored, and the nuke disk should store its original station.
             // This is fine for now, because we can assume a single station in base SS14.
@@ -655,7 +656,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
 
         if (TryComp<ShuttleComponent>(shuttleId, out var shuttle))
         {
-            _shuttleSystem.TryFTLDock(shuttle, _nukieOutpost.Value);
+            _shuttle.TryFTLDock(shuttleId, shuttle, _nukieOutpost.Value);
         }
 
         _nukiePlanet = mapId;
index bba3fbdd3cb738eaa6aa219c235cf7731acb4f2a..1479d2d316d42dd0f4417269b9ef4d7f68e23cfd 100644 (file)
@@ -32,7 +32,7 @@ namespace Content.Server.RoundEnd
         [Dependency] private readonly IPrototypeManager _protoManager = default!;
         [Dependency] private readonly ChatSystem _chatSystem = default!;
         [Dependency] private readonly GameTicker _gameTicker = default!;
-        [Dependency] private readonly ShuttleSystem _shuttle = default!;
+        [Dependency] private readonly EmergencyShuttleSystem _shuttle = default!;
         [Dependency] private readonly StationSystem _stationSystem = default!;
 
         public TimeSpan DefaultCooldownDuration { get; set; } = TimeSpan.FromSeconds(30);
index 05cdfa50c24db022f364218804c45e3de998051c..3c881dcbb5b45ba9f870abaabbe787c6b97121ad 100644 (file)
@@ -11,12 +11,15 @@ namespace Content.Server.Shuttles.Commands;
 [AdminCommand(AdminFlags.Fun)]
 public sealed class DelayRoundEndCommand : IConsoleCommand
 {
+    [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
     public string Command => "delayroundend";
     public string Description => Loc.GetString("emergency-shuttle-command-round-desc");
     public string Help => $"{Command}";
     public void Execute(IConsoleShell shell, string argStr, string[] args)
     {
-        var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+        var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
+
         if (system.DelayEmergencyRoundEnd())
         {
             shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-yes"));
similarity index 98%
rename from Content.Server/Shuttles/DockCommand.cs
rename to Content.Server/Shuttles/Commands/DockCommand.cs
index 1f6d7440a872d095c02584e35778f7404e61ebb9..7a621c2b80c65b3186027bf4a67ae1ca5e7da62f 100644 (file)
@@ -4,7 +4,7 @@ using Content.Server.Shuttles.Systems;
 using Content.Shared.Administration;
 using Robust.Shared.Console;
 
-namespace Content.Server.Shuttles;
+namespace Content.Server.Shuttles.Commands;
 
 [AdminCommand(AdminFlags.Mapping)]
 public sealed class DockCommand : IConsoleCommand
index ae8093c8fd6459dc7fbe826ffe31f927da096b67..8febe51f5a76d19f40881da041e65350b1f18a73 100644 (file)
@@ -11,12 +11,14 @@ namespace Content.Server.Shuttles.Commands;
 [AdminCommand(AdminFlags.Fun)]
 public sealed class DockEmergencyShuttleCommand : IConsoleCommand
 {
+    [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
     public string Command => "dockemergencyshuttle";
     public string Description => Loc.GetString("emergency-shuttle-command-dock-desc");
     public string Help => $"{Command}";
     public void Execute(IConsoleShell shell, string argStr, string[] args)
     {
-        var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+        var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
         system.CallEmergencyShuttle();
     }
 }
index 20381b9017ede94f6e6e65ccb890b6572ca7f047..b9dd9c20e95e1e713d8cffb5186803190fac17c2 100644 (file)
@@ -11,12 +11,14 @@ namespace Content.Server.Shuttles.Commands;
 [AdminCommand(AdminFlags.Fun)]
 public sealed class LaunchEmergencyShuttleCommand : IConsoleCommand
 {
+    [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
     public string Command => "launchemergencyshuttle";
     public string Description => Loc.GetString("emergency-shuttle-command-launch-desc");
     public string Help => $"{Command}";
     public void Execute(IConsoleShell shell, string argStr, string[] args)
     {
-        var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+        var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
         system.EarlyLaunch();
     }
 }
index 564f795b337ea80a4ce0dc623888bd11638c0d0b..1f6b777963c78150e6293a78878355fb3bba27f0 100644 (file)
@@ -1,8 +1,9 @@
+using Content.Server.Shuttles.Systems;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
 namespace Content.Server.Shuttles.Components;
 
-[RegisterComponent]
+[RegisterComponent, Access(typeof(ArrivalsSystem))]
 public sealed class ArrivalsShuttleComponent : Component
 {
     [DataField("station")]
index 549a5b29034ec741025f82bcadac2745ca01c5c5..a1a00793f26ce17a4c93e67c746969b8fb8a652c 100644 (file)
@@ -1,9 +1,11 @@
+using Content.Server.Shuttles.Systems;
+
 namespace Content.Server.Shuttles.Components;
 
 /// <summary>
 /// Added to a designated arrivals station for players to spawn at, if enabled.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, Access(typeof(ArrivalsSystem))]
 public sealed class ArrivalsSourceComponent : Component
 {
 
diff --git a/Content.Server/Shuttles/Components/EscapePodComponent.cs b/Content.Server/Shuttles/Components/EscapePodComponent.cs
new file mode 100644 (file)
index 0000000..20d3bba
--- /dev/null
@@ -0,0 +1,14 @@
+using Content.Server.Shuttles.Systems;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Shuttles.Components;
+
+/// <summary>
+/// If added to a grid gets launched when the emergency shuttle launches.
+/// </summary>
+[RegisterComponent, Access(typeof(EmergencyShuttleSystem))]
+public sealed class EscapePodComponent : Component
+{
+    [DataField("launchTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
+    public TimeSpan? LaunchTime;
+}
diff --git a/Content.Server/Shuttles/Components/GridFillComponent.cs b/Content.Server/Shuttles/Components/GridFillComponent.cs
new file mode 100644 (file)
index 0000000..c1493a9
--- /dev/null
@@ -0,0 +1,13 @@
+using Content.Server.Shuttles.Systems;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Shuttles.Components;
+
+/// <summary>
+/// If added to an airlock will try to autofill a grid onto it on MapInit
+/// </summary>
+[RegisterComponent, Access(typeof(ShuttleSystem))]
+public sealed class GridFillComponent : Component
+{
+    [DataField("path")] public ResourcePath Path = new("/Maps/Shuttles/escape_pod_small.yml");
+}
index fded1b1d3a35bba3045846285468789ee832c9ca..5ba5c194ed6b877932b03b2a8e622b46ba133a76 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Server.Shuttles.Systems;
 using Robust.Shared.Utility;
 
 namespace Content.Server.Shuttles.Components;
@@ -5,7 +6,7 @@ namespace Content.Server.Shuttles.Components;
 /// <summary>
 /// Added to a station that is available for arrivals shuttles.
 /// </summary>
-[RegisterComponent]
+[RegisterComponent, Access(typeof(ArrivalsSystem))]
 public sealed class StationArrivalsComponent : Component
 {
     [DataField("shuttle")]
diff --git a/Content.Server/Shuttles/DockingConfig.cs b/Content.Server/Shuttles/DockingConfig.cs
new file mode 100644 (file)
index 0000000..446f832
--- /dev/null
@@ -0,0 +1,28 @@
+using Content.Server.Shuttles.Components;
+using Robust.Shared.Map;
+
+namespace Content.Server.Shuttles;
+
+/// <summary>
+/// Stores the data for a valid docking configuration for the emergency shuttle
+/// </summary>
+public sealed class DockingConfig
+{
+    /// <summary>
+    /// The pairs of docks that can connect.
+    /// </summary>
+    public List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)> Docks = new();
+
+    /// <summary>
+    /// Area relative to the target grid the emergency shuttle will spawn in on.
+    /// </summary>
+    public Box2 Area;
+
+    /// <summary>
+    /// Target grid for docking.
+    /// </summary>
+    public EntityUid TargetGrid;
+
+    public EntityCoordinates Coordinates;
+    public Angle Angle;
+}
index b05be7cd0a3beffa6fac802ad3189401c8746952..cb7d0074be651b8b11cf9a5c7180836b808d164a 100644 (file)
@@ -280,7 +280,7 @@ public sealed class ArrivalsSystem : EntitySystem
         // TODO: Need some kind of comp to shunt people off if they try to get on?
         if (TryComp<TransformComponent>(arrivals, out var arrivalsXform))
         {
-            while (query.MoveNext(out var comp, out var shuttle, out var xform))
+            while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform))
             {
                 if (comp.NextTransfer > curTime || !TryComp<StationDataComponent>(comp.Station, out var data))
                     continue;
@@ -289,7 +289,7 @@ public sealed class ArrivalsSystem : EntitySystem
                 if (xform.MapUid != arrivalsXform.MapUid)
                 {
                     if (arrivals.IsValid())
-                        _shuttles.FTLTravel(shuttle, arrivals, dock: true);
+                        _shuttles.FTLTravel(uid, shuttle, arrivals, dock: true);
                 }
                 // Go to station
                 else
@@ -297,7 +297,7 @@ public sealed class ArrivalsSystem : EntitySystem
                     var targetGrid = _station.GetLargestGrid(data);
 
                     if (targetGrid != null)
-                        _shuttles.FTLTravel(shuttle, targetGrid.Value, dock: true);
+                        _shuttles.FTLTravel(uid, shuttle, targetGrid.Value, dock: true);
                 }
 
                 comp.NextTransfer += TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
@@ -395,7 +395,7 @@ public sealed class ArrivalsSystem : EntitySystem
             var arrivalsComp = EnsureComp<ArrivalsShuttleComponent>(component.Shuttle);
             arrivalsComp.Station = uid;
             EnsureComp<ProtectedGridComponent>(uid);
-            _shuttles.FTLTravel(shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
+            _shuttles.FTLTravel(component.Shuttle, shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
             arrivalsComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
         }
 
index fc8d331e2d8c78bcca0d0d66578e3ffa21ba801b..6091b80a7709d9f328f25b1a427ab7339f876e3f 100644 (file)
@@ -56,8 +56,8 @@ public sealed partial class DockingSystem
                 continue;
             }
 
-            var worldPos = _transformSystem.GetWorldPosition(xform, xformQuery);
-            var otherWorldPos = _transformSystem.GetWorldPosition(otherXform, xformQuery);
+            var worldPos = _transform.GetWorldPosition(xform, xformQuery);
+            var otherWorldPos = _transform.GetWorldPosition(otherXform, xformQuery);
 
             if ((worldPos - otherWorldPos).Length < comp.Radius) continue;
 
diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs
new file mode 100644 (file)
index 0000000..f72a1df
--- /dev/null
@@ -0,0 +1,267 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Shuttles.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+
+namespace Content.Server.Shuttles.Systems;
+
+public sealed partial class DockingSystem
+{
+    /*
+     * Handles the shuttle side of FTL docking.
+     */
+
+    public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
+   {
+       var (shuttlePos, shuttleRot) = _transform.GetWorldPositionRotation(xform, xformQuery);
+       var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery);
+
+       var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
+           Comp<PhysicsComponent>(uid).LocalCenter);
+       var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
+           Comp<PhysicsComponent>(targetUid).LocalCenter);
+
+       var mapDiff = shuttleCOM - targetCOM;
+       var angle = mapDiff.ToWorldAngle();
+       angle -= targetRot;
+       return angle;
+   }
+
+   /// <summary>
+   /// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
+   /// </summary>
+   public bool CanDock(
+       DockingComponent shuttleDock,
+       TransformComponent shuttleDockXform,
+       DockingComponent gridDock,
+       TransformComponent gridDockXform,
+       Angle targetGridRotation,
+       Box2 shuttleAABB,
+       MapGridComponent grid,
+       [NotNullWhen(true)] out Box2? shuttleDockedAABB,
+       out Matrix3 matty,
+       out Angle gridRotation)
+   {
+       gridRotation = Angle.Zero;
+       matty = Matrix3.Identity;
+       shuttleDockedAABB = null;
+
+       if (shuttleDock.Docked ||
+           gridDock.Docked ||
+           !shuttleDockXform.Anchored ||
+           !gridDockXform.Anchored)
+       {
+           return false;
+       }
+
+       // First, get the station dock's position relative to the shuttle, this is where we rotate it around
+       var stationDockPos = shuttleDockXform.LocalPosition +
+                            shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
+
+       // Need to invert the grid's angle.
+       var shuttleDockAngle = shuttleDockXform.LocalRotation;
+       var gridDockAngle = gridDockXform.LocalRotation.Opposite();
+
+       var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
+       var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
+       Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
+       shuttleDockedAABB = matty.TransformBox(shuttleAABB);
+       // Rounding moment
+       shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
+
+       if (!ValidSpawn(grid, shuttleDockedAABB.Value))
+           return false;
+
+       gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
+       return true;
+   }
+
+   /// <summary>
+   /// Gets docking config between 2 specific docks.
+   /// </summary>
+   public DockingConfig? GetDockingConfig(
+       EntityUid shuttleUid,
+       EntityUid targetGrid,
+       EntityUid shuttleDockUid,
+       DockingComponent shuttleDock,
+       EntityUid gridDockUid,
+       DockingComponent gridDock)
+   {
+       var shuttleDocks = new List<(EntityUid, DockingComponent)>(1)
+       {
+           (shuttleDockUid, shuttleDock)
+       };
+
+       var gridDocks = new List<(EntityUid, DockingComponent)>(1)
+       {
+           (gridDockUid, gridDock)
+       };
+
+       return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks);
+   }
+
+   /// <summary>
+   /// Tries to get a valid docking configuration for the shuttle to the target grid.
+   /// </summary>
+   /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
+   public DockingConfig? GetDockingConfig(EntityUid shuttleUid, EntityUid targetGrid, string? priorityTag = null)
+   {
+       var gridDocks = GetDocks(targetGrid);
+       var shuttleDocks = GetDocks(shuttleUid);
+
+       return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks, priorityTag);
+   }
+
+   private DockingConfig? GetDockingConfigPrivate(
+       EntityUid shuttleUid,
+       EntityUid targetGrid,
+       List<(EntityUid, DockingComponent)> shuttleDocks,
+       List<(EntityUid, DockingComponent)> gridDocks,
+       string? priorityTag = null)
+    {
+        if (gridDocks.Count <= 0)
+            return null;
+
+        var xformQuery = GetEntityQuery<TransformComponent>();
+        var targetGridGrid = Comp<MapGridComponent>(targetGrid);
+        var targetGridXform = xformQuery.GetComponent(targetGrid);
+        var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced();
+
+        var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
+
+        var validDockConfigs = new List<DockingConfig>();
+
+        if (shuttleDocks.Count > 0)
+        {
+           // We'll try all combinations of shuttle docks and see which one is most suitable
+           foreach (var (dockUid, shuttleDock) in shuttleDocks)
+           {
+               var shuttleDockXform = xformQuery.GetComponent(dockUid);
+
+               foreach (var (gridDockUid, gridDock) in gridDocks)
+               {
+                   var gridXform = xformQuery.GetComponent(gridDockUid);
+
+                   if (!CanDock(
+                           shuttleDock, shuttleDockXform,
+                           gridDock, gridXform,
+                           targetGridAngle,
+                           shuttleAABB,
+                           targetGridGrid,
+                           out var dockedAABB,
+                           out var matty,
+                           out var targetAngle))
+                   {
+                       continue;
+                   }
+
+                   // Can't just use the AABB as we want to get bounds as tight as possible.
+                   var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
+                   spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager, _transform));
+
+                   var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
+
+                   // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
+                   if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
+                           dockedBounds).Any(o => o.Owner != targetGrid))
+                   {
+                       continue;
+                   }
+
+                   // Alright well the spawn is valid now to check how many we can connect
+                   // Get the matrix for each shuttle dock and test it against the grid docks to see
+                   // if the connected position / direction matches.
+
+                   var dockedPorts = new List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)>()
+                   {
+                       (dockUid, gridDockUid, shuttleDock, gridDock),
+                   };
+
+                   // TODO: Check shuttle orientation as the tiebreaker.
+
+                   foreach (var (otherUid, other) in shuttleDocks)
+                   {
+                       if (other == shuttleDock)
+                           continue;
+
+                       foreach (var (otherGridUid, otherGrid) in gridDocks)
+                       {
+                           if (otherGrid == gridDock)
+                               continue;
+
+                           if (!CanDock(
+                                   other,
+                                   xformQuery.GetComponent(otherUid),
+                                   otherGrid,
+                                   xformQuery.GetComponent(otherGridUid),
+                                   targetGridAngle,
+                                   shuttleAABB, targetGridGrid,
+                                   out var otherDockedAABB,
+                                   out _,
+                                   out var otherTargetAngle) ||
+                               !otherDockedAABB.Equals(dockedAABB) ||
+                               !targetAngle.Equals(otherTargetAngle))
+                           {
+                               continue;
+                           }
+
+                           dockedPorts.Add((otherUid, otherGridUid, other, otherGrid));
+                       }
+                   }
+
+                   validDockConfigs.Add(new DockingConfig()
+                   {
+                       Docks = dockedPorts,
+                       Area = dockedAABB.Value,
+                       Coordinates = spawnPosition,
+                       Angle = targetAngle,
+                   });
+               }
+           }
+        }
+
+        if (validDockConfigs.Count <= 0)
+           return null;
+
+        // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
+        validDockConfigs = validDockConfigs
+           .OrderByDescending(x => x.Docks.Any(docks =>
+               TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
+               priority.Tag?.Equals(priorityTag) == true))
+           .ThenByDescending(x => x.Docks.Count)
+           .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
+
+        var location = validDockConfigs.First();
+        location.TargetGrid = targetGrid;
+        // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
+
+        return location;
+    }
+
+   /// <summary>
+   /// Checks whether the emergency shuttle can warp to the specified position.
+   /// </summary>
+   private bool ValidSpawn(MapGridComponent grid, Box2 area)
+   {
+       return !grid.GetLocalTilesIntersecting(area).Any();
+   }
+
+   public List<(EntityUid Uid, DockingComponent Component)> GetDocks(EntityUid uid)
+   {
+       var result = new List<(EntityUid Uid, DockingComponent Component)>();
+       var query = AllEntityQuery<DockingComponent, TransformComponent>();
+
+       while (query.MoveNext(out var dockUid, out var dock, out var xform))
+       {
+           if (xform.ParentUid != uid || !dock.Enabled)
+               continue;
+
+           result.Add((dockUid, dock));
+       }
+
+       return result;
+   }
+}
index 38f8e4f0a429fd8227a84519b66538b46ec4d598..28fac88f2082af812bb1576acdcc8ae24a34ab62 100644 (file)
@@ -25,7 +25,7 @@ namespace Content.Server.Shuttles.Systems
         [Dependency] private readonly ShuttleConsoleSystem _console = default!;
         [Dependency] private readonly SharedJointSystem _jointSystem = default!;
         [Dependency] private readonly SharedPhysicsSystem _physics = default!;
-        [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+        [Dependency] private readonly SharedTransformSystem _transform = default!;
 
         private ISawmill _sawmill = default!;
         private const string DockingFixture = "docking";
similarity index 81%
rename from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs
rename to Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs
index 536de0c1d8186117a6354581ad099f50ee2ca93b..3bc7264fb3c38b180d99d73e47f8c2fa0ce13333 100644 (file)
@@ -1,14 +1,11 @@
 using System.Threading;
-using Content.Server.Access.Systems;
-using Content.Server.Popups;
-using Content.Server.RoundEnd;
 using Content.Server.Shuttles.Components;
 using Content.Server.Shuttles.Events;
 using Content.Server.Station.Components;
 using Content.Server.UserInterface;
-using Content.Shared.Access.Systems;
 using Content.Shared.CCVar;
 using Content.Shared.Database;
+using Content.Shared.GameTicking;
 using Content.Shared.Popups;
 using Content.Shared.Shuttles.BUIStates;
 using Content.Shared.Shuttles.Events;
@@ -16,23 +13,16 @@ using Content.Shared.Shuttles.Systems;
 using Robust.Shared.Audio;
 using Robust.Shared.Map;
 using Robust.Shared.Player;
-using Robust.Shared.Timing;
 using Timer = Robust.Shared.Timing.Timer;
 
 namespace Content.Server.Shuttles.Systems;
 
-public sealed partial class ShuttleSystem
+public sealed partial class EmergencyShuttleSystem
 {
     /*
      * Handles the emergency shuttle's console and early launching.
      */
 
-    [Dependency] private readonly IGameTiming _timing = default!;
-    [Dependency] private readonly IdCardSystem _idSystem = default!;
-    [Dependency] private readonly AccessReaderSystem _reader = default!;
-    [Dependency] private readonly PopupSystem _popup = default!;
-    [Dependency] private readonly RoundEndSystem _roundEnd = default!;
-
     /// <summary>
     /// Has the emergency shuttle arrived?
     /// </summary>
@@ -43,7 +33,7 @@ public sealed partial class ShuttleSystem
     /// <summary>
     /// How much time remaining until the shuttle consoles for emergency shuttles are unlocked?
     /// </summary>
-    private float _consoleAccumulator;
+    private float _consoleAccumulator = float.MinValue;
 
     /// <summary>
     /// How long after the transit is over to end the round.
@@ -70,6 +60,8 @@ public sealed partial class ShuttleSystem
     /// </summary>
     private bool _launchedShuttles;
 
+    private bool _leftShuttles;
+
     /// <summary>
     /// Have we announced the launch?
     /// </summary>
@@ -84,6 +76,8 @@ public sealed partial class ShuttleSystem
         SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealMessage>(OnEmergencyRepeal);
         SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealAllMessage>(OnEmergencyRepealAll);
         SubscribeLocalEvent<EmergencyShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnEmergencyOpenAttempt);
+
+        SubscribeLocalEvent<EscapePodComponent, EntityUnpausedEvent>(OnEscapeUnpaused);
     }
 
     private void OnEmergencyOpenAttempt(EntityUid uid, EmergencyShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
@@ -119,7 +113,14 @@ public sealed partial class ShuttleSystem
 
     private void UpdateEmergencyConsole(float frameTime)
     {
-        if (_consoleAccumulator <= 0f) return;
+        // Add some buffer time so eshuttle always first.
+        var minTime = -(TransitTime - (ShuttleSystem.DefaultStartupTime + ShuttleSystem.DefaultTravelTime + 1f));
+
+        // TODO: I know this is shit but I already just cleaned up a billion things.
+        if (_consoleAccumulator < minTime)
+        {
+            return;
+        }
 
         _consoleAccumulator -= frameTime;
 
@@ -131,46 +132,76 @@ public sealed partial class ShuttleSystem
         }
 
         // Imminent departure
-        if (!_launchedShuttles && _consoleAccumulator <= DefaultStartupTime)
+        if (!_launchedShuttles && _consoleAccumulator <= ShuttleSystem.DefaultStartupTime)
         {
             _launchedShuttles = true;
 
             if (CentComMap != null)
             {
-                foreach (var comp in EntityQuery<StationDataComponent>(true))
+                var dataQuery = AllEntityQuery<StationDataComponent>();
+
+                while (dataQuery.MoveNext(out var comp))
                 {
-                    if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle)) continue;
+                    if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle))
+                        continue;
 
                     if (Deleted(CentCom))
                     {
                         // TODO: Need to get non-overlapping positions.
-                        FTLTravel(shuttle,
+                        _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
                             new EntityCoordinates(
                                 _mapManager.GetMapEntityId(CentComMap.Value),
-                                Vector2.One * 1000f), _consoleAccumulator, TransitTime);
+                                _random.NextVector2(1000f)), _consoleAccumulator, TransitTime);
                     }
                     else
                     {
-                        FTLTravel(shuttle,
+                        _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
                             CentCom.Value, _consoleAccumulator, TransitTime, true);
                     }
                 }
+
+                var podQuery = AllEntityQuery<EscapePodComponent>();
+                var podLaunchOffset = 0.5f;
+
+                // Stagger launches coz funny
+                while (podQuery.MoveNext(out _, out var pod))
+                {
+                    pod.LaunchTime = _timing.CurTime + TimeSpan.FromSeconds(podLaunchOffset);
+                    podLaunchOffset += _random.NextFloat(0.5f, 2.5f);
+                }
             }
         }
 
+        var podLaunchQuery = EntityQueryEnumerator<EscapePodComponent, ShuttleComponent>();
+
+        while (podLaunchQuery.MoveNext(out var uid, out var pod, out var shuttle))
+        {
+            if (CentCom == null || pod.LaunchTime == null || pod.LaunchTime < _timing.CurTime)
+                continue;
+
+            // Don't dock them. If you do end up doing this then stagger launch.
+            _shuttle.FTLTravel(uid, shuttle,
+                CentCom.Value, hyperspaceTime: TransitTime);
+
+            RemCompDeferred<EscapePodComponent>(uid);
+        }
+
         // Departed
-        if (_consoleAccumulator <= 0f)
+        if (!_leftShuttles && _consoleAccumulator <= 0f)
         {
-            _launchedShuttles = true;
+            _leftShuttles = true;
             _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}")));
 
             _roundEndCancelToken = new CancellationTokenSource();
             Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
+        }
 
+        // All the others.
+        if (_consoleAccumulator < minTime)
+        {
             // Guarantees that emergency shuttle arrives first before anyone else can FTL.
             if (CentCom != null)
-                AddFTLDestination(CentCom.Value, true);
-
+                _shuttle.AddFTLDestination(CentCom.Value, true);
         }
     }
 
@@ -185,7 +216,8 @@ public sealed partial class ShuttleSystem
             return;
         }
 
-        if (component.AuthorizedEntities.Count == 0) return;
+        if (component.AuthorizedEntities.Count == 0)
+            return;
 
         _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL ALL by {args.Session:user}");
         _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-console-auth-revoked", ("remaining", component.AuthorizationsRequired)));
@@ -246,8 +278,9 @@ public sealed partial class ShuttleSystem
     {
         _announced = false;
         _roundEndCancelToken = null;
+        _leftShuttles = false;
         _launchedShuttles = false;
-        _consoleAccumulator = 0f;
+        _consoleAccumulator = float.MinValue;
         EarlyLaunchAuthorized = false;
         EmergencyShuttleArrived = false;
     }
@@ -294,7 +327,7 @@ public sealed partial class ShuttleSystem
         if (EarlyLaunchAuthorized || !EmergencyShuttleArrived || _consoleAccumulator <= _authorizeTime) return false;
 
         _logger.Add(LogType.EmergencyShuttle, LogImpact.Extreme, $"Emergency shuttle launch authorized");
-        _consoleAccumulator =_authorizeTime;
+        _consoleAccumulator = _authorizeTime;
         EarlyLaunchAuthorized = true;
         RaiseLocalEvent(new EmergencyShuttleAuthorizedEvent());
         AnnounceLaunch();
similarity index 61%
rename from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyShuttle.cs
rename to Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
index ebe7b0e7ddf6acffcc4e51b8ef1b25ebdf013301..f8b48e474e773f830591357431d7dd7b198332be 100644 (file)
@@ -1,33 +1,30 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
+using Content.Server.Access.Systems;
 using Content.Server.Administration.Logs;
 using Content.Server.Administration.Managers;
 using Content.Server.Chat.Systems;
 using Content.Server.Communications;
 using Content.Server.GameTicking.Events;
+using Content.Server.Popups;
+using Content.Server.RoundEnd;
 using Content.Server.Shuttles.Components;
 using Content.Server.Station.Components;
 using Content.Server.Station.Systems;
+using Content.Shared.Access.Systems;
 using Content.Shared.CCVar;
 using Content.Shared.Database;
 using Content.Shared.Shuttles.Events;
-using Content.Shared.Tiles;
-using Content.Shared.Tag;
 using Robust.Server.GameObjects;
 using Robust.Server.Maps;
 using Robust.Server.Player;
-using Robust.Shared.Audio;
 using Robust.Shared.Configuration;
 using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Components;
 using Robust.Shared.Player;
 using Robust.Shared.Random;
+using Robust.Shared.Timing;
 
 namespace Content.Server.Shuttles.Systems;
 
-public sealed partial class ShuttleSystem
+public sealed partial class EmergencyShuttleSystem : EntitySystem
 {
    /*
     * Handles the escape shuttle + CentCom.
@@ -36,12 +33,23 @@ public sealed partial class ShuttleSystem
    [Dependency] private readonly IAdminLogManager _logger = default!;
    [Dependency] private readonly IAdminManager _admin = default!;
    [Dependency] private readonly IConfigurationManager _configManager = default!;
+   [Dependency] private readonly IGameTiming _timing = default!;
+   [Dependency] private readonly IMapManager _mapManager = default!;
    [Dependency] private readonly IRobustRandom _random = default!;
+   [Dependency] private readonly AccessReaderSystem _reader = default!;
    [Dependency] private readonly ChatSystem _chatSystem = default!;
    [Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!;
-   [Dependency] private readonly DockingSystem _dockSystem = default!;
+   [Dependency] private readonly DockingSystem _dock = default!;
+   [Dependency] private readonly IdCardSystem _idSystem = default!;
    [Dependency] private readonly MapLoaderSystem _map = default!;
+   [Dependency] private readonly PopupSystem _popup = default!;
+   [Dependency] private readonly RoundEndSystem _roundEnd = default!;
+   [Dependency] private readonly SharedAudioSystem _audio = default!;
+   [Dependency] private readonly ShuttleSystem _shuttle = default!;
    [Dependency] private readonly StationSystem _station = default!;
+   [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
+
+   private ISawmill _sawmill = default!;
 
    public MapId? CentComMap { get; private set; }
    public EntityUid? CentCom { get; private set; }
@@ -55,19 +63,22 @@ public sealed partial class ShuttleSystem
 
    private bool _emergencyShuttleEnabled;
 
-   private void InitializeEscape()
+   public override void Initialize()
    {
+       _sawmill = Logger.GetSawmill("shuttle.emergency");
        _emergencyShuttleEnabled = _configManager.GetCVar(CCVars.EmergencyShuttleEnabled);
        // Don't immediately invoke as roundstart will just handle it.
        _configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
        SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
        SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationStartup);
        SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition);
+       InitializeEmergencyConsole();
    }
 
    private void SetEmergencyShuttleEnabled(bool value)
    {
-       if (_emergencyShuttleEnabled == value) return;
+       if (_emergencyShuttleEnabled == value)
+           return;
        _emergencyShuttleEnabled = value;
 
        if (value)
@@ -80,9 +91,16 @@ public sealed partial class ShuttleSystem
        }
    }
 
-   private void ShutdownEscape()
+   public override void Update(float frameTime)
+   {
+       base.Update(frameTime);
+       UpdateEmergencyConsole(frameTime);
+   }
+
+   public override void Shutdown()
    {
-        _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
+       _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
+       ShutdownEmergencyConsole();
    }
 
    /// <summary>
@@ -90,18 +108,25 @@ public sealed partial class ShuttleSystem
    /// </summary>
    private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args)
    {
-       if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) return;
+       if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
+           return;
 
        var player = args.SenderSession.AttachedEntity;
 
        if (player == null ||
            !TryComp<StationDataComponent>(_station.GetOwningStation(player.Value), out var stationData) ||
-           !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
+           !HasComp<ShuttleComponent>(stationData.EmergencyShuttle))
+       {
+           return;
+       }
 
        var targetGrid = _station.GetLargestGrid(stationData);
-       if (targetGrid == null) return;
-       var config = GetDockingConfig(shuttle, targetGrid.Value);
-       if (config == null) return;
+       if (targetGrid == null)
+           return;
+
+       var config = _dock.GetDockingConfig(stationData.EmergencyShuttle.Value, targetGrid.Value);
+       if (config == null)
+           return;
 
        RaiseNetworkEvent(new EmergencyShuttlePositionMessage()
        {
@@ -117,9 +142,12 @@ public sealed partial class ShuttleSystem
    {
        if (!TryComp<StationDataComponent>(stationUid, out var stationData) ||
            !TryComp<TransformComponent>(stationData.EmergencyShuttle, out var xform) ||
-           !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
+           !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle))
+       {
+           return;
+       }
 
-      var targetGrid = _station.GetLargestGrid(stationData);
+       var targetGrid = _station.GetLargestGrid(stationData);
 
        // UHH GOOD LUCK
        if (targetGrid == null)
@@ -127,105 +155,38 @@ public sealed partial class ShuttleSystem
            _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}");
            _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
            // TODO: Need filter extensions or something don't blame me.
-           SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
+           _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
            return;
        }
 
        var xformQuery = GetEntityQuery<TransformComponent>();
 
-       if (TryFTLDock(shuttle, targetGrid.Value))
+       if (_shuttle.TryFTLDock(stationData.EmergencyShuttle.Value, shuttle, targetGrid.Value))
        {
            if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
            {
-               var angle = GetAngle(xform, targetXform, xformQuery);
+               var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
                _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
            }
 
            _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations");
            // TODO: Need filter extensions or something don't blame me.
-           SoundSystem.Play("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast());
+           _audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true);
        }
        else
        {
            if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
            {
-               var angle = GetAngle(xform, targetXform, xformQuery);
+               var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
                _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
            }
 
            _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}");
            // TODO: Need filter extensions or something don't blame me.
-           SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
+           _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
        }
    }
 
-   private Angle GetAngle(TransformComponent xform, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
-   {
-       var (shuttlePos, shuttleRot) = xform.GetWorldPositionRotation(xformQuery);
-       var (targetPos, targetRot) = targetXform.GetWorldPositionRotation(xformQuery);
-
-       var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
-           Comp<PhysicsComponent>(xform.Owner).LocalCenter);
-       var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
-           Comp<PhysicsComponent>(targetXform.Owner).LocalCenter);
-
-       var mapDiff = shuttleCOM - targetCOM;
-       var targetRotation = targetRot;
-       var angle = mapDiff.ToWorldAngle();
-       angle -= targetRotation;
-       return angle;
-   }
-
-   /// <summary>
-   /// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
-   /// </summary>
-   private bool CanDock(
-       DockingComponent shuttleDock,
-       TransformComponent shuttleDockXform,
-       DockingComponent gridDock,
-       TransformComponent gridDockXform,
-       Angle targetGridRotation,
-       Box2 shuttleAABB,
-       EntityUid gridUid,
-       MapGridComponent grid,
-       [NotNullWhen(true)] out Box2? shuttleDockedAABB,
-       out Matrix3 matty,
-       out Angle gridRotation)
-   {
-       gridRotation = Angle.Zero;
-       matty = Matrix3.Identity;
-       shuttleDockedAABB = null;
-
-       if (shuttleDock.Docked ||
-           gridDock.Docked ||
-           !shuttleDockXform.Anchored ||
-           !gridDockXform.Anchored)
-       {
-           return false;
-       }
-
-       // First, get the station dock's position relative to the shuttle, this is where we rotate it around
-       var stationDockPos = shuttleDockXform.LocalPosition +
-                            shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
-
-       // Need to invert the grid's angle.
-       var shuttleDockAngle = shuttleDockXform.LocalRotation;
-       var gridDockAngle = gridDockXform.LocalRotation.Opposite();
-
-       var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
-       var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
-       Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
-       shuttleDockedAABB = matty.TransformBox(shuttleAABB);
-       // Rounding moment
-       shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
-
-       if (!ValidSpawn(gridUid, grid, shuttleDockedAABB.Value))
-           return false;
-
-       gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
-       return true;
-   }
-
    private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentStartup args)
    {
        AddEmergencyShuttle(component);
@@ -233,6 +194,7 @@ public sealed partial class ShuttleSystem
 
    private void OnRoundStart(RoundStartingEvent ev)
    {
+       CleanupEmergencyConsole();
        SetupEmergencyShuttle();
    }
 
@@ -241,7 +203,8 @@ public sealed partial class ShuttleSystem
    /// </summary>
    public void CallEmergencyShuttle()
    {
-       if (EmergencyShuttleArrived) return;
+       if (EmergencyShuttleArrived)
+           return;
 
        if (!_emergencyShuttleEnabled)
        {
@@ -255,26 +218,14 @@ public sealed partial class ShuttleSystem
        if (CentComMap != null)
          _mapManager.SetMapPaused(CentComMap.Value, false);
 
-       foreach (var comp in EntityQuery<StationDataComponent>(true))
-       {
-           CallEmergencyShuttle(comp.Owner);
-       }
+       var query = AllEntityQuery<StationDataComponent>();
 
-       _commsConsole.UpdateCommsConsoleInterface();
-   }
-
-   public List<DockingComponent> GetDocks(EntityUid uid)
-   {
-       var result = new List<DockingComponent>();
-
-       foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
+       while (query.MoveNext(out var uid, out var comp))
        {
-           if (xform.ParentUid != uid || !dock.Enabled) continue;
-
-           result.Add(dock);
+           CallEmergencyShuttle(uid);
        }
 
-       return result;
+       _commsConsole.UpdateCommsConsoleInterface();
    }
 
    private void SetupEmergencyShuttle()
@@ -293,7 +244,7 @@ public sealed partial class ShuttleSystem
            CentCom = centcomm;
 
            if (CentCom != null)
-               AddFTLDestination(CentCom.Value, false);
+               _shuttle.AddFTLDestination(CentCom.Value, false);
        }
        else
        {
@@ -332,7 +283,6 @@ public sealed partial class ShuttleSystem
 
        _shuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer;
        component.EmergencyShuttle = shuttle;
-       EnsureComp<ProtectedGridComponent>(shuttle.Value);
    }
 
    private void CleanupEmergencyShuttle()
@@ -354,27 +304,11 @@ public sealed partial class ShuttleSystem
        _mapManager.DeleteMap(CentComMap.Value);
    }
 
-   /// <summary>
-   /// Stores the data for a valid docking configuration for the emergency shuttle
-   /// </summary>
-   private sealed class DockingConfig
+   private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args)
    {
-       /// <summary>
-       /// The pairs of docks that can connect.
-       /// </summary>
-       public List<(DockingComponent DockA, DockingComponent DockB)> Docks = new();
-
-       /// <summary>
-       /// Area relative to the target grid the emergency shuttle will spawn in on.
-       /// </summary>
-       public Box2 Area;
-
-       /// <summary>
-       /// Target grid for docking.
-       /// </summary>
-       public EntityUid TargetGrid;
-
-       public EntityCoordinates Coordinates;
-       public Angle Angle;
+       if (component.LaunchTime == null)
+           return;
+
+       component.LaunchTime = component.LaunchTime.Value + args.PausedTime;
    }
 }
index d4b52de29aafb46f85ffc3f23722ce9201ed9aa8..cce035dca50d031c5fbaaceee8cf66d310ac1744 100644 (file)
@@ -12,435 +12,439 @@ using Content.Shared.Shuttles.Events;
 using Content.Shared.Shuttles.Systems;
 using Content.Shared.Tag;
 using Robust.Server.GameObjects;
+using Robust.Shared.Collections;
 using Robust.Shared.GameStates;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 
-namespace Content.Server.Shuttles.Systems
+namespace Content.Server.Shuttles.Systems;
+
+public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
 {
-    public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+    [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly ShuttleSystem _shuttle = default!;
+    [Dependency] private readonly TagSystem _tags = default!;
+    [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+    public override void Initialize()
     {
-        [Dependency] private readonly IGameTiming _timing = default!;
-        [Dependency] private readonly ActionBlockerSystem _blocker = default!;
-        [Dependency] private readonly AlertsSystem _alertsSystem = default!;
-        [Dependency] private readonly SharedPopupSystem _popup = default!;
-        [Dependency] private readonly ShuttleSystem _shuttle = default!;
-        [Dependency] private readonly TagSystem _tags = default!;
-        [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
-        public override void Initialize()
-        {
-            base.Initialize();
+        base.Initialize();
 
-            SubscribeLocalEvent<ShuttleConsoleComponent, ComponentShutdown>(OnConsoleShutdown);
-            SubscribeLocalEvent<ShuttleConsoleComponent, PowerChangedEvent>(OnConsolePowerChange);
-            SubscribeLocalEvent<ShuttleConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChange);
-            SubscribeLocalEvent<ShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnConsoleUIOpenAttempt);
-            SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleConsoleDestinationMessage>(OnDestinationMessage);
-            SubscribeLocalEvent<ShuttleConsoleComponent, BoundUIClosedEvent>(OnConsoleUIClose);
+        SubscribeLocalEvent<ShuttleConsoleComponent, ComponentShutdown>(OnConsoleShutdown);
+        SubscribeLocalEvent<ShuttleConsoleComponent, PowerChangedEvent>(OnConsolePowerChange);
+        SubscribeLocalEvent<ShuttleConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChange);
+        SubscribeLocalEvent<ShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnConsoleUIOpenAttempt);
+        SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleConsoleDestinationMessage>(OnDestinationMessage);
+        SubscribeLocalEvent<ShuttleConsoleComponent, BoundUIClosedEvent>(OnConsoleUIClose);
 
-            SubscribeLocalEvent<DockEvent>(OnDock);
-            SubscribeLocalEvent<UndockEvent>(OnUndock);
+        SubscribeLocalEvent<DockEvent>(OnDock);
+        SubscribeLocalEvent<UndockEvent>(OnUndock);
 
-            SubscribeLocalEvent<PilotComponent, MoveEvent>(HandlePilotMove);
-            SubscribeLocalEvent<PilotComponent, ComponentGetState>(OnGetState);
+        SubscribeLocalEvent<PilotComponent, MoveEvent>(HandlePilotMove);
+        SubscribeLocalEvent<PilotComponent, ComponentGetState>(OnGetState);
 
-            SubscribeLocalEvent<FTLDestinationComponent, ComponentStartup>(OnFtlDestStartup);
-            SubscribeLocalEvent<FTLDestinationComponent, ComponentShutdown>(OnFtlDestShutdown);
-        }
+        SubscribeLocalEvent<FTLDestinationComponent, ComponentStartup>(OnFtlDestStartup);
+        SubscribeLocalEvent<FTLDestinationComponent, ComponentShutdown>(OnFtlDestShutdown);
+    }
 
-        private void OnFtlDestStartup(EntityUid uid, FTLDestinationComponent component, ComponentStartup args)
-        {
-            RefreshShuttleConsoles();
-        }
+    private void OnFtlDestStartup(EntityUid uid, FTLDestinationComponent component, ComponentStartup args)
+    {
+        RefreshShuttleConsoles();
+    }
 
-        private void OnFtlDestShutdown(EntityUid uid, FTLDestinationComponent component, ComponentShutdown args)
-        {
-            RefreshShuttleConsoles();
-        }
+    private void OnFtlDestShutdown(EntityUid uid, FTLDestinationComponent component, ComponentShutdown args)
+    {
+        RefreshShuttleConsoles();
+    }
 
-        private void OnDestinationMessage(EntityUid uid, ShuttleConsoleComponent component, ShuttleConsoleDestinationMessage args)
+    private void OnDestinationMessage(EntityUid uid, ShuttleConsoleComponent component, ShuttleConsoleDestinationMessage args)
+    {
+        if (!TryComp<FTLDestinationComponent>(args.Destination, out var dest))
         {
-            if (!TryComp<FTLDestinationComponent>(args.Destination, out var dest))
-            {
-                return;
-            }
-
-            if (!dest.Enabled)
-                return;
-
-            EntityUid? entity = uid;
-
-            var getShuttleEv = new ConsoleShuttleEvent
-            {
-                Console = uid,
-            };
-
-            RaiseLocalEvent(entity.Value, ref getShuttleEv);
-            entity = getShuttleEv.Console;
-
-            if (!TryComp<TransformComponent>(entity, out var xform) ||
-                !TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
-            {
-                return;
-            }
+            return;
+        }
 
-            if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
-                dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
-            {
-                return;
-            }
+        if (!dest.Enabled)
+            return;
 
-            if (HasComp<FTLComponent>(xform.GridUid))
-            {
-                _popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), args.Session);
-                return;
-            }
+        EntityUid? entity = uid;
 
-            if (!_shuttle.CanFTL(xform.GridUid, out var reason))
-            {
-                _popup.PopupCursor(reason, args.Session);
-                return;
-            }
+        var getShuttleEv = new ConsoleShuttleEvent
+        {
+            Console = uid,
+        };
 
-            var dock = HasComp<MapComponent>(args.Destination) && HasComp<MapGridComponent>(args.Destination);
-            var tagEv = new FTLTagEvent();
-            RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
+        RaiseLocalEvent(entity.Value, ref getShuttleEv);
+        entity = getShuttleEv.Console;
 
-            _shuttle.FTLTravel(shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
+        if (!TryComp<TransformComponent>(entity, out var xform) ||
+            !TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
+        {
+            return;
         }
 
-        private void OnDock(DockEvent ev)
+        if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
+            dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
         {
-            RefreshShuttleConsoles();
+            return;
         }
 
-        private void OnUndock(UndockEvent ev)
+        var shuttleUid = xform.GridUid.Value;
+
+        if (HasComp<FTLComponent>(shuttleUid))
         {
-            RefreshShuttleConsoles();
+            _popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), args.Session);
+            return;
         }
 
-        public void RefreshShuttleConsoles(EntityUid uid)
+        if (!_shuttle.CanFTL(xform.GridUid, out var reason))
         {
-            // TODO: Should really call this per shuttle in some instances.
-            RefreshShuttleConsoles();
+            _popup.PopupCursor(reason, args.Session);
+            return;
         }
 
-        /// <summary>
-        /// Refreshes all of the data for shuttle consoles.
-        /// </summary>
-        public void RefreshShuttleConsoles()
-        {
-            var docks = GetAllDocks();
-            var query = AllEntityQuery<ShuttleConsoleComponent>();
+        var dock = HasComp<MapComponent>(args.Destination) && HasComp<MapGridComponent>(args.Destination);
+        var tagEv = new FTLTagEvent();
+        RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
 
-            while (query.MoveNext(out var uid, out var comp))
-            {
-                UpdateState(uid, comp, docks);
-            }
-        }
+        _shuttle.FTLTravel(xform.GridUid.Value, shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
+    }
 
-        /// <summary>
-        /// Stop piloting if the window is closed.
-        /// </summary>
-        private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args)
-        {
-            if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key ||
-                args.Session.AttachedEntity is not { } user)
-            {
-                return;
-            }
+    private void OnDock(DockEvent ev)
+    {
+        RefreshShuttleConsoles();
+    }
 
-            // In case they D/C should still clean them up.
-            foreach (var comp in EntityQuery<AutoDockComponent>(true))
-            {
-                comp.Requesters.Remove(user);
-            }
+    private void OnUndock(UndockEvent ev)
+    {
+        RefreshShuttleConsoles();
+    }
 
-            RemovePilot(user);
-        }
+    public void RefreshShuttleConsoles(EntityUid uid)
+    {
+        // TODO: Should really call this per shuttle in some instances.
+        RefreshShuttleConsoles();
+    }
 
-        private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
-        {
-            if (!TryPilot(args.User, uid))
-                args.Cancel();
-        }
+    /// <summary>
+    /// Refreshes all of the data for shuttle consoles.
+    /// </summary>
+    public void RefreshShuttleConsoles()
+    {
+        var docks = GetAllDocks();
+        var query = AllEntityQuery<ShuttleConsoleComponent>();
 
-        private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
+        while (query.MoveNext(out var uid, out var comp))
         {
-            UpdateState(uid, component);
+            UpdateState(uid, docks);
         }
+    }
 
-        private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
+    /// <summary>
+    /// Stop piloting if the window is closed.
+    /// </summary>
+    private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args)
+    {
+        if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key ||
+            args.Session.AttachedEntity is not { } user)
         {
-            UpdateState(uid, component);
+            return;
         }
 
-        private bool TryPilot(EntityUid user, EntityUid uid)
+        // In case they D/C should still clean them up.
+        foreach (var comp in EntityQuery<AutoDockComponent>(true))
         {
-            if (!_tags.HasTag(user, "CanPilot") ||
-                !TryComp<ShuttleConsoleComponent>(uid, out var component) ||
-                !this.IsPowered(uid, EntityManager) ||
-                !Transform(uid).Anchored ||
-                !_blocker.CanInteract(user, uid))
-            {
-                return false;
-            }
+            comp.Requesters.Remove(user);
+        }
 
-            var pilotComponent = EntityManager.EnsureComponent<PilotComponent>(user);
-            var console = pilotComponent.Console;
+        RemovePilot(user);
+    }
 
-            if (console != null)
-            {
-                RemovePilot(user, pilotComponent);
+    private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
+    {
+        if (!TryPilot(args.User, uid))
+            args.Cancel();
+    }
 
-                if (console == component)
-                {
-                    return false;
-                }
-            }
+    private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
+    {
+        UpdateState(uid);
+    }
 
-            AddPilot(user, component);
-            return true;
-        }
+    private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
+    {
+        UpdateState(uid);
+    }
 
-        private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args)
+    private bool TryPilot(EntityUid user, EntityUid uid)
+    {
+        if (!_tags.HasTag(user, "CanPilot") ||
+            !TryComp<ShuttleConsoleComponent>(uid, out var component) ||
+            !this.IsPowered(uid, EntityManager) ||
+            !Transform(uid).Anchored ||
+            !_blocker.CanInteract(user, uid))
         {
-            args.State = new PilotComponentState(component.Console?.Owner);
+            return false;
         }
 
-        /// <summary>
-        /// Returns the position and angle of all dockingcomponents.
-        /// </summary>
-        private List<DockingInterfaceState> GetAllDocks()
+        var pilotComponent = EnsureComp<PilotComponent>(user);
+        var console = pilotComponent.Console;
+
+        if (console != null)
         {
-            // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
-            var result = new List<DockingInterfaceState>();
-            var query = AllEntityQuery<DockingComponent, TransformComponent>();
+            RemovePilot(user, pilotComponent);
 
-            while (query.MoveNext(out var uid, out var comp, out var xform))
+            if (console == component)
             {
-                if (xform.ParentUid != xform.GridUid)
-                    continue;
-
-                var state = new DockingInterfaceState()
-                {
-                    Coordinates = xform.Coordinates,
-                    Angle = xform.LocalRotation,
-                    Entity = uid,
-                    Connected = comp.Docked,
-                    Color = comp.RadarColor,
-                    HighlightedColor = comp.HighlightedRadarColor,
-                };
-                result.Add(state);
+                return false;
             }
-
-            return result;
         }
 
-        private void UpdateState(EntityUid consoleUid, ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null)
+        AddPilot(user, component);
+        return true;
+    }
+
+    private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args)
+    {
+        args.State = new PilotComponentState(component.Console?.Owner);
+    }
+
+    /// <summary>
+    /// Returns the position and angle of all dockingcomponents.
+    /// </summary>
+    private List<DockingInterfaceState> GetAllDocks()
+    {
+        // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
+        var result = new List<DockingInterfaceState>();
+        var query = AllEntityQuery<DockingComponent, TransformComponent>();
+
+        while (query.MoveNext(out var uid, out var comp, out var xform))
         {
-            EntityUid? entity = consoleUid;
+            if (xform.ParentUid != xform.GridUid)
+                continue;
 
-            var getShuttleEv = new ConsoleShuttleEvent
+            var state = new DockingInterfaceState()
             {
-                Console = entity,
+                Coordinates = xform.Coordinates,
+                Angle = xform.LocalRotation,
+                Entity = uid,
+                Connected = comp.Docked,
+                Color = comp.RadarColor,
+                HighlightedColor = comp.HighlightedRadarColor,
             };
+            result.Add(state);
+        }
 
-            RaiseLocalEvent(entity.Value, ref getShuttleEv);
-            entity = getShuttleEv.Console;
-
-            TryComp<TransformComponent>(entity, out var consoleXform);
-            TryComp<RadarConsoleComponent>(entity, out var radar);
-            var range = radar?.MaxRange ?? SharedRadarConsoleSystem.DefaultMaxRange;
-
-            var shuttleGridUid = consoleXform?.GridUid;
+        return result;
+    }
 
-            var destinations = new List<(EntityUid, string, bool)>();
-            var ftlState = FTLState.Available;
-            var ftlTime = TimeSpan.Zero;
+    private void UpdateState(EntityUid consoleUid, List<DockingInterfaceState>? docks = null)
+    {
+        EntityUid? entity = consoleUid;
 
-            if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
-            {
-                ftlState = shuttleFtl.State;
-                ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
-            }
+        var getShuttleEv = new ConsoleShuttleEvent
+        {
+            Console = entity,
+        };
 
-            // Mass too large
-            if (entity != null && shuttleGridUid != null &&
-                (!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
-            {
-                var metaQuery = GetEntityQuery<MetaDataComponent>();
+        RaiseLocalEvent(entity.Value, ref getShuttleEv);
+        entity = getShuttleEv.Console;
 
-                // Can't go anywhere when in FTL.
-                var locked = shuttleFtl != null || Paused(shuttleGridUid.Value);
+        TryComp<TransformComponent>(entity, out var consoleXform);
+        TryComp<RadarConsoleComponent>(entity, out var radar);
+        var range = radar?.MaxRange ?? SharedRadarConsoleSystem.DefaultMaxRange;
 
-                // Can't cache it because it may have a whitelist for the particular console.
-                // Include paused as we still want to show CentCom.
-                var destQuery = AllEntityQuery<FTLDestinationComponent>();
+        var shuttleGridUid = consoleXform?.GridUid;
 
-                while (destQuery.MoveNext(out var destUid, out var comp))
-                {
-                    // Can't warp to itself or if it's not on the whitelist (console or shuttle).
-                    if (destUid == shuttleGridUid ||
-                        comp.Whitelist?.IsValid(entity.Value) == false &&
-                        (shuttleGridUid == null || comp.Whitelist?.IsValid(shuttleGridUid.Value, EntityManager) == false))
-                    {
-                        continue;
-                    }
-
-                    var meta = metaQuery.GetComponent(destUid);
-                    var name = meta.EntityName;
-
-                    if (string.IsNullOrEmpty(name))
-                        name = Loc.GetString("shuttle-console-unknown");
-
-                    var canTravel = !locked &&
-                                    comp.Enabled &&
-                                    (!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown);
-
-                    // Can't travel to same map (yet)
-                    if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
-                    {
-                        canTravel = false;
-                    }
-
-                    destinations.Add((destUid, name, canTravel));
-                }
-            }
+        var destinations = new List<(EntityUid, string, bool)>();
+        var ftlState = FTLState.Available;
+        var ftlTime = TimeSpan.Zero;
 
-            docks ??= GetAllDocks();
-
-            _ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
-                ?.SetState(new ShuttleConsoleBoundInterfaceState(
-                    ftlState,
-                    ftlTime,
-                    destinations,
-                    range,
-                    consoleXform?.Coordinates,
-                    consoleXform?.LocalRotation,
-                    docks));
+        if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
+        {
+            ftlState = shuttleFtl.State;
+            ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
         }
 
-        public override void Update(float frameTime)
+        // Mass too large
+        if (entity != null && shuttleGridUid != null &&
+            (!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
         {
-            base.Update(frameTime);
+            var metaQuery = GetEntityQuery<MetaDataComponent>();
+
+            // Can't go anywhere when in FTL.
+            var locked = shuttleFtl != null || Paused(shuttleGridUid.Value);
 
-            var toRemove = new RemQueue<PilotComponent>();
-            var query = EntityQueryEnumerator<PilotComponent>();
+            // Can't cache it because it may have a whitelist for the particular console.
+            // Include paused as we still want to show CentCom.
+            var destQuery = AllEntityQuery<FTLDestinationComponent>();
 
-            while (query.MoveNext(out var uid, out var comp))
+            while (destQuery.MoveNext(out var destUid, out var comp))
             {
-                if (comp.Console == null)
+                // Can't warp to itself or if it's not on the whitelist (console or shuttle).
+                if (destUid == shuttleGridUid ||
+                    comp.Whitelist?.IsValid(entity.Value) == false &&
+                    (shuttleGridUid == null || comp.Whitelist?.IsValid(shuttleGridUid.Value, EntityManager) == false))
+                {
                     continue;
+                }
 
-                if (!_blocker.CanInteract(uid, comp.Console.Owner))
+                var meta = metaQuery.GetComponent(destUid);
+                var name = meta.EntityName;
+
+                if (string.IsNullOrEmpty(name))
+                    name = Loc.GetString("shuttle-console-unknown");
+
+                var canTravel = !locked &&
+                                comp.Enabled &&
+                                (!TryComp<FTLComponent>(destUid, out var ftl) || ftl.State == FTLState.Cooldown);
+
+                // Can't travel to same map (yet)
+                if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
                 {
-                    toRemove.Add(comp);
+                    canTravel = false;
                 }
-            }
 
-            foreach (var comp in toRemove)
-            {
-                RemovePilot(comp.Owner, comp);
+                destinations.Add((destUid, name, canTravel));
             }
         }
 
-        /// <summary>
-        /// If pilot is moved then we'll stop them from piloting.
-        /// </summary>
-        private void HandlePilotMove(EntityUid uid, PilotComponent component, ref MoveEvent args)
+        docks ??= GetAllDocks();
+
+        _ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
+            ?.SetState(new ShuttleConsoleBoundInterfaceState(
+                ftlState,
+                ftlTime,
+                destinations,
+                range,
+                consoleXform?.Coordinates,
+                consoleXform?.LocalRotation,
+                docks));
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var toRemove = new ValueList<(EntityUid, PilotComponent)>();
+        var query = EntityQueryEnumerator<PilotComponent>();
+
+        while (query.MoveNext(out var uid, out var comp))
         {
-            if (component.Console == null || component.Position == null)
-            {
-                DebugTools.Assert(component.Position == null && component.Console == null);
-                EntityManager.RemoveComponent<PilotComponent>(uid);
-                return;
-            }
+            if (comp.Console == null)
+                continue;
 
-            if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) &&
-                distance < PilotComponent.BreakDistance)
+            if (!_blocker.CanInteract(uid, comp.Console.Owner))
             {
-                return;
+                toRemove.Add((uid, comp));
             }
-
-            RemovePilot(uid, component);
         }
 
-        protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
+        foreach (var (uid, comp) in toRemove)
         {
-            base.HandlePilotShutdown(uid, component, args);
-            RemovePilot(uid, component);
+            RemovePilot(uid, comp);
         }
+    }
 
-        private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
+    /// <summary>
+    /// If pilot is moved then we'll stop them from piloting.
+    /// </summary>
+    private void HandlePilotMove(EntityUid uid, PilotComponent component, ref MoveEvent args)
+    {
+        if (component.Console == null || component.Position == null)
         {
-            ClearPilots(component);
+            DebugTools.Assert(component.Position == null && component.Console == null);
+            EntityManager.RemoveComponent<PilotComponent>(uid);
+            return;
         }
 
-        public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
+        if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) &&
+            distance < PilotComponent.BreakDistance)
         {
-            if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
-                component.SubscribedPilots.Contains(pilotComponent))
-            {
-                return;
-            }
+            return;
+        }
 
-            if (TryComp<SharedEyeComponent>(entity, out var eye))
-            {
-                eye.Zoom = component.Zoom;
-            }
+        RemovePilot(uid, component);
+    }
 
-            component.SubscribedPilots.Add(pilotComponent);
+    protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
+    {
+        base.HandlePilotShutdown(uid, component, args);
+        RemovePilot(uid, component);
+    }
 
-            _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);
+    private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
+    {
+        ClearPilots(component);
+    }
 
-            pilotComponent.Console = component;
-            ActionBlockerSystem.UpdateCanMove(entity);
-            pilotComponent.Position = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
-            Dirty(pilotComponent);
+    public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
+    {
+        if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
+            component.SubscribedPilots.Contains(pilotComponent))
+        {
+            return;
         }
 
-        public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
+        if (TryComp<SharedEyeComponent>(entity, out var eye))
         {
-            var console = pilotComponent.Console;
+            eye.Zoom = component.Zoom;
+        }
 
-            if (console is not ShuttleConsoleComponent helmsman)
-                return;
+        component.SubscribedPilots.Add(pilotComponent);
 
-            pilotComponent.Console = null;
-            pilotComponent.Position = null;
+        _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);
 
-            if (TryComp<SharedEyeComponent>(pilotUid, out var eye))
-            {
-                eye.Zoom = new(1.0f, 1.0f);
-            }
+        pilotComponent.Console = component;
+        ActionBlockerSystem.UpdateCanMove(entity);
+        pilotComponent.Position = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
+        Dirty(pilotComponent);
+    }
 
-            if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return;
+    public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
+    {
+        var console = pilotComponent.Console;
 
-            _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
+        if (console is not ShuttleConsoleComponent helmsman)
+            return;
 
-            pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end"));
+        pilotComponent.Console = null;
+        pilotComponent.Position = null;
 
-            if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
-                EntityManager.RemoveComponent<PilotComponent>(pilotUid);
+        if (TryComp<SharedEyeComponent>(pilotUid, out var eye))
+        {
+            eye.Zoom = new(1.0f, 1.0f);
         }
 
-        public void RemovePilot(EntityUid entity)
-        {
-            if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return;
+        if (!helmsman.SubscribedPilots.Remove(pilotComponent))
+            return;
 
-            RemovePilot(entity, pilotComponent);
-        }
+        _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
+
+        _popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid);
 
-        public void ClearPilots(ShuttleConsoleComponent component)
+        if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
+            EntityManager.RemoveComponent<PilotComponent>(pilotUid);
+    }
+
+    public void RemovePilot(EntityUid entity)
+    {
+        if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent))
+            return;
+
+        RemovePilot(entity, pilotComponent);
+    }
+
+    public void ClearPilots(ShuttleConsoleComponent component)
+    {
+        while (component.SubscribedPilots.TryGetValue(0, out var pilot))
         {
-            while (component.SubscribedPilots.TryGetValue(0, out var pilot))
-            {
-                RemovePilot(pilot.Owner, pilot);
-            }
+            RemovePilot(pilot.Owner, pilot);
         }
     }
 }
index 369803b497493dd1bc236a270c04a910f266fc73..d613e51eb74e78cd710b0742e0c23b3d26fa67b1 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Doors.Systems;
 using Content.Server.Shuttles.Components;
 using Content.Server.Station.Systems;
-using Content.Server.Stunnable;
 using Content.Shared.Parallax;
 using Content.Shared.Shuttles.Systems;
 using Content.Shared.StatusEffect;
@@ -29,18 +28,11 @@ public sealed partial class ShuttleSystem
      * This is a way to move a shuttle from one location to another, via an intermediate map for fanciness.
      */
 
-    [Dependency] private readonly AirlockSystem _airlock = default!;
-    [Dependency] private readonly DoorSystem _doors = default!;
-    [Dependency] private readonly ShuttleConsoleSystem _console = default!;
-    [Dependency] private readonly SharedTransformSystem _transform = default!;
-    [Dependency] private readonly StunSystem _stuns = default!;
-    [Dependency] private readonly ThrusterSystem _thruster = default!;
-
     private MapId? _hyperSpaceMap;
 
-    private const float DefaultStartupTime = 5.5f;
-    private const float DefaultTravelTime = 30f;
-    private const float DefaultArrivalTime = 5f;
+    public const float DefaultStartupTime = 5.5f;
+    public const float DefaultTravelTime = 30f;
+    public const float DefaultArrivalTime = 5f;
     private const float FTLCooldown = 30f;
     private const float ShuttleFTLRange = 100f;
 
@@ -106,14 +98,17 @@ public sealed partial class ShuttleSystem
             return true;
         }
 
-        var bounds = xform.WorldMatrix.TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
+        var bounds = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
         var bodyQuery = GetEntityQuery<PhysicsComponent>();
 
         foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
         {
-            if (grid.Owner == other.Owner ||
+            if (uid == other.Owner ||
                 !bodyQuery.TryGetComponent(other.Owner, out var body) ||
-                body.Mass < ShuttleFTLMassThreshold) continue;
+                body.Mass < ShuttleFTLMassThreshold)
+            {
+                continue;
+            }
 
             reason = Loc.GetString("shuttle-console-proximity");
             return false;
@@ -127,7 +122,8 @@ public sealed partial class ShuttleSystem
     /// </summary>
     public FTLDestinationComponent AddFTLDestination(EntityUid uid, bool enabled)
     {
-        if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled) return destination;
+        if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled)
+            return destination;
 
         destination = EnsureComp<FTLDestinationComponent>(uid);
 
@@ -146,19 +142,22 @@ public sealed partial class ShuttleSystem
     {
         if (!RemComp<FTLDestinationComponent>(uid))
             return;
+
         _console.RefreshShuttleConsoles();
     }
 
     /// <summary>
     /// Moves a shuttle from its current position to the target one. Goes through the hyperspace map while the timer is running.
     /// </summary>
-    public void FTLTravel(ShuttleComponent component,
+    public void FTLTravel(
+        EntityUid shuttleUid,
+        ShuttleComponent component,
         EntityCoordinates coordinates,
         float startupTime = DefaultStartupTime,
         float hyperspaceTime = DefaultTravelTime,
         string? priorityTag = null)
     {
-        if (!TrySetupFTL(component, out var hyperspace))
+        if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
            return;
 
         hyperspace.StartupTime = startupTime;
@@ -173,14 +172,16 @@ public sealed partial class ShuttleSystem
     /// <summary>
     /// Moves a shuttle from its current position to docked on the target one. Goes through the hyperspace map while the timer is running.
     /// </summary>
-    public void FTLTravel(ShuttleComponent component,
+    public void FTLTravel(
+        EntityUid shuttleUid,
+        ShuttleComponent component,
         EntityUid target,
         float startupTime = DefaultStartupTime,
         float hyperspaceTime = DefaultTravelTime,
         bool dock = false,
         string? priorityTag = null)
     {
-        if (!TrySetupFTL(component, out var hyperspace))
+        if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
             return;
 
         hyperspace.StartupTime = startupTime;
@@ -192,9 +193,8 @@ public sealed partial class ShuttleSystem
         _console.RefreshShuttleConsoles();
     }
 
-    private bool TrySetupFTL(ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
+    private bool TrySetupFTL(EntityUid uid, ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
     {
-        var uid = shuttle.Owner;
         component = null;
 
         if (HasComp<FTLComponent>(uid))
@@ -310,9 +310,9 @@ public sealed partial class ShuttleSystem
                     if (comp.TargetUid != null && shuttle != null)
                     {
                         if (comp.Dock)
-                            TryFTLDock(shuttle, comp.TargetUid.Value, comp.PriorityTag);
+                            TryFTLDock(uid, shuttle, comp.TargetUid.Value, comp.PriorityTag);
                         else
-                            TryFTLProximity(shuttle, comp.TargetUid.Value);
+                            TryFTLProximity(uid, shuttle, comp.TargetUid.Value);
 
                         mapId = Transform(comp.TargetUid.Value).MapID;
                     }
@@ -350,7 +350,7 @@ public sealed partial class ShuttleSystem
                         comp.TravelStream = null;
                     }
 
-                    SoundSystem.Play(_arrivalSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), _arrivalSound.Params);
+                    _audio.PlayGlobal(_arrivalSound, Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), true);
 
                     if (TryComp<FTLDestinationComponent>(uid, out var dest))
                     {
@@ -380,7 +380,9 @@ public sealed partial class ShuttleSystem
     {
         foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
         {
-            if (xform.ParentUid != uid || dock.Enabled == enabled) continue;
+            if (xform.ParentUid != uid || dock.Enabled == enabled)
+                continue;
+
             _dockSystem.Undock(dock);
             dock.Enabled = enabled;
         }
@@ -388,25 +390,30 @@ public sealed partial class ShuttleSystem
 
     private void SetDockBolts(EntityUid uid, bool enabled)
     {
-        foreach (var (_, door, xform) in EntityQuery<DockingComponent, AirlockComponent, TransformComponent>(true))
+        var query = AllEntityQuery<DockingComponent, AirlockComponent, TransformComponent>();
+
+        while (query.MoveNext(out var doorUid, out _, out var door, out var xform))
         {
-            if (xform.ParentUid != uid) continue;
+            if (xform.ParentUid != uid)
+                continue;
 
-            _doors.TryClose(door.Owner);
-            _airlock.SetBoltsWithAudio(door.Owner, door, enabled);
+            _doors.TryClose(doorUid);
+            _airlock.SetBoltsWithAudio(doorUid, door, enabled);
         }
     }
 
     private float GetSoundRange(EntityUid uid)
     {
-        if (!_mapManager.TryGetGrid(uid, out var grid)) return 4f;
+        if (!_mapManager.TryGetGrid(uid, out var grid))
+            return 4f;
 
         return MathF.Max(grid.LocalAABB.Width, grid.LocalAABB.Height) + 12.5f;
     }
 
     private void SetupHyperspace()
     {
-        if (_hyperSpaceMap != null) return;
+        if (_hyperSpaceMap != null)
+            return;
 
         _hyperSpaceMap = _mapManager.CreateMap();
         _sawmill.Info($"Setup hyperspace map at {_hyperSpaceMap.Value}");
@@ -453,7 +460,8 @@ public sealed partial class ShuttleSystem
 
         while (childEnumerator.MoveNext(out var child))
         {
-            if (!buckleQuery.TryGetComponent(child.Value, out var buckle) || buckle.Buckled) continue;
+            if (!buckleQuery.TryGetComponent(child.Value, out var buckle) || buckle.Buckled)
+                continue;
 
             toKnock.Add(child.Value);
         }
@@ -462,10 +470,9 @@ public sealed partial class ShuttleSystem
     /// <summary>
     /// Tries to dock with the target grid, otherwise falls back to proximity.
     /// </summary>
-    /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
-    public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
+    public bool TryFTLDock(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
     {
-        if (!TryComp<TransformComponent>(component.Owner, out var xform) ||
+        if (!TryComp<TransformComponent>(shuttleUid, out var shuttleXform) ||
             !TryComp<TransformComponent>(targetUid, out var targetXform) ||
             targetXform.MapUid == null ||
             !targetXform.MapUid.Value.IsValid())
@@ -473,42 +480,49 @@ public sealed partial class ShuttleSystem
             return false;
         }
 
-        var config = GetDockingConfig(component, targetUid, priorityTag);
+        var config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag);
 
         if (config != null)
         {
-           // Set position
-           xform.Coordinates = config.Coordinates;
-           xform.WorldRotation = config.Angle;
-
-           // Connect everything
-           foreach (var (dockA, dockB) in config.Docks)
-           {
-               _dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
-           }
-
-           return true;
+            FTLDock(config, shuttleXform);
+            return true;
         }
 
-        TryFTLProximity(component, targetUid, xform, targetXform);
+        TryFTLProximity(shuttleUid, component, targetUid, shuttleXform, targetXform);
         return false;
     }
 
+    /// <summary>
+    /// Forces an FTL dock.
+    /// </summary>
+    public void FTLDock(DockingConfig config, TransformComponent shuttleXform)
+    {
+        // Set position
+        shuttleXform.Coordinates = config.Coordinates;
+        _transform.SetWorldRotation(shuttleXform, config.Angle);
+
+        // Connect everything
+        foreach (var (dockAUid, dockBUid, dockA, dockB) in config.Docks)
+        {
+            _dockSystem.Dock(dockAUid, dockA, dockBUid, dockB);
+        }
+    }
+
     /// <summary>
     /// Tries to arrive nearby without overlapping with other grids.
     /// </summary>
-    public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
+    public bool TryFTLProximity(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
     {
         if (!Resolve(targetUid, ref targetXform) ||
             targetXform.MapUid == null ||
             !targetXform.MapUid.Value.IsValid() ||
-            !Resolve(component.Owner, ref xform))
+            !Resolve(shuttleUid, ref xform))
         {
             return false;
         }
 
         var xformQuery = GetEntityQuery<TransformComponent>();
-        var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
+        var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
         Box2 targetLocalAABB;
 
         // Spawn nearby.
@@ -534,7 +548,8 @@ public sealed partial class ShuttleSystem
         {
             foreach (var grid in _mapManager.FindGridsIntersecting(mapId, targetAABB))
             {
-                if (!nearbyGrids.Add(grid.Owner)) continue;
+                if (!nearbyGrids.Add(grid.Owner))
+                    continue;
 
                 targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
                     .TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
@@ -557,7 +572,8 @@ public sealed partial class ShuttleSystem
             foreach (var grid in _mapManager.GetAllGrids())
             {
                 // Don't add anymore as it is irrelevant, but that doesn't mean we need to re-do existing work.
-                if (nearbyGrids.Contains(grid.Owner)) continue;
+                if (nearbyGrids.Contains(grid.Owner))
+                    continue;
 
                 targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
                     .TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
@@ -568,10 +584,10 @@ public sealed partial class ShuttleSystem
 
         Vector2 spawnPos;
 
-        if (TryComp<PhysicsComponent>(component.Owner, out var shuttleBody))
+        if (TryComp<PhysicsComponent>(shuttleUid, out var shuttleBody))
         {
-            _physics.SetLinearVelocity(component.Owner, Vector2.Zero, body: shuttleBody);
-            _physics.SetAngularVelocity(component.Owner, 0f, body: shuttleBody);
+            _physics.SetLinearVelocity(shuttleUid, Vector2.Zero, body: shuttleBody);
+            _physics.SetAngularVelocity(shuttleUid, 0f, body: shuttleBody);
         }
 
         // TODO: This is pretty crude for multiple landings.
@@ -604,138 +620,4 @@ public sealed partial class ShuttleSystem
 
         return true;
     }
-
-    /// <summary>
-   /// Checks whether the emergency shuttle can warp to the specified position.
-   /// </summary>
-   private bool ValidSpawn(EntityUid gridUid, MapGridComponent grid, Box2 area)
-   {
-       // If the target is a map then any tile is valid.
-       // TODO: We already need the entities-under check
-       if (HasComp<MapComponent>(gridUid))
-           return true;
-
-       return !grid.GetLocalTilesIntersecting(area).Any();
-   }
-
-   /// <summary>
-   /// Tries to get a valid docking configuration for the shuttle to the target grid.
-   /// </summary>
-   /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
-   private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid, string? priorityTag = null)
-   {
-       var gridDocks = GetDocks(targetGrid);
-
-       if (gridDocks.Count <= 0)
-           return null;
-
-       var xformQuery = GetEntityQuery<TransformComponent>();
-       var targetGridGrid = Comp<MapGridComponent>(targetGrid);
-       var targetGridXform = xformQuery.GetComponent(targetGrid);
-       var targetGridAngle = targetGridXform.WorldRotation.Reduced();
-
-       var shuttleDocks = GetDocks(component.Owner);
-       var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
-
-       var validDockConfigs = new List<DockingConfig>();
-
-       if (shuttleDocks.Count > 0)
-       {
-           // We'll try all combinations of shuttle docks and see which one is most suitable
-           foreach (var shuttleDock in shuttleDocks)
-           {
-               var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);
-
-               foreach (var gridDock in gridDocks)
-               {
-                   var gridXform = xformQuery.GetComponent(gridDock.Owner);
-
-                   if (!CanDock(
-                           shuttleDock, shuttleDockXform,
-                           gridDock, gridXform,
-                           targetGridAngle,
-                           shuttleAABB,
-                           targetGrid,
-                           targetGridGrid,
-                           out var dockedAABB,
-                           out var matty,
-                           out var targetAngle)) continue;
-
-                   // Can't just use the AABB as we want to get bounds as tight as possible.
-                   var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
-                   spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
-
-                   var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
-
-                   // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
-                   if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
-                           dockedBounds).Any(o => o.Owner != targetGrid))
-                   {
-                       continue;
-                   }
-
-                   // Alright well the spawn is valid now to check how many we can connect
-                   // Get the matrix for each shuttle dock and test it against the grid docks to see
-                   // if the connected position / direction matches.
-
-                   var dockedPorts = new List<(DockingComponent DockA, DockingComponent DockB)>()
-                   {
-                       (shuttleDock, gridDock),
-                   };
-
-                   foreach (var other in shuttleDocks)
-                   {
-                       if (other == shuttleDock) continue;
-
-                       foreach (var otherGrid in gridDocks)
-                       {
-                           if (otherGrid == gridDock) continue;
-
-                           if (!CanDock(
-                                   other,
-                                   xformQuery.GetComponent(other.Owner),
-                                   otherGrid,
-                                   xformQuery.GetComponent(otherGrid.Owner),
-                                   targetGridAngle,
-                                   shuttleAABB,
-                                   targetGrid,
-                                   targetGridGrid,
-                                   out var otherDockedAABB,
-                                   out _,
-                                   out var otherTargetAngle) ||
-                               !otherDockedAABB.Equals(dockedAABB) ||
-                               !targetAngle.Equals(otherTargetAngle)) continue;
-
-                           dockedPorts.Add((other, otherGrid));
-                       }
-                   }
-
-                   validDockConfigs.Add(new DockingConfig()
-                   {
-                       Docks = dockedPorts,
-                       Area = dockedAABB.Value,
-                       Coordinates = spawnPosition,
-                       Angle = targetAngle,
-                   });
-               }
-           }
-       }
-
-       if (validDockConfigs.Count <= 0)
-           return null;
-
-       // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
-       validDockConfigs = validDockConfigs
-           .OrderByDescending(x => x.Docks.Any(docks =>
-               TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
-               priority.Tag?.Equals(priorityTag) == true))
-           .ThenByDescending(x => x.Docks.Count)
-           .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
-
-       var location = validDockConfigs.First();
-       location.TargetGrid = targetGrid;
-       // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
-
-       return location;
-   }
 }
diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs
new file mode 100644 (file)
index 0000000..ac66d2a
--- /dev/null
@@ -0,0 +1,69 @@
+using Content.Server.Shuttles.Components;
+
+namespace Content.Server.Shuttles.Systems;
+
+public sealed partial class ShuttleSystem
+{
+    private void InitializeGridFills()
+    {
+        SubscribeLocalEvent<GridFillComponent, MapInitEvent>(OnGridFillMapInit);
+    }
+
+    private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args)
+    {
+        if (!TryComp<DockingComponent>(uid, out var dock) ||
+            !TryComp<TransformComponent>(uid, out var xform) ||
+            xform.GridUid == null)
+        {
+            return;
+        }
+
+        // Spawn on a dummy map and try to dock if possible, otherwise dump it.
+        var mapId = _mapManager.CreateMap();
+        var valid = false;
+
+        if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) &&
+            ent.Count == 1 &&
+            TryComp<TransformComponent>(ent[0], out var shuttleXform))
+        {
+            var escape = GetSingleDock(ent[0]);
+
+            if (escape != null)
+            {
+                var config = _dockSystem.GetDockingConfig(ent[0], xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock);
+
+                if (config != null)
+                {
+                    FTLDock(config, shuttleXform);
+                    valid = true;
+                }
+            }
+        }
+
+        if (!valid)
+        {
+            _sawmill.Error($"Error loading gridfill dock for {ToPrettyString(uid)} / {component.Path}");
+        }
+
+        _mapManager.DeleteMap(mapId);
+    }
+
+    private (EntityUid Entity, DockingComponent Component)? GetSingleDock(EntityUid uid)
+    {
+        var dockQuery = GetEntityQuery<DockingComponent>();
+        var xformQuery = GetEntityQuery<TransformComponent>();
+        var xform = xformQuery.GetComponent(uid);
+
+        var rator = xform.ChildEnumerator;
+
+        while (rator.MoveNext(out var child))
+        {
+            if (!dockQuery.TryGetComponent(child, out var dock))
+                continue;
+
+            return (child.Value, dock);
+        }
+
+        return null;
+    }
+}
index 1673058bb7b28db0da8bb00f8246a4057d85311c..f1ab8c078d33217ffb70a238e6dcd941c37d658a 100644 (file)
@@ -1,15 +1,16 @@
+using Content.Server.Doors.Systems;
 using Content.Server.Shuttles.Components;
-using Content.Shared.CCVar;
+using Content.Server.Stunnable;
 using Content.Shared.GameTicking;
 using Content.Shared.Shuttles.Systems;
 using JetBrains.Annotations;
 using Robust.Server.GameObjects;
-using Robust.Shared.Configuration;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Systems;
+using Robust.Shared.Random;
 
 namespace Content.Server.Shuttles.Systems
 {
@@ -17,9 +18,18 @@ namespace Content.Server.Shuttles.Systems
     public sealed partial class ShuttleSystem : SharedShuttleSystem
     {
         [Dependency] private readonly IMapManager _mapManager = default!;
+        [Dependency] private readonly IRobustRandom _random = default!;
+        [Dependency] private readonly AirlockSystem _airlock = default!;
+        [Dependency] private readonly DockingSystem _dockSystem = default!;
+        [Dependency] private readonly DoorSystem _doors = default!;
         [Dependency] private readonly FixtureSystem _fixtures = default!;
+        [Dependency] private readonly MapLoaderSystem _loader = default!;
         [Dependency] private readonly SharedAudioSystem _audio = default!;
         [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+        [Dependency] private readonly SharedTransformSystem _transform = default!;
+        [Dependency] private readonly ShuttleConsoleSystem _console = default!;
+        [Dependency] private readonly StunSystem _stuns = default!;
+        [Dependency] private readonly ThrusterSystem _thruster = default!;
         [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
 
         private ISawmill _sawmill = default!;
@@ -34,9 +44,8 @@ namespace Content.Server.Shuttles.Systems
             base.Initialize();
             _sawmill = Logger.GetSawmill("shuttles");
 
-            InitializeEmergencyConsole();
-            InitializeEscape();
             InitializeFTL();
+            InitializeGridFills();
             InitializeIFF();
             InitializeImpact();
 
@@ -53,24 +62,14 @@ namespace Content.Server.Shuttles.Systems
         public override void Update(float frameTime)
         {
             base.Update(frameTime);
-            UpdateEmergencyConsole(frameTime);
             UpdateHyperspace(frameTime);
         }
 
         private void OnRoundRestart(RoundRestartCleanupEvent ev)
         {
-            CleanupEmergencyConsole();
-            CleanupEmergencyShuttle();
             CleanupHyperspace();
         }
 
-        public override void Shutdown()
-        {
-            base.Shutdown();
-            ShutdownEscape();
-            ShutdownEmergencyConsole();
-        }
-
         private void OnShuttleAdd(EntityUid uid, ShuttleComponent component, ComponentAdd args)
         {
             // Easier than doing it in the comp and they don't have constructors.
@@ -83,7 +82,8 @@ namespace Content.Server.Shuttles.Systems
         private void OnGridFixtureChange(GridFixtureChangeEvent args)
         {
             // Look this is jank but it's a placeholder until we design it.
-            if (args.NewFixtures.Count == 0) return;
+            if (args.NewFixtures.Count == 0)
+                return;
 
             var uid = args.NewFixtures[0].Body.Owner;
             var manager = Comp<FixturesComponent>(uid);
@@ -107,12 +107,12 @@ namespace Content.Server.Shuttles.Systems
 
         private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args)
         {
-            if (!EntityManager.HasComponent<MapGridComponent>(component.Owner))
+            if (!EntityManager.HasComponent<MapGridComponent>(uid))
             {
                 return;
             }
 
-            if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
+            if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
             {
                 return;
             }
@@ -125,7 +125,8 @@ namespace Content.Server.Shuttles.Systems
 
         public void Toggle(EntityUid uid, ShuttleComponent component)
         {
-            if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent)) return;
+            if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
+                return;
 
             component.Enabled = !component.Enabled;
 
@@ -164,7 +165,7 @@ namespace Content.Server.Shuttles.Systems
             // None of the below is necessary for any cleanup if we're just deleting.
             if (EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
 
-            if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
+            if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
             {
                 return;
             }
index 6c87f95494e36f5ffcd7f6a6010b18aa81936ed5..60d58d671fd2f26ae04d1d018632d53c979f1aff 100644 (file)
@@ -28,6 +28,6 @@ public sealed class StationDataComponent : Component
     /// <summary>
     /// The emergency shuttle assigned to this station.
     /// </summary>
-    [ViewVariables, Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadWrite)]
+    [ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)]
     public EntityUid? EmergencyShuttle;
 }
index cc4baac71721fb323a579308cd0c3e1e9b29ad4f..8ff3e7467c86524032ff44893bd1ebf04b86ee9c 100644 (file)
@@ -68,12 +68,12 @@ namespace Content.Shared.Popups
         public abstract void PopupEntity(string message, EntityUid uid, PopupType type=PopupType.Small);
 
         /// <summary>
-        ///     Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
+        ///     Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
         /// </summary>
         public abstract void PopupEntity(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small);
 
         /// <summary>
-        ///     Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
+        ///     Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
         /// </summary>
         public abstract void PopupEntity(string message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small);
 
diff --git a/Resources/Maps/Shuttles/escape_pod_small.yml b/Resources/Maps/Shuttles/escape_pod_small.yml
new file mode 100644 (file)
index 0000000..fa995e2
--- /dev/null
@@ -0,0 +1,406 @@
+meta:
+  format: 3
+  name: DemoStation
+  author: Space-Wizards
+  postmapinit: false
+tilemap:
+  0: Space
+  1: FloorArcadeBlue
+  2: FloorArcadeBlue2
+  3: FloorArcadeRed
+  4: FloorAsteroidCoarseSand0
+  5: FloorAsteroidCoarseSandDug
+  6: FloorAsteroidIronsand1
+  7: FloorAsteroidIronsand2
+  8: FloorAsteroidIronsand3
+  9: FloorAsteroidIronsand4
+  10: FloorAsteroidSand
+  11: FloorAsteroidTile
+  12: FloorBar
+  13: FloorBasalt
+  14: FloorBlue
+  15: FloorBlueCircuit
+  16: FloorBoxing
+  17: FloorCarpetClown
+  18: FloorCarpetOffice
+  19: FloorCave
+  20: FloorCaveDrought
+  21: FloorClown
+  22: FloorDark
+  23: FloorDarkDiagonal
+  24: FloorDarkDiagonalMini
+  25: FloorDarkHerringbone
+  26: FloorDarkMini
+  27: FloorDarkMono
+  28: FloorDarkOffset
+  29: FloorDarkPavement
+  30: FloorDarkPavementVertical
+  31: FloorDarkPlastic
+  32: FloorDesert
+  33: FloorDirt
+  34: FloorEighties
+  35: FloorElevatorShaft
+  36: FloorFlesh
+  37: FloorFreezer
+  38: FloorGlass
+  39: FloorGold
+  40: FloorGrass
+  41: FloorGrassDark
+  42: FloorGrassJungle
+  43: FloorGrassLight
+  44: FloorGreenCircuit
+  45: FloorGym
+  46: FloorHydro
+  47: FloorKitchen
+  48: FloorLaundry
+  49: FloorLino
+  50: FloorLowDesert
+  51: FloorMetalDiamond
+  52: FloorMime
+  53: FloorMono
+  54: FloorPlanetDirt
+  55: FloorPlanetGrass
+  56: FloorPlastic
+  57: FloorRGlass
+  58: FloorReinforced
+  59: FloorRockVault
+  60: FloorShowroom
+  61: FloorShuttleBlue
+  62: FloorShuttleOrange
+  63: FloorShuttlePurple
+  64: FloorShuttleRed
+  65: FloorShuttleWhite
+  66: FloorSilver
+  67: FloorSnow
+  68: FloorSteel
+  69: FloorSteelDiagonal
+  70: FloorSteelDiagonalMini
+  71: FloorSteelDirty
+  72: FloorSteelHerringbone
+  73: FloorSteelMini
+  74: FloorSteelMono
+  75: FloorSteelOffset
+  76: FloorSteelPavement
+  77: FloorSteelPavementVertical
+  78: FloorTechMaint
+  79: FloorTechMaint2
+  80: FloorTechMaint3
+  81: FloorWhite
+  82: FloorWhiteDiagonal
+  83: FloorWhiteDiagonalMini
+  84: FloorWhiteHerringbone
+  85: FloorWhiteMini
+  86: FloorWhiteMono
+  87: FloorWhiteOffset
+  88: FloorWhitePavement
+  89: FloorWhitePavementVertical
+  90: FloorWhitePlastic
+  91: FloorWood
+  92: FloorWoodTile
+  93: Lattice
+  94: Plating
+entities:
+- uid: 0
+  components:
+  - type: MetaData
+  - type: EscapePod
+  - parent: invalid
+    type: Transform
+  - chunks:
+      -1,-1:
+        ind: -1,-1
+        tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAPQAAAA==
+      0,0:
+        ind: 0,0
+        tiles: XgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+      -1,0:
+        ind: -1,0
+        tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+      0,-1:
+        ind: 0,-1
+        tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+    type: MapGrid
+  - type: Broadphase
+  - angularDamping: 0.05
+    linearDamping: 0.05
+    fixedRotation: False
+    bodyType: Dynamic
+    type: Physics
+  - fixtures: []
+    type: Fixtures
+  - id: Empty
+    type: BecomesStation
+  - type: OccluderTree
+  - type: Shuttle
+  - nextUpdate: 88.8784752
+    type: GridPathfinding
+  - gravityShakeSound: !type:SoundPathSpecifier
+      path: /Audio/Effects/alert.ogg
+    type: Gravity
+  - chunkCollection:
+      version: 2
+      nodes: []
+    type: DecalGrid
+  - version: 2
+    data:
+      tiles:
+        -1,-1:
+          0: 52224
+        0,0:
+          0: 1
+        -1,0:
+          0: 12
+        0,-1:
+          0: 4352
+      uniqueMixes:
+      - volume: 2500
+        temperature: 293.15
+        moles:
+        - 21.824879
+        - 82.10312
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+        - 0
+      chunkSize: 4
+    type: GridAtmosphere
+  - type: GasTileOverlay
+  - type: RadiationGridResistance
+- uid: 1
+  type: WallShuttle
+  components:
+  - pos: -1.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 2
+  type: WallShuttle
+  components:
+  - pos: -1.5,0.5
+    parent: 0
+    type: Transform
+- uid: 3
+  type: WallShuttle
+  components:
+  - pos: 0.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 4
+  type: WallShuttle
+  components:
+  - pos: 0.5,0.5
+    parent: 0
+    type: Transform
+- uid: 5
+  type: WallShuttleDiagonal
+  components:
+  - pos: -1.5,1.5
+    parent: 0
+    type: Transform
+- uid: 6
+  type: WallShuttleDiagonal
+  components:
+  - rot: -1.5707963267948966 rad
+    pos: 0.5,1.5
+    parent: 0
+    type: Transform
+- uid: 7
+  type: ShuttleWindow
+  components:
+  - pos: -0.5,1.5
+    parent: 0
+    type: Transform
+- uid: 8
+  type: Grille
+  components:
+  - pos: -0.5,1.5
+    parent: 0
+    type: Transform
+- uid: 9
+  type: AtmosDeviceFanTiny
+  components:
+  - pos: -0.5,-1.5
+    parent: 0
+    type: Transform
+- uid: 10
+  type: Thruster
+  components:
+  - rot: 3.141592653589793 rad
+    pos: -1.5,-1.5
+    parent: 0
+    type: Transform
+  - bodyType: Static
+    type: Physics
+- uid: 11
+  type: Thruster
+  components:
+  - rot: 3.141592653589793 rad
+    pos: 0.5,-1.5
+    parent: 0
+    type: Transform
+  - bodyType: Static
+    type: Physics
+- uid: 12
+  type: AirlockExternalShuttleLocked
+  components:
+  - pos: -0.5,-1.5
+    parent: 0
+    type: Transform
+  - fixtures:
+    - shape: !type:PolygonShape
+        radius: 0.01
+        vertices:
+        - 0.49,-0.49
+        - 0.49,0.49
+        - -0.49,0.49
+        - -0.49,-0.49
+      mask:
+      - Impassable
+      - MidImpassable
+      - HighImpassable
+      - LowImpassable
+      - InteractImpassable
+      layer:
+      - MidImpassable
+      - HighImpassable
+      - BulletImpassable
+      - InteractImpassable
+      - Opaque
+      density: 100
+      hard: True
+      restitution: 0
+      friction: 0.4
+      id: null
+    - shape: !type:PhysShapeCircle
+        radius: 0.2
+        position: 0,-0.5
+      mask: []
+      layer: []
+      density: 1
+      hard: False
+      restitution: 0
+      friction: 0.4
+      id: docking
+    type: Fixtures
+- uid: 13
+  type: GeneratorWallmountAPU
+  components:
+  - pos: -1.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 14
+  type: SubstationWallBasic
+  components:
+  - pos: 0.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 15
+  type: APCBasic
+  components:
+  - rot: 1.5707963267948966 rad
+    pos: -1.5,0.5
+    parent: 0
+    type: Transform
+- uid: 16
+  type: CableHV
+  components:
+  - pos: -1.5,-0.5
+    parent: 0
+    type: Transform
+  - enabled: True
+    type: AmbientSound
+- uid: 17
+  type: CableHV
+  components:
+  - pos: -0.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 18
+  type: CableHV
+  components:
+  - pos: 0.5,-0.5
+    parent: 0
+    type: Transform
+  - enabled: True
+    type: AmbientSound
+- uid: 19
+  type: CableMV
+  components:
+  - pos: 0.5,-0.5
+    parent: 0
+    type: Transform
+  - enabled: True
+    type: AmbientSound
+- uid: 20
+  type: CableMV
+  components:
+  - pos: -0.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 21
+  type: CableMV
+  components:
+  - pos: -0.5,0.5
+    parent: 0
+    type: Transform
+- uid: 22
+  type: CableMV
+  components:
+  - pos: -1.5,0.5
+    parent: 0
+    type: Transform
+  - enabled: True
+    type: AmbientSound
+- uid: 23
+  type: CableApcExtension
+  components:
+  - pos: -1.5,0.5
+    parent: 0
+    type: Transform
+  - enabled: True
+    type: AmbientSound
+- uid: 24
+  type: CableApcExtension
+  components:
+  - pos: -0.5,0.5
+    parent: 0
+    type: Transform
+- uid: 25
+  type: CableApcExtension
+  components:
+  - pos: -0.5,-0.5
+    parent: 0
+    type: Transform
+- uid: 26
+  type: ChairPilotSeat
+  components:
+  - rot: 3.141592653589793 rad
+    pos: -0.5,-0.5
+    parent: 0
+    type: Transform
+  - bodyType: Static
+    type: Physics
+- uid: 27
+  type: ChairPilotSeat
+  components:
+  - rot: 3.141592653589793 rad
+    pos: -0.5,0.5
+    parent: 0
+    type: Transform
+  - bodyType: Static
+    type: Physics
+- uid: 28
+  type: PoweredSmallLight
+  components:
+  - rot: 1.5707963267948966 rad
+    pos: -0.5,-0.5
+    parent: 0
+    type: Transform
+  - powerLoad: 0
+    type: ApcPowerReceiver
+...
index fe0848a950ce48bbe941a598f6e974b4dbdb51ee..e5ec85282b012a171311e1fe2cb4ecf98b8caad0 100644 (file)
     - type: PriorityDock
       tag: DockArrivals
 
+- type: entity
+  parent: AirlockGlassShuttle
+  id: AirlockExternalGlassShuttleEscape
+  suffix: External, Escape 3x4, Glass, Docking
+  components:
+    - type: GridFill
+
 #HighSecDoors
 - type: entity
   parent: HighSecDoor