From 448165ffdab95fb7bd846275e42321aa1c95a080 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:54:41 +1100 Subject: [PATCH] Escape pods (#14809) * Namespace adjustments for days * pod * thanks rider * Fix the oop launch * Fixes * Fix stuff * eeeeeeeee * Fix * access * map * forgor * thing * Genericise escape pod fill --- .../Tests/PostMapInitTest.cs | 2 +- .../CommunicationsConsoleSystem.cs | 4 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 9 +- Content.Server/RoundEnd/RoundEndSystem.cs | 2 +- .../Commands/DelayShuttleRoundEndCommand.cs | 5 +- .../Shuttles/{ => Commands}/DockCommand.cs | 2 +- .../Commands/DockEmergencyShuttleCommand.cs | 4 +- .../Commands/LaunchEmergencyShuttleCommand.cs | 4 +- .../Components/ArrivalsShuttleComponent.cs | 3 +- .../Components/ArrivalsSourceComponent.cs | 4 +- .../Shuttles/Components/EscapePodComponent.cs | 14 + .../Shuttles/Components/GridFillComponent.cs | 13 + .../Components/StationArrivalsComponent.cs | 3 +- Content.Server/Shuttles/DockingConfig.cs | 28 + .../Shuttles/Systems/ArrivalsSystem.cs | 8 +- .../Systems/DockingSystem.AutoDock.cs | 4 +- .../Shuttles/Systems/DockingSystem.Shuttle.cs | 267 ++++++++ .../Shuttles/Systems/DockingSystem.cs | 2 +- ...e.cs => EmergencyShuttleSystem.Console.cs} | 87 ++- ...cyShuttle.cs => EmergencyShuttleSystem.cs} | 202 ++---- .../Shuttles/Systems/ShuttleConsoleSystem.cs | 628 +++++++++--------- .../Systems/ShuttleSystem.FasterThanLight.cs | 262 ++------ .../Systems/ShuttleSystem.GridFill.cs | 69 ++ .../Shuttles/Systems/ShuttleSystem.cs | 39 +- .../Components/StationDataComponent.cs | 2 +- Content.Shared/Popups/SharedPopupSystem.cs | 4 +- Resources/Maps/Shuttles/escape_pod_small.yml | 406 +++++++++++ .../Structures/Doors/Airlocks/access.yml | 7 + 28 files changed, 1377 insertions(+), 707 deletions(-) rename Content.Server/Shuttles/{ => Commands}/DockCommand.cs (98%) create mode 100644 Content.Server/Shuttles/Components/EscapePodComponent.cs create mode 100644 Content.Server/Shuttles/Components/GridFillComponent.cs create mode 100644 Content.Server/Shuttles/DockingConfig.cs create mode 100644 Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs rename Content.Server/Shuttles/Systems/{ShuttleSystem.EmergencyConsole.cs => EmergencyShuttleSystem.Console.cs} (81%) rename Content.Server/Shuttles/Systems/{ShuttleSystem.EmergencyShuttle.cs => EmergencyShuttleSystem.cs} (61%) create mode 100644 Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs create mode 100644 Resources/Maps/Shuttles/escape_pod_small.yml diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs index e8cc0f3fd8..c1d7c2ac48 100644 --- a/Content.IntegrationTests/Tests/PostMapInitTest.cs +++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs @@ -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(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}"); + Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}"); mapManager.DeleteMap(shuttleMap); diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index 6e2844e271..3a1df97e18 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -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 diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index ced5715de1..bf2c7946db 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -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()) { 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(shuttleId, out var shuttle)) { - _shuttleSystem.TryFTLDock(shuttle, _nukieOutpost.Value); + _shuttle.TryFTLDock(shuttleId, shuttle, _nukieOutpost.Value); } _nukiePlanet = mapId; diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index bba3fbdd3c..1479d2d316 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -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); diff --git a/Content.Server/Shuttles/Commands/DelayShuttleRoundEndCommand.cs b/Content.Server/Shuttles/Commands/DelayShuttleRoundEndCommand.cs index 05cdfa50c2..3c881dcbb5 100644 --- a/Content.Server/Shuttles/Commands/DelayShuttleRoundEndCommand.cs +++ b/Content.Server/Shuttles/Commands/DelayShuttleRoundEndCommand.cs @@ -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().GetEntitySystem(); + var system = _sysManager.GetEntitySystem(); + if (system.DelayEmergencyRoundEnd()) { shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-yes")); diff --git a/Content.Server/Shuttles/DockCommand.cs b/Content.Server/Shuttles/Commands/DockCommand.cs similarity index 98% rename from Content.Server/Shuttles/DockCommand.cs rename to Content.Server/Shuttles/Commands/DockCommand.cs index 1f6d7440a8..7a621c2b80 100644 --- a/Content.Server/Shuttles/DockCommand.cs +++ b/Content.Server/Shuttles/Commands/DockCommand.cs @@ -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 diff --git a/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs b/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs index ae8093c8fd..8febe51f5a 100644 --- a/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs +++ b/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs @@ -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().GetEntitySystem(); + var system = _sysManager.GetEntitySystem(); system.CallEmergencyShuttle(); } } diff --git a/Content.Server/Shuttles/Commands/LaunchEmergencyShuttleCommand.cs b/Content.Server/Shuttles/Commands/LaunchEmergencyShuttleCommand.cs index 20381b9017..b9dd9c20e9 100644 --- a/Content.Server/Shuttles/Commands/LaunchEmergencyShuttleCommand.cs +++ b/Content.Server/Shuttles/Commands/LaunchEmergencyShuttleCommand.cs @@ -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().GetEntitySystem(); + var system = _sysManager.GetEntitySystem(); system.EarlyLaunch(); } } diff --git a/Content.Server/Shuttles/Components/ArrivalsShuttleComponent.cs b/Content.Server/Shuttles/Components/ArrivalsShuttleComponent.cs index 564f795b33..1f6b777963 100644 --- a/Content.Server/Shuttles/Components/ArrivalsShuttleComponent.cs +++ b/Content.Server/Shuttles/Components/ArrivalsShuttleComponent.cs @@ -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")] diff --git a/Content.Server/Shuttles/Components/ArrivalsSourceComponent.cs b/Content.Server/Shuttles/Components/ArrivalsSourceComponent.cs index 549a5b2903..a1a00793f2 100644 --- a/Content.Server/Shuttles/Components/ArrivalsSourceComponent.cs +++ b/Content.Server/Shuttles/Components/ArrivalsSourceComponent.cs @@ -1,9 +1,11 @@ +using Content.Server.Shuttles.Systems; + namespace Content.Server.Shuttles.Components; /// /// Added to a designated arrivals station for players to spawn at, if enabled. /// -[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 index 0000000000..20d3bbad39 --- /dev/null +++ b/Content.Server/Shuttles/Components/EscapePodComponent.cs @@ -0,0 +1,14 @@ +using Content.Server.Shuttles.Systems; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.Shuttles.Components; + +/// +/// If added to a grid gets launched when the emergency shuttle launches. +/// +[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 index 0000000000..c1493a98d7 --- /dev/null +++ b/Content.Server/Shuttles/Components/GridFillComponent.cs @@ -0,0 +1,13 @@ +using Content.Server.Shuttles.Systems; +using Robust.Shared.Utility; + +namespace Content.Server.Shuttles.Components; + +/// +/// If added to an airlock will try to autofill a grid onto it on MapInit +/// +[RegisterComponent, Access(typeof(ShuttleSystem))] +public sealed class GridFillComponent : Component +{ + [DataField("path")] public ResourcePath Path = new("/Maps/Shuttles/escape_pod_small.yml"); +} diff --git a/Content.Server/Shuttles/Components/StationArrivalsComponent.cs b/Content.Server/Shuttles/Components/StationArrivalsComponent.cs index fded1b1d3a..5ba5c194ed 100644 --- a/Content.Server/Shuttles/Components/StationArrivalsComponent.cs +++ b/Content.Server/Shuttles/Components/StationArrivalsComponent.cs @@ -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; /// /// Added to a station that is available for arrivals shuttles. /// -[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 index 0000000000..446f832b59 --- /dev/null +++ b/Content.Server/Shuttles/DockingConfig.cs @@ -0,0 +1,28 @@ +using Content.Server.Shuttles.Components; +using Robust.Shared.Map; + +namespace Content.Server.Shuttles; + +/// +/// Stores the data for a valid docking configuration for the emergency shuttle +/// +public sealed class DockingConfig +{ + /// + /// The pairs of docks that can connect. + /// + public List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)> Docks = new(); + + /// + /// Area relative to the target grid the emergency shuttle will spawn in on. + /// + public Box2 Area; + + /// + /// Target grid for docking. + /// + public EntityUid TargetGrid; + + public EntityCoordinates Coordinates; + public Angle Angle; +} diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index b05be7cd0a..cb7d0074be 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -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(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(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(component.Shuttle); arrivalsComp.Station = uid; EnsureComp(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)); } diff --git a/Content.Server/Shuttles/Systems/DockingSystem.AutoDock.cs b/Content.Server/Shuttles/Systems/DockingSystem.AutoDock.cs index fc8d331e2d..6091b80a77 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.AutoDock.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.AutoDock.cs @@ -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 index 0000000000..f72a1df252 --- /dev/null +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -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 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(uid).LocalCenter); + var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot), + Comp(targetUid).LocalCenter); + + var mapDiff = shuttleCOM - targetCOM; + var angle = mapDiff.ToWorldAngle(); + angle -= targetRot; + return angle; + } + + /// + /// Checks if 2 docks can be connected by moving the shuttle directly onto docks. + /// + 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; + } + + /// + /// Gets docking config between 2 specific docks. + /// + 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); + } + + /// + /// Tries to get a valid docking configuration for the shuttle to the target grid. + /// + /// Priority docking tag to prefer, e.g. for emergency shuttle + 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(); + var targetGridGrid = Comp(targetGrid); + var targetGridXform = xformQuery.GetComponent(targetGrid); + var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced(); + + var shuttleAABB = Comp(shuttleUid).LocalAABB; + + var validDockConfigs = new List(); + + 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(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; + } + + /// + /// Checks whether the emergency shuttle can warp to the specified position. + /// + 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(); + + 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; + } +} diff --git a/Content.Server/Shuttles/Systems/DockingSystem.cs b/Content.Server/Shuttles/Systems/DockingSystem.cs index 38f8e4f0a4..28fac88f20 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.cs @@ -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"; diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs similarity index 81% rename from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs rename to Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs index 536de0c1d8..3bc7264fb3 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyConsole.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs @@ -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!; - /// /// Has the emergency shuttle arrived? /// @@ -43,7 +33,7 @@ public sealed partial class ShuttleSystem /// /// How much time remaining until the shuttle consoles for emergency shuttles are unlocked? /// - private float _consoleAccumulator; + private float _consoleAccumulator = float.MinValue; /// /// How long after the transit is over to end the round. @@ -70,6 +60,8 @@ public sealed partial class ShuttleSystem /// private bool _launchedShuttles; + private bool _leftShuttles; + /// /// Have we announced the launch? /// @@ -84,6 +76,8 @@ public sealed partial class ShuttleSystem SubscribeLocalEvent(OnEmergencyRepeal); SubscribeLocalEvent(OnEmergencyRepealAll); SubscribeLocalEvent(OnEmergencyOpenAttempt); + + SubscribeLocalEvent(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(true)) + var dataQuery = AllEntityQuery(); + + while (dataQuery.MoveNext(out var comp)) { - if (!TryComp(comp.EmergencyShuttle, out var shuttle)) continue; + if (!TryComp(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(); + 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(); + + 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(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(); diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyShuttle.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs similarity index 61% rename from Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyShuttle.cs rename to Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index ebe7b0e7dd..f8b48e474e 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.EmergencyShuttle.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -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(OnRoundStart); SubscribeLocalEvent(OnStationStartup); SubscribeNetworkEvent(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(); } /// @@ -90,18 +108,25 @@ public sealed partial class ShuttleSystem /// 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(_station.GetOwningStation(player.Value), out var stationData) || - !TryComp(stationData.EmergencyShuttle, out var shuttle)) return; + !HasComp(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(stationUid, out var stationData) || !TryComp(stationData.EmergencyShuttle, out var xform) || - !TryComp(stationData.EmergencyShuttle, out var shuttle)) return; + !TryComp(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(); - if (TryFTLDock(shuttle, targetGrid.Value)) + if (_shuttle.TryFTLDock(stationData.EmergencyShuttle.Value, shuttle, targetGrid.Value)) { if (TryComp(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(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 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(xform.Owner).LocalCenter); - var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot), - Comp(targetXform.Owner).LocalCenter); - - var mapDiff = shuttleCOM - targetCOM; - var targetRotation = targetRot; - var angle = mapDiff.ToWorldAngle(); - angle -= targetRotation; - return angle; - } - - /// - /// Checks if 2 docks can be connected by moving the shuttle directly onto docks. - /// - 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 /// 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(true)) - { - CallEmergencyShuttle(comp.Owner); - } + var query = AllEntityQuery(); - _commsConsole.UpdateCommsConsoleInterface(); - } - - public List GetDocks(EntityUid uid) - { - var result = new List(); - - foreach (var (dock, xform) in EntityQuery(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(shuttle.Value); } private void CleanupEmergencyShuttle() @@ -354,27 +304,11 @@ public sealed partial class ShuttleSystem _mapManager.DeleteMap(CentComMap.Value); } - /// - /// Stores the data for a valid docking configuration for the emergency shuttle - /// - private sealed class DockingConfig + private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args) { - /// - /// The pairs of docks that can connect. - /// - public List<(DockingComponent DockA, DockingComponent DockB)> Docks = new(); - - /// - /// Area relative to the target grid the emergency shuttle will spawn in on. - /// - public Box2 Area; - - /// - /// Target grid for docking. - /// - public EntityUid TargetGrid; - - public EntityCoordinates Coordinates; - public Angle Angle; + if (component.LaunchTime == null) + return; + + component.LaunchTime = component.LaunchTime.Value + args.PausedTime; } } diff --git a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs index d4b52de29a..cce035dca5 100644 --- a/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleConsoleSystem.cs @@ -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(OnConsoleShutdown); - SubscribeLocalEvent(OnConsolePowerChange); - SubscribeLocalEvent(OnConsoleAnchorChange); - SubscribeLocalEvent(OnConsoleUIOpenAttempt); - SubscribeLocalEvent(OnDestinationMessage); - SubscribeLocalEvent(OnConsoleUIClose); + SubscribeLocalEvent(OnConsoleShutdown); + SubscribeLocalEvent(OnConsolePowerChange); + SubscribeLocalEvent(OnConsoleAnchorChange); + SubscribeLocalEvent(OnConsoleUIOpenAttempt); + SubscribeLocalEvent(OnDestinationMessage); + SubscribeLocalEvent(OnConsoleUIClose); - SubscribeLocalEvent(OnDock); - SubscribeLocalEvent(OnUndock); + SubscribeLocalEvent(OnDock); + SubscribeLocalEvent(OnUndock); - SubscribeLocalEvent(HandlePilotMove); - SubscribeLocalEvent(OnGetState); + SubscribeLocalEvent(HandlePilotMove); + SubscribeLocalEvent(OnGetState); - SubscribeLocalEvent(OnFtlDestStartup); - SubscribeLocalEvent(OnFtlDestShutdown); - } + SubscribeLocalEvent(OnFtlDestStartup); + SubscribeLocalEvent(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(args.Destination, out var dest)) { - if (!TryComp(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(entity, out var xform) || - !TryComp(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(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(args.Destination) && HasComp(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(entity, out var xform) || + !TryComp(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(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; } - /// - /// Refreshes all of the data for shuttle consoles. - /// - public void RefreshShuttleConsoles() - { - var docks = GetAllDocks(); - var query = AllEntityQuery(); + var dock = HasComp(args.Destination) && HasComp(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); + } - /// - /// Stop piloting if the window is closed. - /// - 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(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(); - } + /// + /// Refreshes all of the data for shuttle consoles. + /// + public void RefreshShuttleConsoles() + { + var docks = GetAllDocks(); + var query = AllEntityQuery(); - 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) + /// + /// Stop piloting if the window is closed. + /// + 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(true)) { - if (!_tags.HasTag(user, "CanPilot") || - !TryComp(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(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(uid, out var component) || + !this.IsPowered(uid, EntityManager) || + !Transform(uid).Anchored || + !_blocker.CanInteract(user, uid)) { - args.State = new PilotComponentState(component.Console?.Owner); + return false; } - /// - /// Returns the position and angle of all dockingcomponents. - /// - private List GetAllDocks() + var pilotComponent = EnsureComp(user); + var console = pilotComponent.Console; + + if (console != null) { - // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES! - var result = new List(); - var query = AllEntityQuery(); + 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? docks = null) + AddPilot(user, component); + return true; + } + + private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args) + { + args.State = new PilotComponentState(component.Console?.Owner); + } + + /// + /// Returns the position and angle of all dockingcomponents. + /// + private List GetAllDocks() + { + // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES! + var result = new List(); + var query = AllEntityQuery(); + + 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(entity, out var consoleXform); - TryComp(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? docks = null) + { + EntityUid? entity = consoleUid; - if (TryComp(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(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f)) - { - var metaQuery = GetEntityQuery(); + RaiseLocalEvent(entity.Value, ref getShuttleEv); + entity = getShuttleEv.Console; - // Can't go anywhere when in FTL. - var locked = shuttleFtl != null || Paused(shuttleGridUid.Value); + TryComp(entity, out var consoleXform); + TryComp(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(); + 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(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(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(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f)) { - base.Update(frameTime); + var metaQuery = GetEntityQuery(); + + // Can't go anywhere when in FTL. + var locked = shuttleFtl != null || Paused(shuttleGridUid.Value); - var toRemove = new RemQueue(); - var query = EntityQueryEnumerator(); + // 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(); - 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(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)); } } - /// - /// If pilot is moved then we'll stop them from piloting. - /// - 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(); + + 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(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) + /// + /// If pilot is moved then we'll stop them from piloting. + /// + 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(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(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(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(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(pilotUid, out var eye)) - { - eye.Zoom = new(1.0f, 1.0f); - } + pilotComponent.Console = component; + ActionBlockerSystem.UpdateCanMove(entity); + pilotComponent.Position = EntityManager.GetComponent(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(pilotUid); + if (TryComp(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(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); } } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 369803b497..d613e51eb7 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -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(); 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 /// public FTLDestinationComponent AddFTLDestination(EntityUid uid, bool enabled) { - if (TryComp(uid, out var destination) && destination.Enabled == enabled) return destination; + if (TryComp(uid, out var destination) && destination.Enabled == enabled) + return destination; destination = EnsureComp(uid); @@ -146,19 +142,22 @@ public sealed partial class ShuttleSystem { if (!RemComp(uid)) return; + _console.RefreshShuttleConsoles(); } /// /// Moves a shuttle from its current position to the target one. Goes through the hyperspace map while the timer is running. /// - 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 /// /// Moves a shuttle from its current position to docked on the target one. Goes through the hyperspace map while the timer is running. /// - 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(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(uid, out var dest)) { @@ -380,7 +380,9 @@ public sealed partial class ShuttleSystem { foreach (var (dock, xform) in EntityQuery(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(true)) + var query = AllEntityQuery(); + + 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 /// /// Tries to dock with the target grid, otherwise falls back to proximity. /// - /// Priority docking tag to prefer, e.g. for emergency shuttle - public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid, string? priorityTag = null) + public bool TryFTLDock(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, string? priorityTag = null) { - if (!TryComp(component.Owner, out var xform) || + if (!TryComp(shuttleUid, out var shuttleXform) || !TryComp(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; } + /// + /// Forces an FTL dock. + /// + 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); + } + } + /// /// Tries to arrive nearby without overlapping with other grids. /// - 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(); - var shuttleAABB = Comp(component.Owner).LocalAABB; + var shuttleAABB = Comp(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(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(grid.Owner).LocalAABB)); @@ -568,10 +584,10 @@ public sealed partial class ShuttleSystem Vector2 spawnPos; - if (TryComp(component.Owner, out var shuttleBody)) + if (TryComp(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; } - - /// - /// Checks whether the emergency shuttle can warp to the specified position. - /// - 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(gridUid)) - return true; - - return !grid.GetLocalTilesIntersecting(area).Any(); - } - - /// - /// Tries to get a valid docking configuration for the shuttle to the target grid. - /// - /// Priority docking tag to prefer, e.g. for emergency shuttle - private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid, string? priorityTag = null) - { - var gridDocks = GetDocks(targetGrid); - - if (gridDocks.Count <= 0) - return null; - - var xformQuery = GetEntityQuery(); - var targetGridGrid = Comp(targetGrid); - var targetGridXform = xformQuery.GetComponent(targetGrid); - var targetGridAngle = targetGridXform.WorldRotation.Reduced(); - - var shuttleDocks = GetDocks(component.Owner); - var shuttleAABB = Comp(component.Owner).LocalAABB; - - var validDockConfigs = new List(); - - 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(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 index 0000000000..ac66d2acb3 --- /dev/null +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -0,0 +1,69 @@ +using Content.Server.Shuttles.Components; + +namespace Content.Server.Shuttles.Systems; + +public sealed partial class ShuttleSystem +{ + private void InitializeGridFills() + { + SubscribeLocalEvent(OnGridFillMapInit); + } + + private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args) + { + if (!TryComp(uid, out var dock) || + !TryComp(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(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(); + var xformQuery = GetEntityQuery(); + 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; + } +} diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 1673058bb7..f1ab8c078d 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -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(uid); @@ -107,12 +107,12 @@ namespace Content.Server.Shuttles.Systems private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args) { - if (!EntityManager.HasComponent(component.Owner)) + if (!EntityManager.HasComponent(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(uid).EntityLifeStage >= EntityLifeStage.Terminating) return; - if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent)) + if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent)) { return; } diff --git a/Content.Server/Station/Components/StationDataComponent.cs b/Content.Server/Station/Components/StationDataComponent.cs index 6c87f95494..60d58d671f 100644 --- a/Content.Server/Station/Components/StationDataComponent.cs +++ b/Content.Server/Station/Components/StationDataComponent.cs @@ -28,6 +28,6 @@ public sealed class StationDataComponent : Component /// /// The emergency shuttle assigned to this station. /// - [ViewVariables, Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadWrite)] + [ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)] public EntityUid? EmergencyShuttle; } diff --git a/Content.Shared/Popups/SharedPopupSystem.cs b/Content.Shared/Popups/SharedPopupSystem.cs index cc4baac717..8ff3e7467c 100644 --- a/Content.Shared/Popups/SharedPopupSystem.cs +++ b/Content.Shared/Popups/SharedPopupSystem.cs @@ -68,12 +68,12 @@ namespace Content.Shared.Popups public abstract void PopupEntity(string message, EntityUid uid, PopupType type=PopupType.Small); /// - /// Variant of that shoes the popup only to some specific client. + /// Variant of that shows the popup only to some specific client. /// public abstract void PopupEntity(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small); /// - /// Variant of that shoes the popup only to some specific client. + /// Variant of that shows the popup only to some specific client. /// 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 index 0000000000..fa995e2346 --- /dev/null +++ b/Resources/Maps/Shuttles/escape_pod_small.yml @@ -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 +... diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml index fe0848a950..e5ec85282b 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/access.yml @@ -740,6 +740,13 @@ - type: PriorityDock tag: DockArrivals +- type: entity + parent: AirlockGlassShuttle + id: AirlockExternalGlassShuttleEscape + suffix: External, Escape 3x4, Glass, Docking + components: + - type: GridFill + #HighSecDoors - type: entity parent: HighSecDoor -- 2.51.2