Assert.IsNotNull(stationConfig, $"{entManager.ToPrettyString(station)} had null StationConfig.");
var shuttlePath = stationConfig.EmergencyShuttlePath.ToString();
var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath);
- Assert.That(shuttle != null && shuttleSystem.TryFTLDock(entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
+ Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
mapManager.DeleteMap(shuttleMap);
[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!;
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
[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
}
// UH OH
- if (nukeTransform.MapID == _shuttleSystem.CentComMap)
+ if (nukeTransform.MapID == _emergency.CentComMap)
{
_winConditions.Add(WinCondition.NukeActiveAtCentCom);
RuleWinType = WinType.OpsMajor;
foreach (var (_, transform) in EntityManager.EntityQuery<NukeDiskComponent, TransformComponent>())
{
var diskMapId = transform.MapID;
- diskAtCentCom = _shuttleSystem.CentComMap == diskMapId;
+ diskAtCentCom = _emergency.CentComMap == diskMapId;
// TODO: The target station should be stored, and the nuke disk should store its original station.
// This is fine for now, because we can assume a single station in base SS14.
if (TryComp<ShuttleComponent>(shuttleId, out var shuttle))
{
- _shuttleSystem.TryFTLDock(shuttle, _nukieOutpost.Value);
+ _shuttle.TryFTLDock(shuttleId, shuttle, _nukieOutpost.Value);
}
_nukiePlanet = mapId;
[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);
[AdminCommand(AdminFlags.Fun)]
public sealed class DelayRoundEndCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
public string Command => "delayroundend";
public string Description => Loc.GetString("emergency-shuttle-command-round-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+ var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
+
if (system.DelayEmergencyRoundEnd())
{
shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-yes"));
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
[AdminCommand(AdminFlags.Fun)]
public sealed class DockEmergencyShuttleCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
public string Command => "dockemergencyshuttle";
public string Description => Loc.GetString("emergency-shuttle-command-dock-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+ var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
system.CallEmergencyShuttle();
}
}
[AdminCommand(AdminFlags.Fun)]
public sealed class LaunchEmergencyShuttleCommand : IConsoleCommand
{
+ [Dependency] private readonly IEntitySystemManager _sysManager = default!;
+
public string Command => "launchemergencyshuttle";
public string Description => Loc.GetString("emergency-shuttle-command-launch-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
- var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
+ var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
system.EarlyLaunch();
}
}
+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")]
+using Content.Server.Shuttles.Systems;
+
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a designated arrivals station for players to spawn at, if enabled.
/// </summary>
-[RegisterComponent]
+[RegisterComponent, Access(typeof(ArrivalsSystem))]
public sealed class ArrivalsSourceComponent : Component
{
--- /dev/null
+using Content.Server.Shuttles.Systems;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Server.Shuttles.Components;
+
+/// <summary>
+/// If added to a grid gets launched when the emergency shuttle launches.
+/// </summary>
+[RegisterComponent, Access(typeof(EmergencyShuttleSystem))]
+public sealed class EscapePodComponent : Component
+{
+ [DataField("launchTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
+ public TimeSpan? LaunchTime;
+}
--- /dev/null
+using Content.Server.Shuttles.Systems;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Shuttles.Components;
+
+/// <summary>
+/// If added to an airlock will try to autofill a grid onto it on MapInit
+/// </summary>
+[RegisterComponent, Access(typeof(ShuttleSystem))]
+public sealed class GridFillComponent : Component
+{
+ [DataField("path")] public ResourcePath Path = new("/Maps/Shuttles/escape_pod_small.yml");
+}
+using Content.Server.Shuttles.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a station that is available for arrivals shuttles.
/// </summary>
-[RegisterComponent]
+[RegisterComponent, Access(typeof(ArrivalsSystem))]
public sealed class StationArrivalsComponent : Component
{
[DataField("shuttle")]
--- /dev/null
+using Content.Server.Shuttles.Components;
+using Robust.Shared.Map;
+
+namespace Content.Server.Shuttles;
+
+/// <summary>
+/// Stores the data for a valid docking configuration for the emergency shuttle
+/// </summary>
+public sealed class DockingConfig
+{
+ /// <summary>
+ /// The pairs of docks that can connect.
+ /// </summary>
+ public List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)> Docks = new();
+
+ /// <summary>
+ /// Area relative to the target grid the emergency shuttle will spawn in on.
+ /// </summary>
+ public Box2 Area;
+
+ /// <summary>
+ /// Target grid for docking.
+ /// </summary>
+ public EntityUid TargetGrid;
+
+ public EntityCoordinates Coordinates;
+ public Angle Angle;
+}
// TODO: Need some kind of comp to shunt people off if they try to get on?
if (TryComp<TransformComponent>(arrivals, out var arrivalsXform))
{
- while (query.MoveNext(out var comp, out var shuttle, out var xform))
+ while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform))
{
if (comp.NextTransfer > curTime || !TryComp<StationDataComponent>(comp.Station, out var data))
continue;
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
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));
var arrivalsComp = EnsureComp<ArrivalsShuttleComponent>(component.Shuttle);
arrivalsComp.Station = uid;
EnsureComp<ProtectedGridComponent>(uid);
- _shuttles.FTLTravel(shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
+ _shuttles.FTLTravel(component.Shuttle, shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
arrivalsComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
}
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;
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Shuttles.Components;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Components;
+
+namespace Content.Server.Shuttles.Systems;
+
+public sealed partial class DockingSystem
+{
+ /*
+ * Handles the shuttle side of FTL docking.
+ */
+
+ public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
+ {
+ var (shuttlePos, shuttleRot) = _transform.GetWorldPositionRotation(xform, xformQuery);
+ var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery);
+
+ var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
+ Comp<PhysicsComponent>(uid).LocalCenter);
+ var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
+ Comp<PhysicsComponent>(targetUid).LocalCenter);
+
+ var mapDiff = shuttleCOM - targetCOM;
+ var angle = mapDiff.ToWorldAngle();
+ angle -= targetRot;
+ return angle;
+ }
+
+ /// <summary>
+ /// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
+ /// </summary>
+ public bool CanDock(
+ DockingComponent shuttleDock,
+ TransformComponent shuttleDockXform,
+ DockingComponent gridDock,
+ TransformComponent gridDockXform,
+ Angle targetGridRotation,
+ Box2 shuttleAABB,
+ MapGridComponent grid,
+ [NotNullWhen(true)] out Box2? shuttleDockedAABB,
+ out Matrix3 matty,
+ out Angle gridRotation)
+ {
+ gridRotation = Angle.Zero;
+ matty = Matrix3.Identity;
+ shuttleDockedAABB = null;
+
+ if (shuttleDock.Docked ||
+ gridDock.Docked ||
+ !shuttleDockXform.Anchored ||
+ !gridDockXform.Anchored)
+ {
+ return false;
+ }
+
+ // First, get the station dock's position relative to the shuttle, this is where we rotate it around
+ var stationDockPos = shuttleDockXform.LocalPosition +
+ shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
+
+ // Need to invert the grid's angle.
+ var shuttleDockAngle = shuttleDockXform.LocalRotation;
+ var gridDockAngle = gridDockXform.LocalRotation.Opposite();
+
+ var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
+ var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
+ Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
+ shuttleDockedAABB = matty.TransformBox(shuttleAABB);
+ // Rounding moment
+ shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
+
+ if (!ValidSpawn(grid, shuttleDockedAABB.Value))
+ return false;
+
+ gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
+ return true;
+ }
+
+ /// <summary>
+ /// Gets docking config between 2 specific docks.
+ /// </summary>
+ public DockingConfig? GetDockingConfig(
+ EntityUid shuttleUid,
+ EntityUid targetGrid,
+ EntityUid shuttleDockUid,
+ DockingComponent shuttleDock,
+ EntityUid gridDockUid,
+ DockingComponent gridDock)
+ {
+ var shuttleDocks = new List<(EntityUid, DockingComponent)>(1)
+ {
+ (shuttleDockUid, shuttleDock)
+ };
+
+ var gridDocks = new List<(EntityUid, DockingComponent)>(1)
+ {
+ (gridDockUid, gridDock)
+ };
+
+ return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks);
+ }
+
+ /// <summary>
+ /// Tries to get a valid docking configuration for the shuttle to the target grid.
+ /// </summary>
+ /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
+ public DockingConfig? GetDockingConfig(EntityUid shuttleUid, EntityUid targetGrid, string? priorityTag = null)
+ {
+ var gridDocks = GetDocks(targetGrid);
+ var shuttleDocks = GetDocks(shuttleUid);
+
+ return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks, priorityTag);
+ }
+
+ private DockingConfig? GetDockingConfigPrivate(
+ EntityUid shuttleUid,
+ EntityUid targetGrid,
+ List<(EntityUid, DockingComponent)> shuttleDocks,
+ List<(EntityUid, DockingComponent)> gridDocks,
+ string? priorityTag = null)
+ {
+ if (gridDocks.Count <= 0)
+ return null;
+
+ var xformQuery = GetEntityQuery<TransformComponent>();
+ var targetGridGrid = Comp<MapGridComponent>(targetGrid);
+ var targetGridXform = xformQuery.GetComponent(targetGrid);
+ var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced();
+
+ var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
+
+ var validDockConfigs = new List<DockingConfig>();
+
+ if (shuttleDocks.Count > 0)
+ {
+ // We'll try all combinations of shuttle docks and see which one is most suitable
+ foreach (var (dockUid, shuttleDock) in shuttleDocks)
+ {
+ var shuttleDockXform = xformQuery.GetComponent(dockUid);
+
+ foreach (var (gridDockUid, gridDock) in gridDocks)
+ {
+ var gridXform = xformQuery.GetComponent(gridDockUid);
+
+ if (!CanDock(
+ shuttleDock, shuttleDockXform,
+ gridDock, gridXform,
+ targetGridAngle,
+ shuttleAABB,
+ targetGridGrid,
+ out var dockedAABB,
+ out var matty,
+ out var targetAngle))
+ {
+ continue;
+ }
+
+ // Can't just use the AABB as we want to get bounds as tight as possible.
+ var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
+ spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager, _transform));
+
+ var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
+
+ // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
+ if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
+ dockedBounds).Any(o => o.Owner != targetGrid))
+ {
+ continue;
+ }
+
+ // Alright well the spawn is valid now to check how many we can connect
+ // Get the matrix for each shuttle dock and test it against the grid docks to see
+ // if the connected position / direction matches.
+
+ var dockedPorts = new List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)>()
+ {
+ (dockUid, gridDockUid, shuttleDock, gridDock),
+ };
+
+ // TODO: Check shuttle orientation as the tiebreaker.
+
+ foreach (var (otherUid, other) in shuttleDocks)
+ {
+ if (other == shuttleDock)
+ continue;
+
+ foreach (var (otherGridUid, otherGrid) in gridDocks)
+ {
+ if (otherGrid == gridDock)
+ continue;
+
+ if (!CanDock(
+ other,
+ xformQuery.GetComponent(otherUid),
+ otherGrid,
+ xformQuery.GetComponent(otherGridUid),
+ targetGridAngle,
+ shuttleAABB, targetGridGrid,
+ out var otherDockedAABB,
+ out _,
+ out var otherTargetAngle) ||
+ !otherDockedAABB.Equals(dockedAABB) ||
+ !targetAngle.Equals(otherTargetAngle))
+ {
+ continue;
+ }
+
+ dockedPorts.Add((otherUid, otherGridUid, other, otherGrid));
+ }
+ }
+
+ validDockConfigs.Add(new DockingConfig()
+ {
+ Docks = dockedPorts,
+ Area = dockedAABB.Value,
+ Coordinates = spawnPosition,
+ Angle = targetAngle,
+ });
+ }
+ }
+ }
+
+ if (validDockConfigs.Count <= 0)
+ return null;
+
+ // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
+ validDockConfigs = validDockConfigs
+ .OrderByDescending(x => x.Docks.Any(docks =>
+ TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
+ priority.Tag?.Equals(priorityTag) == true))
+ .ThenByDescending(x => x.Docks.Count)
+ .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
+
+ var location = validDockConfigs.First();
+ location.TargetGrid = targetGrid;
+ // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
+
+ return location;
+ }
+
+ /// <summary>
+ /// Checks whether the emergency shuttle can warp to the specified position.
+ /// </summary>
+ private bool ValidSpawn(MapGridComponent grid, Box2 area)
+ {
+ return !grid.GetLocalTilesIntersecting(area).Any();
+ }
+
+ public List<(EntityUid Uid, DockingComponent Component)> GetDocks(EntityUid uid)
+ {
+ var result = new List<(EntityUid Uid, DockingComponent Component)>();
+ var query = AllEntityQuery<DockingComponent, TransformComponent>();
+
+ while (query.MoveNext(out var dockUid, out var dock, out var xform))
+ {
+ if (xform.ParentUid != uid || !dock.Enabled)
+ continue;
+
+ result.Add((dockUid, dock));
+ }
+
+ return result;
+ }
+}
[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";
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;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
-using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Shuttles.Systems;
-public sealed partial class ShuttleSystem
+public sealed partial class EmergencyShuttleSystem
{
/*
* Handles the emergency shuttle's console and early launching.
*/
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IdCardSystem _idSystem = default!;
- [Dependency] private readonly AccessReaderSystem _reader = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly RoundEndSystem _roundEnd = default!;
-
/// <summary>
/// Has the emergency shuttle arrived?
/// </summary>
/// <summary>
/// How much time remaining until the shuttle consoles for emergency shuttles are unlocked?
/// </summary>
- private float _consoleAccumulator;
+ private float _consoleAccumulator = float.MinValue;
/// <summary>
/// How long after the transit is over to end the round.
/// </summary>
private bool _launchedShuttles;
+ private bool _leftShuttles;
+
/// <summary>
/// Have we announced the launch?
/// </summary>
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealMessage>(OnEmergencyRepeal);
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealAllMessage>(OnEmergencyRepealAll);
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnEmergencyOpenAttempt);
+
+ SubscribeLocalEvent<EscapePodComponent, EntityUnpausedEvent>(OnEscapeUnpaused);
}
private void OnEmergencyOpenAttempt(EntityUid uid, EmergencyShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
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;
}
// Imminent departure
- if (!_launchedShuttles && _consoleAccumulator <= DefaultStartupTime)
+ if (!_launchedShuttles && _consoleAccumulator <= ShuttleSystem.DefaultStartupTime)
{
_launchedShuttles = true;
if (CentComMap != null)
{
- foreach (var comp in EntityQuery<StationDataComponent>(true))
+ var dataQuery = AllEntityQuery<StationDataComponent>();
+
+ while (dataQuery.MoveNext(out var comp))
{
- if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle)) continue;
+ if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle))
+ continue;
if (Deleted(CentCom))
{
// TODO: Need to get non-overlapping positions.
- FTLTravel(shuttle,
+ _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
new EntityCoordinates(
_mapManager.GetMapEntityId(CentComMap.Value),
- Vector2.One * 1000f), _consoleAccumulator, TransitTime);
+ _random.NextVector2(1000f)), _consoleAccumulator, TransitTime);
}
else
{
- FTLTravel(shuttle,
+ _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
CentCom.Value, _consoleAccumulator, TransitTime, true);
}
}
+
+ var podQuery = AllEntityQuery<EscapePodComponent>();
+ var podLaunchOffset = 0.5f;
+
+ // Stagger launches coz funny
+ while (podQuery.MoveNext(out _, out var pod))
+ {
+ pod.LaunchTime = _timing.CurTime + TimeSpan.FromSeconds(podLaunchOffset);
+ podLaunchOffset += _random.NextFloat(0.5f, 2.5f);
+ }
}
}
+ var podLaunchQuery = EntityQueryEnumerator<EscapePodComponent, ShuttleComponent>();
+
+ while (podLaunchQuery.MoveNext(out var uid, out var pod, out var shuttle))
+ {
+ if (CentCom == null || pod.LaunchTime == null || pod.LaunchTime < _timing.CurTime)
+ continue;
+
+ // Don't dock them. If you do end up doing this then stagger launch.
+ _shuttle.FTLTravel(uid, shuttle,
+ CentCom.Value, hyperspaceTime: TransitTime);
+
+ RemCompDeferred<EscapePodComponent>(uid);
+ }
+
// Departed
- if (_consoleAccumulator <= 0f)
+ if (!_leftShuttles && _consoleAccumulator <= 0f)
{
- _launchedShuttles = true;
+ _leftShuttles = true;
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}")));
_roundEndCancelToken = new CancellationTokenSource();
Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
+ }
+ // All the others.
+ if (_consoleAccumulator < minTime)
+ {
// Guarantees that emergency shuttle arrives first before anyone else can FTL.
if (CentCom != null)
- AddFTLDestination(CentCom.Value, true);
-
+ _shuttle.AddFTLDestination(CentCom.Value, true);
}
}
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)));
{
_announced = false;
_roundEndCancelToken = null;
+ _leftShuttles = false;
_launchedShuttles = false;
- _consoleAccumulator = 0f;
+ _consoleAccumulator = float.MinValue;
EarlyLaunchAuthorized = false;
EmergencyShuttleArrived = false;
}
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();
-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.
[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; }
private bool _emergencyShuttleEnabled;
- private void InitializeEscape()
+ public override void Initialize()
{
+ _sawmill = Logger.GetSawmill("shuttle.emergency");
_emergencyShuttleEnabled = _configManager.GetCVar(CCVars.EmergencyShuttleEnabled);
// Don't immediately invoke as roundstart will just handle it.
_configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationStartup);
SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition);
+ InitializeEmergencyConsole();
}
private void SetEmergencyShuttleEnabled(bool value)
{
- if (_emergencyShuttleEnabled == value) return;
+ if (_emergencyShuttleEnabled == value)
+ return;
_emergencyShuttleEnabled = value;
if (value)
}
}
- private void ShutdownEscape()
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ UpdateEmergencyConsole(frameTime);
+ }
+
+ public override void Shutdown()
{
- _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
+ _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
+ ShutdownEmergencyConsole();
}
/// <summary>
/// </summary>
private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args)
{
- if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) return;
+ if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
+ return;
var player = args.SenderSession.AttachedEntity;
if (player == null ||
!TryComp<StationDataComponent>(_station.GetOwningStation(player.Value), out var stationData) ||
- !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
+ !HasComp<ShuttleComponent>(stationData.EmergencyShuttle))
+ {
+ return;
+ }
var targetGrid = _station.GetLargestGrid(stationData);
- if (targetGrid == null) return;
- var config = GetDockingConfig(shuttle, targetGrid.Value);
- if (config == null) return;
+ if (targetGrid == null)
+ return;
+
+ var config = _dock.GetDockingConfig(stationData.EmergencyShuttle.Value, targetGrid.Value);
+ if (config == null)
+ return;
RaiseNetworkEvent(new EmergencyShuttlePositionMessage()
{
{
if (!TryComp<StationDataComponent>(stationUid, out var stationData) ||
!TryComp<TransformComponent>(stationData.EmergencyShuttle, out var xform) ||
- !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
+ !TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle))
+ {
+ return;
+ }
- var targetGrid = _station.GetLargestGrid(stationData);
+ var targetGrid = _station.GetLargestGrid(stationData);
// UHH GOOD LUCK
if (targetGrid == null)
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}");
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
// TODO: Need filter extensions or something don't blame me.
- SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
+ _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
return;
}
var xformQuery = GetEntityQuery<TransformComponent>();
- if (TryFTLDock(shuttle, targetGrid.Value))
+ if (_shuttle.TryFTLDock(stationData.EmergencyShuttle.Value, shuttle, targetGrid.Value))
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
- var angle = GetAngle(xform, targetXform, xformQuery);
+ var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations");
// TODO: Need filter extensions or something don't blame me.
- SoundSystem.Play("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast());
+ _audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true);
}
else
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
- var angle = GetAngle(xform, targetXform, xformQuery);
+ var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}");
// TODO: Need filter extensions or something don't blame me.
- SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
+ _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
}
}
- private Angle GetAngle(TransformComponent xform, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
- {
- var (shuttlePos, shuttleRot) = xform.GetWorldPositionRotation(xformQuery);
- var (targetPos, targetRot) = targetXform.GetWorldPositionRotation(xformQuery);
-
- var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
- Comp<PhysicsComponent>(xform.Owner).LocalCenter);
- var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
- Comp<PhysicsComponent>(targetXform.Owner).LocalCenter);
-
- var mapDiff = shuttleCOM - targetCOM;
- var targetRotation = targetRot;
- var angle = mapDiff.ToWorldAngle();
- angle -= targetRotation;
- return angle;
- }
-
- /// <summary>
- /// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
- /// </summary>
- private bool CanDock(
- DockingComponent shuttleDock,
- TransformComponent shuttleDockXform,
- DockingComponent gridDock,
- TransformComponent gridDockXform,
- Angle targetGridRotation,
- Box2 shuttleAABB,
- EntityUid gridUid,
- MapGridComponent grid,
- [NotNullWhen(true)] out Box2? shuttleDockedAABB,
- out Matrix3 matty,
- out Angle gridRotation)
- {
- gridRotation = Angle.Zero;
- matty = Matrix3.Identity;
- shuttleDockedAABB = null;
-
- if (shuttleDock.Docked ||
- gridDock.Docked ||
- !shuttleDockXform.Anchored ||
- !gridDockXform.Anchored)
- {
- return false;
- }
-
- // First, get the station dock's position relative to the shuttle, this is where we rotate it around
- var stationDockPos = shuttleDockXform.LocalPosition +
- shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
-
- // Need to invert the grid's angle.
- var shuttleDockAngle = shuttleDockXform.LocalRotation;
- var gridDockAngle = gridDockXform.LocalRotation.Opposite();
-
- var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
- var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
- Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
- shuttleDockedAABB = matty.TransformBox(shuttleAABB);
- // Rounding moment
- shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
-
- if (!ValidSpawn(gridUid, grid, shuttleDockedAABB.Value))
- return false;
-
- gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
- return true;
- }
-
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentStartup args)
{
AddEmergencyShuttle(component);
private void OnRoundStart(RoundStartingEvent ev)
{
+ CleanupEmergencyConsole();
SetupEmergencyShuttle();
}
/// </summary>
public void CallEmergencyShuttle()
{
- if (EmergencyShuttleArrived) return;
+ if (EmergencyShuttleArrived)
+ return;
if (!_emergencyShuttleEnabled)
{
if (CentComMap != null)
_mapManager.SetMapPaused(CentComMap.Value, false);
- foreach (var comp in EntityQuery<StationDataComponent>(true))
- {
- CallEmergencyShuttle(comp.Owner);
- }
+ var query = AllEntityQuery<StationDataComponent>();
- _commsConsole.UpdateCommsConsoleInterface();
- }
-
- public List<DockingComponent> GetDocks(EntityUid uid)
- {
- var result = new List<DockingComponent>();
-
- foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
+ while (query.MoveNext(out var uid, out var comp))
{
- if (xform.ParentUid != uid || !dock.Enabled) continue;
-
- result.Add(dock);
+ CallEmergencyShuttle(uid);
}
- return result;
+ _commsConsole.UpdateCommsConsoleInterface();
}
private void SetupEmergencyShuttle()
CentCom = centcomm;
if (CentCom != null)
- AddFTLDestination(CentCom.Value, false);
+ _shuttle.AddFTLDestination(CentCom.Value, false);
}
else
{
_shuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer;
component.EmergencyShuttle = shuttle;
- EnsureComp<ProtectedGridComponent>(shuttle.Value);
}
private void CleanupEmergencyShuttle()
_mapManager.DeleteMap(CentComMap.Value);
}
- /// <summary>
- /// Stores the data for a valid docking configuration for the emergency shuttle
- /// </summary>
- private sealed class DockingConfig
+ private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args)
{
- /// <summary>
- /// The pairs of docks that can connect.
- /// </summary>
- public List<(DockingComponent DockA, DockingComponent DockB)> Docks = new();
-
- /// <summary>
- /// Area relative to the target grid the emergency shuttle will spawn in on.
- /// </summary>
- public Box2 Area;
-
- /// <summary>
- /// Target grid for docking.
- /// </summary>
- public EntityUid TargetGrid;
-
- public EntityCoordinates Coordinates;
- public Angle Angle;
+ if (component.LaunchTime == null)
+ return;
+
+ component.LaunchTime = component.LaunchTime.Value + args.PausedTime;
}
}
using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
+using Robust.Shared.Collections;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
-namespace Content.Server.Shuttles.Systems
+namespace Content.Server.Shuttles.Systems;
+
+public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
{
- public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ActionBlockerSystem _blocker = default!;
+ [Dependency] private readonly AlertsSystem _alertsSystem = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly ShuttleSystem _shuttle = default!;
+ [Dependency] private readonly TagSystem _tags = default!;
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+
+ public override void Initialize()
{
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly ActionBlockerSystem _blocker = default!;
- [Dependency] private readonly AlertsSystem _alertsSystem = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly ShuttleSystem _shuttle = default!;
- [Dependency] private readonly TagSystem _tags = default!;
- [Dependency] private readonly UserInterfaceSystem _ui = default!;
-
- public override void Initialize()
- {
- base.Initialize();
+ base.Initialize();
- SubscribeLocalEvent<ShuttleConsoleComponent, ComponentShutdown>(OnConsoleShutdown);
- SubscribeLocalEvent<ShuttleConsoleComponent, PowerChangedEvent>(OnConsolePowerChange);
- SubscribeLocalEvent<ShuttleConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChange);
- SubscribeLocalEvent<ShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnConsoleUIOpenAttempt);
- SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleConsoleDestinationMessage>(OnDestinationMessage);
- SubscribeLocalEvent<ShuttleConsoleComponent, BoundUIClosedEvent>(OnConsoleUIClose);
+ SubscribeLocalEvent<ShuttleConsoleComponent, ComponentShutdown>(OnConsoleShutdown);
+ SubscribeLocalEvent<ShuttleConsoleComponent, PowerChangedEvent>(OnConsolePowerChange);
+ SubscribeLocalEvent<ShuttleConsoleComponent, AnchorStateChangedEvent>(OnConsoleAnchorChange);
+ SubscribeLocalEvent<ShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnConsoleUIOpenAttempt);
+ SubscribeLocalEvent<ShuttleConsoleComponent, ShuttleConsoleDestinationMessage>(OnDestinationMessage);
+ SubscribeLocalEvent<ShuttleConsoleComponent, BoundUIClosedEvent>(OnConsoleUIClose);
- SubscribeLocalEvent<DockEvent>(OnDock);
- SubscribeLocalEvent<UndockEvent>(OnUndock);
+ SubscribeLocalEvent<DockEvent>(OnDock);
+ SubscribeLocalEvent<UndockEvent>(OnUndock);
- SubscribeLocalEvent<PilotComponent, MoveEvent>(HandlePilotMove);
- SubscribeLocalEvent<PilotComponent, ComponentGetState>(OnGetState);
+ SubscribeLocalEvent<PilotComponent, MoveEvent>(HandlePilotMove);
+ SubscribeLocalEvent<PilotComponent, ComponentGetState>(OnGetState);
- SubscribeLocalEvent<FTLDestinationComponent, ComponentStartup>(OnFtlDestStartup);
- SubscribeLocalEvent<FTLDestinationComponent, ComponentShutdown>(OnFtlDestShutdown);
- }
+ SubscribeLocalEvent<FTLDestinationComponent, ComponentStartup>(OnFtlDestStartup);
+ SubscribeLocalEvent<FTLDestinationComponent, ComponentShutdown>(OnFtlDestShutdown);
+ }
- private void OnFtlDestStartup(EntityUid uid, FTLDestinationComponent component, ComponentStartup args)
- {
- RefreshShuttleConsoles();
- }
+ private void OnFtlDestStartup(EntityUid uid, FTLDestinationComponent component, ComponentStartup args)
+ {
+ RefreshShuttleConsoles();
+ }
- private void OnFtlDestShutdown(EntityUid uid, FTLDestinationComponent component, ComponentShutdown args)
- {
- RefreshShuttleConsoles();
- }
+ private void OnFtlDestShutdown(EntityUid uid, FTLDestinationComponent component, ComponentShutdown args)
+ {
+ RefreshShuttleConsoles();
+ }
- private void OnDestinationMessage(EntityUid uid, ShuttleConsoleComponent component, ShuttleConsoleDestinationMessage args)
+ private void OnDestinationMessage(EntityUid uid, ShuttleConsoleComponent component, ShuttleConsoleDestinationMessage args)
+ {
+ if (!TryComp<FTLDestinationComponent>(args.Destination, out var dest))
{
- if (!TryComp<FTLDestinationComponent>(args.Destination, out var dest))
- {
- return;
- }
-
- if (!dest.Enabled)
- return;
-
- EntityUid? entity = uid;
-
- var getShuttleEv = new ConsoleShuttleEvent
- {
- Console = uid,
- };
-
- RaiseLocalEvent(entity.Value, ref getShuttleEv);
- entity = getShuttleEv.Console;
-
- if (!TryComp<TransformComponent>(entity, out var xform) ||
- !TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
- {
- return;
- }
+ return;
+ }
- if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
- dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
- {
- return;
- }
+ if (!dest.Enabled)
+ return;
- if (HasComp<FTLComponent>(xform.GridUid))
- {
- _popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), args.Session);
- return;
- }
+ EntityUid? entity = uid;
- if (!_shuttle.CanFTL(xform.GridUid, out var reason))
- {
- _popup.PopupCursor(reason, args.Session);
- return;
- }
+ var getShuttleEv = new ConsoleShuttleEvent
+ {
+ Console = uid,
+ };
- var dock = HasComp<MapComponent>(args.Destination) && HasComp<MapGridComponent>(args.Destination);
- var tagEv = new FTLTagEvent();
- RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
+ RaiseLocalEvent(entity.Value, ref getShuttleEv);
+ entity = getShuttleEv.Console;
- _shuttle.FTLTravel(shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
+ if (!TryComp<TransformComponent>(entity, out var xform) ||
+ !TryComp<ShuttleComponent>(xform.GridUid, out var shuttle))
+ {
+ return;
}
- private void OnDock(DockEvent ev)
+ if (dest.Whitelist?.IsValid(entity.Value, EntityManager) == false &&
+ dest.Whitelist?.IsValid(xform.GridUid.Value, EntityManager) == false)
{
- RefreshShuttleConsoles();
+ return;
}
- private void OnUndock(UndockEvent ev)
+ var shuttleUid = xform.GridUid.Value;
+
+ if (HasComp<FTLComponent>(shuttleUid))
{
- RefreshShuttleConsoles();
+ _popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), args.Session);
+ return;
}
- public void RefreshShuttleConsoles(EntityUid uid)
+ if (!_shuttle.CanFTL(xform.GridUid, out var reason))
{
- // TODO: Should really call this per shuttle in some instances.
- RefreshShuttleConsoles();
+ _popup.PopupCursor(reason, args.Session);
+ return;
}
- /// <summary>
- /// Refreshes all of the data for shuttle consoles.
- /// </summary>
- public void RefreshShuttleConsoles()
- {
- var docks = GetAllDocks();
- var query = AllEntityQuery<ShuttleConsoleComponent>();
+ var dock = HasComp<MapComponent>(args.Destination) && HasComp<MapGridComponent>(args.Destination);
+ var tagEv = new FTLTagEvent();
+ RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
- while (query.MoveNext(out var uid, out var comp))
- {
- UpdateState(uid, comp, docks);
- }
- }
+ _shuttle.FTLTravel(xform.GridUid.Value, shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
+ }
- /// <summary>
- /// Stop piloting if the window is closed.
- /// </summary>
- private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args)
- {
- if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key ||
- args.Session.AttachedEntity is not { } user)
- {
- return;
- }
+ private void OnDock(DockEvent ev)
+ {
+ RefreshShuttleConsoles();
+ }
- // In case they D/C should still clean them up.
- foreach (var comp in EntityQuery<AutoDockComponent>(true))
- {
- comp.Requesters.Remove(user);
- }
+ private void OnUndock(UndockEvent ev)
+ {
+ RefreshShuttleConsoles();
+ }
- RemovePilot(user);
- }
+ public void RefreshShuttleConsoles(EntityUid uid)
+ {
+ // TODO: Should really call this per shuttle in some instances.
+ RefreshShuttleConsoles();
+ }
- private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
- {
- if (!TryPilot(args.User, uid))
- args.Cancel();
- }
+ /// <summary>
+ /// Refreshes all of the data for shuttle consoles.
+ /// </summary>
+ public void RefreshShuttleConsoles()
+ {
+ var docks = GetAllDocks();
+ var query = AllEntityQuery<ShuttleConsoleComponent>();
- private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
+ while (query.MoveNext(out var uid, out var comp))
{
- UpdateState(uid, component);
+ UpdateState(uid, docks);
}
+ }
- private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
+ /// <summary>
+ /// Stop piloting if the window is closed.
+ /// </summary>
+ private void OnConsoleUIClose(EntityUid uid, ShuttleConsoleComponent component, BoundUIClosedEvent args)
+ {
+ if ((ShuttleConsoleUiKey) args.UiKey != ShuttleConsoleUiKey.Key ||
+ args.Session.AttachedEntity is not { } user)
{
- UpdateState(uid, component);
+ return;
}
- private bool TryPilot(EntityUid user, EntityUid uid)
+ // In case they D/C should still clean them up.
+ foreach (var comp in EntityQuery<AutoDockComponent>(true))
{
- if (!_tags.HasTag(user, "CanPilot") ||
- !TryComp<ShuttleConsoleComponent>(uid, out var component) ||
- !this.IsPowered(uid, EntityManager) ||
- !Transform(uid).Anchored ||
- !_blocker.CanInteract(user, uid))
- {
- return false;
- }
+ comp.Requesters.Remove(user);
+ }
- var pilotComponent = EntityManager.EnsureComponent<PilotComponent>(user);
- var console = pilotComponent.Console;
+ RemovePilot(user);
+ }
- if (console != null)
- {
- RemovePilot(user, pilotComponent);
+ private void OnConsoleUIOpenAttempt(EntityUid uid, ShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
+ {
+ if (!TryPilot(args.User, uid))
+ args.Cancel();
+ }
- if (console == component)
- {
- return false;
- }
- }
+ private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
+ {
+ UpdateState(uid);
+ }
- AddPilot(user, component);
- return true;
- }
+ private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
+ {
+ UpdateState(uid);
+ }
- private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args)
+ private bool TryPilot(EntityUid user, EntityUid uid)
+ {
+ if (!_tags.HasTag(user, "CanPilot") ||
+ !TryComp<ShuttleConsoleComponent>(uid, out var component) ||
+ !this.IsPowered(uid, EntityManager) ||
+ !Transform(uid).Anchored ||
+ !_blocker.CanInteract(user, uid))
{
- args.State = new PilotComponentState(component.Console?.Owner);
+ return false;
}
- /// <summary>
- /// Returns the position and angle of all dockingcomponents.
- /// </summary>
- private List<DockingInterfaceState> GetAllDocks()
+ var pilotComponent = EnsureComp<PilotComponent>(user);
+ var console = pilotComponent.Console;
+
+ if (console != null)
{
- // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
- var result = new List<DockingInterfaceState>();
- var query = AllEntityQuery<DockingComponent, TransformComponent>();
+ RemovePilot(user, pilotComponent);
- while (query.MoveNext(out var uid, out var comp, out var xform))
+ if (console == component)
{
- if (xform.ParentUid != xform.GridUid)
- continue;
-
- var state = new DockingInterfaceState()
- {
- Coordinates = xform.Coordinates,
- Angle = xform.LocalRotation,
- Entity = uid,
- Connected = comp.Docked,
- Color = comp.RadarColor,
- HighlightedColor = comp.HighlightedRadarColor,
- };
- result.Add(state);
+ return false;
}
-
- return result;
}
- private void UpdateState(EntityUid consoleUid, ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null)
+ AddPilot(user, component);
+ return true;
+ }
+
+ private void OnGetState(EntityUid uid, PilotComponent component, ref ComponentGetState args)
+ {
+ args.State = new PilotComponentState(component.Console?.Owner);
+ }
+
+ /// <summary>
+ /// Returns the position and angle of all dockingcomponents.
+ /// </summary>
+ private List<DockingInterfaceState> GetAllDocks()
+ {
+ // TODO: NEED TO MAKE SURE THIS UPDATES ON ANCHORING CHANGES!
+ var result = new List<DockingInterfaceState>();
+ var query = AllEntityQuery<DockingComponent, TransformComponent>();
+
+ while (query.MoveNext(out var uid, out var comp, out var xform))
{
- EntityUid? entity = consoleUid;
+ if (xform.ParentUid != xform.GridUid)
+ continue;
- var getShuttleEv = new ConsoleShuttleEvent
+ var state = new DockingInterfaceState()
{
- Console = entity,
+ Coordinates = xform.Coordinates,
+ Angle = xform.LocalRotation,
+ Entity = uid,
+ Connected = comp.Docked,
+ Color = comp.RadarColor,
+ HighlightedColor = comp.HighlightedRadarColor,
};
+ result.Add(state);
+ }
- RaiseLocalEvent(entity.Value, ref getShuttleEv);
- entity = getShuttleEv.Console;
-
- TryComp<TransformComponent>(entity, out var consoleXform);
- TryComp<RadarConsoleComponent>(entity, out var radar);
- var range = radar?.MaxRange ?? SharedRadarConsoleSystem.DefaultMaxRange;
-
- var shuttleGridUid = consoleXform?.GridUid;
+ return result;
+ }
- var destinations = new List<(EntityUid, string, bool)>();
- var ftlState = FTLState.Available;
- var ftlTime = TimeSpan.Zero;
+ private void UpdateState(EntityUid consoleUid, List<DockingInterfaceState>? docks = null)
+ {
+ EntityUid? entity = consoleUid;
- if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
- {
- ftlState = shuttleFtl.State;
- ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
- }
+ var getShuttleEv = new ConsoleShuttleEvent
+ {
+ Console = entity,
+ };
- // Mass too large
- if (entity != null && shuttleGridUid != null &&
- (!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
- {
- var metaQuery = GetEntityQuery<MetaDataComponent>();
+ RaiseLocalEvent(entity.Value, ref getShuttleEv);
+ entity = getShuttleEv.Console;
- // Can't go anywhere when in FTL.
- var locked = shuttleFtl != null || Paused(shuttleGridUid.Value);
+ TryComp<TransformComponent>(entity, out var consoleXform);
+ TryComp<RadarConsoleComponent>(entity, out var radar);
+ var range = radar?.MaxRange ?? SharedRadarConsoleSystem.DefaultMaxRange;
- // Can't cache it because it may have a whitelist for the particular console.
- // Include paused as we still want to show CentCom.
- var destQuery = AllEntityQuery<FTLDestinationComponent>();
+ var shuttleGridUid = consoleXform?.GridUid;
- while (destQuery.MoveNext(out var destUid, out var comp))
- {
- // Can't warp to itself or if it's not on the whitelist (console or shuttle).
- if (destUid == shuttleGridUid ||
- comp.Whitelist?.IsValid(entity.Value) == false &&
- (shuttleGridUid == null || comp.Whitelist?.IsValid(shuttleGridUid.Value, EntityManager) == false))
- {
- continue;
- }
-
- var meta = metaQuery.GetComponent(destUid);
- var name = meta.EntityName;
-
- if (string.IsNullOrEmpty(name))
- name = Loc.GetString("shuttle-console-unknown");
-
- var canTravel = !locked &&
- comp.Enabled &&
- (!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown);
-
- // Can't travel to same map (yet)
- if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
- {
- canTravel = false;
- }
-
- destinations.Add((destUid, name, canTravel));
- }
- }
+ var destinations = new List<(EntityUid, string, bool)>();
+ var ftlState = FTLState.Available;
+ var ftlTime = TimeSpan.Zero;
- docks ??= GetAllDocks();
-
- _ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
- ?.SetState(new ShuttleConsoleBoundInterfaceState(
- ftlState,
- ftlTime,
- destinations,
- range,
- consoleXform?.Coordinates,
- consoleXform?.LocalRotation,
- docks));
+ if (TryComp<FTLComponent>(shuttleGridUid, out var shuttleFtl))
+ {
+ ftlState = shuttleFtl.State;
+ ftlTime = _timing.CurTime + TimeSpan.FromSeconds(shuttleFtl.Accumulator);
}
- public override void Update(float frameTime)
+ // Mass too large
+ if (entity != null && shuttleGridUid != null &&
+ (!TryComp<PhysicsComponent>(shuttleGridUid, out var shuttleBody) || shuttleBody.Mass < 1000f))
{
- base.Update(frameTime);
+ var metaQuery = GetEntityQuery<MetaDataComponent>();
+
+ // Can't go anywhere when in FTL.
+ var locked = shuttleFtl != null || Paused(shuttleGridUid.Value);
- var toRemove = new RemQueue<PilotComponent>();
- var query = EntityQueryEnumerator<PilotComponent>();
+ // Can't cache it because it may have a whitelist for the particular console.
+ // Include paused as we still want to show CentCom.
+ var destQuery = AllEntityQuery<FTLDestinationComponent>();
- while (query.MoveNext(out var uid, out var comp))
+ while (destQuery.MoveNext(out var destUid, out var comp))
{
- if (comp.Console == null)
+ // Can't warp to itself or if it's not on the whitelist (console or shuttle).
+ if (destUid == shuttleGridUid ||
+ comp.Whitelist?.IsValid(entity.Value) == false &&
+ (shuttleGridUid == null || comp.Whitelist?.IsValid(shuttleGridUid.Value, EntityManager) == false))
+ {
continue;
+ }
- if (!_blocker.CanInteract(uid, comp.Console.Owner))
+ var meta = metaQuery.GetComponent(destUid);
+ var name = meta.EntityName;
+
+ if (string.IsNullOrEmpty(name))
+ name = Loc.GetString("shuttle-console-unknown");
+
+ var canTravel = !locked &&
+ comp.Enabled &&
+ (!TryComp<FTLComponent>(destUid, out var ftl) || ftl.State == FTLState.Cooldown);
+
+ // Can't travel to same map (yet)
+ if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
{
- toRemove.Add(comp);
+ canTravel = false;
}
- }
- foreach (var comp in toRemove)
- {
- RemovePilot(comp.Owner, comp);
+ destinations.Add((destUid, name, canTravel));
}
}
- /// <summary>
- /// If pilot is moved then we'll stop them from piloting.
- /// </summary>
- private void HandlePilotMove(EntityUid uid, PilotComponent component, ref MoveEvent args)
+ docks ??= GetAllDocks();
+
+ _ui.GetUiOrNull(consoleUid, ShuttleConsoleUiKey.Key)
+ ?.SetState(new ShuttleConsoleBoundInterfaceState(
+ ftlState,
+ ftlTime,
+ destinations,
+ range,
+ consoleXform?.Coordinates,
+ consoleXform?.LocalRotation,
+ docks));
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var toRemove = new ValueList<(EntityUid, PilotComponent)>();
+ var query = EntityQueryEnumerator<PilotComponent>();
+
+ while (query.MoveNext(out var uid, out var comp))
{
- if (component.Console == null || component.Position == null)
- {
- DebugTools.Assert(component.Position == null && component.Console == null);
- EntityManager.RemoveComponent<PilotComponent>(uid);
- return;
- }
+ if (comp.Console == null)
+ continue;
- if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) &&
- distance < PilotComponent.BreakDistance)
+ if (!_blocker.CanInteract(uid, comp.Console.Owner))
{
- return;
+ toRemove.Add((uid, comp));
}
-
- RemovePilot(uid, component);
}
- protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
+ foreach (var (uid, comp) in toRemove)
{
- base.HandlePilotShutdown(uid, component, args);
- RemovePilot(uid, component);
+ RemovePilot(uid, comp);
}
+ }
- private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
+ /// <summary>
+ /// If pilot is moved then we'll stop them from piloting.
+ /// </summary>
+ private void HandlePilotMove(EntityUid uid, PilotComponent component, ref MoveEvent args)
+ {
+ if (component.Console == null || component.Position == null)
{
- ClearPilots(component);
+ DebugTools.Assert(component.Position == null && component.Console == null);
+ EntityManager.RemoveComponent<PilotComponent>(uid);
+ return;
}
- public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
+ if (args.NewPosition.TryDistance(EntityManager, component.Position.Value, out var distance) &&
+ distance < PilotComponent.BreakDistance)
{
- if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
- component.SubscribedPilots.Contains(pilotComponent))
- {
- return;
- }
+ return;
+ }
- if (TryComp<SharedEyeComponent>(entity, out var eye))
- {
- eye.Zoom = component.Zoom;
- }
+ RemovePilot(uid, component);
+ }
- component.SubscribedPilots.Add(pilotComponent);
+ protected override void HandlePilotShutdown(EntityUid uid, PilotComponent component, ComponentShutdown args)
+ {
+ base.HandlePilotShutdown(uid, component, args);
+ RemovePilot(uid, component);
+ }
- _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);
+ private void OnConsoleShutdown(EntityUid uid, ShuttleConsoleComponent component, ComponentShutdown args)
+ {
+ ClearPilots(component);
+ }
- pilotComponent.Console = component;
- ActionBlockerSystem.UpdateCanMove(entity);
- pilotComponent.Position = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
- Dirty(pilotComponent);
+ public void AddPilot(EntityUid entity, ShuttleConsoleComponent component)
+ {
+ if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent) ||
+ component.SubscribedPilots.Contains(pilotComponent))
+ {
+ return;
}
- public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
+ if (TryComp<SharedEyeComponent>(entity, out var eye))
{
- var console = pilotComponent.Console;
+ eye.Zoom = component.Zoom;
+ }
- if (console is not ShuttleConsoleComponent helmsman)
- return;
+ component.SubscribedPilots.Add(pilotComponent);
- pilotComponent.Console = null;
- pilotComponent.Position = null;
+ _alertsSystem.ShowAlert(entity, AlertType.PilotingShuttle);
- if (TryComp<SharedEyeComponent>(pilotUid, out var eye))
- {
- eye.Zoom = new(1.0f, 1.0f);
- }
+ pilotComponent.Console = component;
+ ActionBlockerSystem.UpdateCanMove(entity);
+ pilotComponent.Position = EntityManager.GetComponent<TransformComponent>(entity).Coordinates;
+ Dirty(pilotComponent);
+ }
- if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return;
+ public void RemovePilot(EntityUid pilotUid, PilotComponent pilotComponent)
+ {
+ var console = pilotComponent.Console;
- _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
+ if (console is not ShuttleConsoleComponent helmsman)
+ return;
- pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end"));
+ pilotComponent.Console = null;
+ pilotComponent.Position = null;
- if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
- EntityManager.RemoveComponent<PilotComponent>(pilotUid);
+ if (TryComp<SharedEyeComponent>(pilotUid, out var eye))
+ {
+ eye.Zoom = new(1.0f, 1.0f);
}
- public void RemovePilot(EntityUid entity)
- {
- if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return;
+ if (!helmsman.SubscribedPilots.Remove(pilotComponent))
+ return;
- RemovePilot(entity, pilotComponent);
- }
+ _alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
+
+ _popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid);
- public void ClearPilots(ShuttleConsoleComponent component)
+ if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
+ EntityManager.RemoveComponent<PilotComponent>(pilotUid);
+ }
+
+ public void RemovePilot(EntityUid entity)
+ {
+ if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent))
+ return;
+
+ RemovePilot(entity, pilotComponent);
+ }
+
+ public void ClearPilots(ShuttleConsoleComponent component)
+ {
+ while (component.SubscribedPilots.TryGetValue(0, out var pilot))
{
- while (component.SubscribedPilots.TryGetValue(0, out var pilot))
- {
- RemovePilot(pilot.Owner, pilot);
- }
+ RemovePilot(pilot.Owner, pilot);
}
}
}
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;
* 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;
return true;
}
- var bounds = xform.WorldMatrix.TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
+ var bounds = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
var bodyQuery = GetEntityQuery<PhysicsComponent>();
foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
{
- if (grid.Owner == other.Owner ||
+ if (uid == other.Owner ||
!bodyQuery.TryGetComponent(other.Owner, out var body) ||
- body.Mass < ShuttleFTLMassThreshold) continue;
+ body.Mass < ShuttleFTLMassThreshold)
+ {
+ continue;
+ }
reason = Loc.GetString("shuttle-console-proximity");
return false;
/// </summary>
public FTLDestinationComponent AddFTLDestination(EntityUid uid, bool enabled)
{
- if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled) return destination;
+ if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled)
+ return destination;
destination = EnsureComp<FTLDestinationComponent>(uid);
{
if (!RemComp<FTLDestinationComponent>(uid))
return;
+
_console.RefreshShuttleConsoles();
}
/// <summary>
/// Moves a shuttle from its current position to the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
- public void FTLTravel(ShuttleComponent component,
+ public void FTLTravel(
+ EntityUid shuttleUid,
+ ShuttleComponent component,
EntityCoordinates coordinates,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime,
string? priorityTag = null)
{
- if (!TrySetupFTL(component, out var hyperspace))
+ if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
hyperspace.StartupTime = startupTime;
/// <summary>
/// Moves a shuttle from its current position to docked on the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
- public void FTLTravel(ShuttleComponent component,
+ public void FTLTravel(
+ EntityUid shuttleUid,
+ ShuttleComponent component,
EntityUid target,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime,
bool dock = false,
string? priorityTag = null)
{
- if (!TrySetupFTL(component, out var hyperspace))
+ if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
hyperspace.StartupTime = startupTime;
_console.RefreshShuttleConsoles();
}
- private bool TrySetupFTL(ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
+ private bool TrySetupFTL(EntityUid uid, ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
{
- var uid = shuttle.Owner;
component = null;
if (HasComp<FTLComponent>(uid))
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;
}
comp.TravelStream = null;
}
- SoundSystem.Play(_arrivalSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), _arrivalSound.Params);
+ _audio.PlayGlobal(_arrivalSound, Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), true);
if (TryComp<FTLDestinationComponent>(uid, out var dest))
{
{
foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
{
- if (xform.ParentUid != uid || dock.Enabled == enabled) continue;
+ if (xform.ParentUid != uid || dock.Enabled == enabled)
+ continue;
+
_dockSystem.Undock(dock);
dock.Enabled = enabled;
}
private void SetDockBolts(EntityUid uid, bool enabled)
{
- foreach (var (_, door, xform) in EntityQuery<DockingComponent, AirlockComponent, TransformComponent>(true))
+ var query = AllEntityQuery<DockingComponent, AirlockComponent, TransformComponent>();
+
+ while (query.MoveNext(out var doorUid, out _, out var door, out var xform))
{
- if (xform.ParentUid != uid) continue;
+ if (xform.ParentUid != uid)
+ continue;
- _doors.TryClose(door.Owner);
- _airlock.SetBoltsWithAudio(door.Owner, door, enabled);
+ _doors.TryClose(doorUid);
+ _airlock.SetBoltsWithAudio(doorUid, door, enabled);
}
}
private float GetSoundRange(EntityUid uid)
{
- if (!_mapManager.TryGetGrid(uid, out var grid)) return 4f;
+ if (!_mapManager.TryGetGrid(uid, out var grid))
+ return 4f;
return MathF.Max(grid.LocalAABB.Width, grid.LocalAABB.Height) + 12.5f;
}
private void SetupHyperspace()
{
- if (_hyperSpaceMap != null) return;
+ if (_hyperSpaceMap != null)
+ return;
_hyperSpaceMap = _mapManager.CreateMap();
_sawmill.Info($"Setup hyperspace map at {_hyperSpaceMap.Value}");
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);
}
/// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity.
/// </summary>
- /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
- public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
+ public bool TryFTLDock(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
{
- if (!TryComp<TransformComponent>(component.Owner, out var xform) ||
+ if (!TryComp<TransformComponent>(shuttleUid, out var shuttleXform) ||
!TryComp<TransformComponent>(targetUid, out var targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid())
return false;
}
- var config = GetDockingConfig(component, targetUid, priorityTag);
+ var config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag);
if (config != null)
{
- // Set position
- xform.Coordinates = config.Coordinates;
- xform.WorldRotation = config.Angle;
-
- // Connect everything
- foreach (var (dockA, dockB) in config.Docks)
- {
- _dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
- }
-
- return true;
+ FTLDock(config, shuttleXform);
+ return true;
}
- TryFTLProximity(component, targetUid, xform, targetXform);
+ TryFTLProximity(shuttleUid, component, targetUid, shuttleXform, targetXform);
return false;
}
+ /// <summary>
+ /// Forces an FTL dock.
+ /// </summary>
+ public void FTLDock(DockingConfig config, TransformComponent shuttleXform)
+ {
+ // Set position
+ shuttleXform.Coordinates = config.Coordinates;
+ _transform.SetWorldRotation(shuttleXform, config.Angle);
+
+ // Connect everything
+ foreach (var (dockAUid, dockBUid, dockA, dockB) in config.Docks)
+ {
+ _dockSystem.Dock(dockAUid, dockA, dockBUid, dockB);
+ }
+ }
+
/// <summary>
/// Tries to arrive nearby without overlapping with other grids.
/// </summary>
- public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
+ public bool TryFTLProximity(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
{
if (!Resolve(targetUid, ref targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid() ||
- !Resolve(component.Owner, ref xform))
+ !Resolve(shuttleUid, ref xform))
{
return false;
}
var xformQuery = GetEntityQuery<TransformComponent>();
- var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
+ var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
Box2 targetLocalAABB;
// Spawn nearby.
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, targetAABB))
{
- if (!nearbyGrids.Add(grid.Owner)) continue;
+ if (!nearbyGrids.Add(grid.Owner))
+ continue;
targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
.TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
foreach (var grid in _mapManager.GetAllGrids())
{
// Don't add anymore as it is irrelevant, but that doesn't mean we need to re-do existing work.
- if (nearbyGrids.Contains(grid.Owner)) continue;
+ if (nearbyGrids.Contains(grid.Owner))
+ continue;
targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
.TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
Vector2 spawnPos;
- if (TryComp<PhysicsComponent>(component.Owner, out var shuttleBody))
+ if (TryComp<PhysicsComponent>(shuttleUid, out var shuttleBody))
{
- _physics.SetLinearVelocity(component.Owner, Vector2.Zero, body: shuttleBody);
- _physics.SetAngularVelocity(component.Owner, 0f, body: shuttleBody);
+ _physics.SetLinearVelocity(shuttleUid, Vector2.Zero, body: shuttleBody);
+ _physics.SetAngularVelocity(shuttleUid, 0f, body: shuttleBody);
}
// TODO: This is pretty crude for multiple landings.
return true;
}
-
- /// <summary>
- /// Checks whether the emergency shuttle can warp to the specified position.
- /// </summary>
- private bool ValidSpawn(EntityUid gridUid, MapGridComponent grid, Box2 area)
- {
- // If the target is a map then any tile is valid.
- // TODO: We already need the entities-under check
- if (HasComp<MapComponent>(gridUid))
- return true;
-
- return !grid.GetLocalTilesIntersecting(area).Any();
- }
-
- /// <summary>
- /// Tries to get a valid docking configuration for the shuttle to the target grid.
- /// </summary>
- /// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
- private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid, string? priorityTag = null)
- {
- var gridDocks = GetDocks(targetGrid);
-
- if (gridDocks.Count <= 0)
- return null;
-
- var xformQuery = GetEntityQuery<TransformComponent>();
- var targetGridGrid = Comp<MapGridComponent>(targetGrid);
- var targetGridXform = xformQuery.GetComponent(targetGrid);
- var targetGridAngle = targetGridXform.WorldRotation.Reduced();
-
- var shuttleDocks = GetDocks(component.Owner);
- var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
-
- var validDockConfigs = new List<DockingConfig>();
-
- if (shuttleDocks.Count > 0)
- {
- // We'll try all combinations of shuttle docks and see which one is most suitable
- foreach (var shuttleDock in shuttleDocks)
- {
- var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);
-
- foreach (var gridDock in gridDocks)
- {
- var gridXform = xformQuery.GetComponent(gridDock.Owner);
-
- if (!CanDock(
- shuttleDock, shuttleDockXform,
- gridDock, gridXform,
- targetGridAngle,
- shuttleAABB,
- targetGrid,
- targetGridGrid,
- out var dockedAABB,
- out var matty,
- out var targetAngle)) continue;
-
- // Can't just use the AABB as we want to get bounds as tight as possible.
- var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
- spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
-
- var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
-
- // Check if there's no intersecting grids (AKA oh god it's docking at cargo).
- if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
- dockedBounds).Any(o => o.Owner != targetGrid))
- {
- continue;
- }
-
- // Alright well the spawn is valid now to check how many we can connect
- // Get the matrix for each shuttle dock and test it against the grid docks to see
- // if the connected position / direction matches.
-
- var dockedPorts = new List<(DockingComponent DockA, DockingComponent DockB)>()
- {
- (shuttleDock, gridDock),
- };
-
- foreach (var other in shuttleDocks)
- {
- if (other == shuttleDock) continue;
-
- foreach (var otherGrid in gridDocks)
- {
- if (otherGrid == gridDock) continue;
-
- if (!CanDock(
- other,
- xformQuery.GetComponent(other.Owner),
- otherGrid,
- xformQuery.GetComponent(otherGrid.Owner),
- targetGridAngle,
- shuttleAABB,
- targetGrid,
- targetGridGrid,
- out var otherDockedAABB,
- out _,
- out var otherTargetAngle) ||
- !otherDockedAABB.Equals(dockedAABB) ||
- !targetAngle.Equals(otherTargetAngle)) continue;
-
- dockedPorts.Add((other, otherGrid));
- }
- }
-
- validDockConfigs.Add(new DockingConfig()
- {
- Docks = dockedPorts,
- Area = dockedAABB.Value,
- Coordinates = spawnPosition,
- Angle = targetAngle,
- });
- }
- }
- }
-
- if (validDockConfigs.Count <= 0)
- return null;
-
- // Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
- validDockConfigs = validDockConfigs
- .OrderByDescending(x => x.Docks.Any(docks =>
- TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
- priority.Tag?.Equals(priorityTag) == true))
- .ThenByDescending(x => x.Docks.Count)
- .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
-
- var location = validDockConfigs.First();
- location.TargetGrid = targetGrid;
- // TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
-
- return location;
- }
}
--- /dev/null
+using Content.Server.Shuttles.Components;
+
+namespace Content.Server.Shuttles.Systems;
+
+public sealed partial class ShuttleSystem
+{
+ private void InitializeGridFills()
+ {
+ SubscribeLocalEvent<GridFillComponent, MapInitEvent>(OnGridFillMapInit);
+ }
+
+ private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args)
+ {
+ if (!TryComp<DockingComponent>(uid, out var dock) ||
+ !TryComp<TransformComponent>(uid, out var xform) ||
+ xform.GridUid == null)
+ {
+ return;
+ }
+
+ // Spawn on a dummy map and try to dock if possible, otherwise dump it.
+ var mapId = _mapManager.CreateMap();
+ var valid = false;
+
+ if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) &&
+ ent.Count == 1 &&
+ TryComp<TransformComponent>(ent[0], out var shuttleXform))
+ {
+ var escape = GetSingleDock(ent[0]);
+
+ if (escape != null)
+ {
+ var config = _dockSystem.GetDockingConfig(ent[0], xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock);
+
+ if (config != null)
+ {
+ FTLDock(config, shuttleXform);
+ valid = true;
+ }
+ }
+ }
+
+ if (!valid)
+ {
+ _sawmill.Error($"Error loading gridfill dock for {ToPrettyString(uid)} / {component.Path}");
+ }
+
+ _mapManager.DeleteMap(mapId);
+ }
+
+ private (EntityUid Entity, DockingComponent Component)? GetSingleDock(EntityUid uid)
+ {
+ var dockQuery = GetEntityQuery<DockingComponent>();
+ var xformQuery = GetEntityQuery<TransformComponent>();
+ var xform = xformQuery.GetComponent(uid);
+
+ var rator = xform.ChildEnumerator;
+
+ while (rator.MoveNext(out var child))
+ {
+ if (!dockQuery.TryGetComponent(child, out var dock))
+ continue;
+
+ return (child.Value, dock);
+ }
+
+ return null;
+ }
+}
+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
{
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!;
base.Initialize();
_sawmill = Logger.GetSawmill("shuttles");
- InitializeEmergencyConsole();
- InitializeEscape();
InitializeFTL();
+ InitializeGridFills();
InitializeIFF();
InitializeImpact();
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.
private void OnGridFixtureChange(GridFixtureChangeEvent args)
{
// Look this is jank but it's a placeholder until we design it.
- if (args.NewFixtures.Count == 0) return;
+ if (args.NewFixtures.Count == 0)
+ return;
var uid = args.NewFixtures[0].Body.Owner;
var manager = Comp<FixturesComponent>(uid);
private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args)
{
- if (!EntityManager.HasComponent<MapGridComponent>(component.Owner))
+ if (!EntityManager.HasComponent<MapGridComponent>(uid))
{
return;
}
- if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
+ if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
{
return;
}
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;
// None of the below is necessary for any cleanup if we're just deleting.
if (EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
- if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
+ if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
{
return;
}
/// <summary>
/// The emergency shuttle assigned to this station.
/// </summary>
- [ViewVariables, Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadWrite)]
+ [ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)]
public EntityUid? EmergencyShuttle;
}
public abstract void PopupEntity(string message, EntityUid uid, PopupType type=PopupType.Small);
/// <summary>
- /// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
+ /// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
/// </summary>
public abstract void PopupEntity(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small);
/// <summary>
- /// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
+ /// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
/// </summary>
public abstract void PopupEntity(string message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small);
--- /dev/null
+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
+...
- type: PriorityDock
tag: DockArrivals
+- type: entity
+ parent: AirlockGlassShuttle
+ id: AirlockExternalGlassShuttleEscape
+ suffix: External, Escape 3x4, Glass, Docking
+ components:
+ - type: GridFill
+
#HighSecDoors
- type: entity
parent: HighSecDoor