Escape pods (#14809)

* Namespace adjustments for days

* pod

* thanks rider

* Fix the oop launch

* Fixes

* Fix stuff

* eeeeeeeee

* Fix

* access

* map

* forgor

* thing

* Genericise escape pod fill
This commit is contained in:
metalgearsloth
2023-03-24 12:54:41 +11:00
committed by GitHub
parent 9aadc77b92
commit 448165ffda
28 changed files with 1417 additions and 747 deletions

View File

@@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests
Assert.IsNotNull(stationConfig, $"{entManager.ToPrettyString(station)} had null StationConfig.");
var shuttlePath = stationConfig.EmergencyShuttlePath.ToString();
var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath);
Assert.That(shuttle != null && shuttleSystem.TryFTLDock(entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
mapManager.DeleteMap(shuttleMap);

View File

@@ -30,10 +30,10 @@ namespace Content.Server.Communications
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
@@ -186,7 +186,7 @@ namespace Content.Server.Communications
private bool CanCallOrRecall(CommunicationsConsoleComponent comp)
{
// Defer to what the round end system thinks we should be able to do.
if (_shuttle.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
if (_emergency.EmergencyShuttleArrived || !_roundEndSystem.CanCallOrRecall())
return false;
// Calling shuttle checks

View File

@@ -43,15 +43,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playerSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly FactionSystem _faction = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly ShuttleSystem _shuttleSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
private enum WinType
@@ -277,7 +278,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
}
// UH OH
if (nukeTransform.MapID == _shuttleSystem.CentComMap)
if (nukeTransform.MapID == _emergency.CentComMap)
{
_winConditions.Add(WinCondition.NukeActiveAtCentCom);
RuleWinType = WinType.OpsMajor;
@@ -334,7 +335,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
foreach (var (_, transform) in EntityManager.EntityQuery<NukeDiskComponent, TransformComponent>())
{
var diskMapId = transform.MapID;
diskAtCentCom = _shuttleSystem.CentComMap == diskMapId;
diskAtCentCom = _emergency.CentComMap == diskMapId;
// TODO: The target station should be stored, and the nuke disk should store its original station.
// This is fine for now, because we can assume a single station in base SS14.
@@ -655,7 +656,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
if (TryComp<ShuttleComponent>(shuttleId, out var shuttle))
{
_shuttleSystem.TryFTLDock(shuttle, _nukieOutpost.Value);
_shuttle.TryFTLDock(shuttleId, shuttle, _nukieOutpost.Value);
}
_nukiePlanet = mapId;

View File

@@ -32,7 +32,7 @@ namespace Content.Server.RoundEnd
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly EmergencyShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
public TimeSpan DefaultCooldownDuration { get; set; } = TimeSpan.FromSeconds(30);

View File

@@ -11,12 +11,15 @@ namespace Content.Server.Shuttles.Commands;
[AdminCommand(AdminFlags.Fun)]
public sealed class DelayRoundEndCommand : IConsoleCommand
{
[Dependency] private readonly IEntitySystemManager _sysManager = default!;
public string Command => "delayroundend";
public string Description => Loc.GetString("emergency-shuttle-command-round-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
if (system.DelayEmergencyRoundEnd())
{
shell.WriteLine(Loc.GetString("emergency-shuttle-command-round-yes"));

View File

@@ -4,7 +4,7 @@ using Content.Server.Shuttles.Systems;
using Content.Shared.Administration;
using Robust.Shared.Console;
namespace Content.Server.Shuttles;
namespace Content.Server.Shuttles.Commands;
[AdminCommand(AdminFlags.Mapping)]
public sealed class DockCommand : IConsoleCommand

View File

@@ -11,12 +11,14 @@ namespace Content.Server.Shuttles.Commands;
[AdminCommand(AdminFlags.Fun)]
public sealed class DockEmergencyShuttleCommand : IConsoleCommand
{
[Dependency] private readonly IEntitySystemManager _sysManager = default!;
public string Command => "dockemergencyshuttle";
public string Description => Loc.GetString("emergency-shuttle-command-dock-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
system.CallEmergencyShuttle();
}
}

View File

@@ -11,12 +11,14 @@ namespace Content.Server.Shuttles.Commands;
[AdminCommand(AdminFlags.Fun)]
public sealed class LaunchEmergencyShuttleCommand : IConsoleCommand
{
[Dependency] private readonly IEntitySystemManager _sysManager = default!;
public string Command => "launchemergencyshuttle";
public string Description => Loc.GetString("emergency-shuttle-command-launch-desc");
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
var system = _sysManager.GetEntitySystem<EmergencyShuttleSystem>();
system.EarlyLaunch();
}
}

View File

@@ -1,8 +1,9 @@
using Content.Server.Shuttles.Systems;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Shuttles.Components;
[RegisterComponent]
[RegisterComponent, Access(typeof(ArrivalsSystem))]
public sealed class ArrivalsShuttleComponent : Component
{
[DataField("station")]

View File

@@ -1,9 +1,11 @@
using Content.Server.Shuttles.Systems;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a designated arrivals station for players to spawn at, if enabled.
/// </summary>
[RegisterComponent]
[RegisterComponent, Access(typeof(ArrivalsSystem))]
public sealed class ArrivalsSourceComponent : Component
{

View File

@@ -0,0 +1,14 @@
using Content.Server.Shuttles.Systems;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// If added to a grid gets launched when the emergency shuttle launches.
/// </summary>
[RegisterComponent, Access(typeof(EmergencyShuttleSystem))]
public sealed class EscapePodComponent : Component
{
[DataField("launchTime", customTypeSerializer:typeof(TimeOffsetSerializer))]
public TimeSpan? LaunchTime;
}

View File

@@ -0,0 +1,13 @@
using Content.Server.Shuttles.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Components;
/// <summary>
/// If added to an airlock will try to autofill a grid onto it on MapInit
/// </summary>
[RegisterComponent, Access(typeof(ShuttleSystem))]
public sealed class GridFillComponent : Component
{
[DataField("path")] public ResourcePath Path = new("/Maps/Shuttles/escape_pod_small.yml");
}

View File

@@ -1,3 +1,4 @@
using Content.Server.Shuttles.Systems;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Components;
@@ -5,7 +6,7 @@ namespace Content.Server.Shuttles.Components;
/// <summary>
/// Added to a station that is available for arrivals shuttles.
/// </summary>
[RegisterComponent]
[RegisterComponent, Access(typeof(ArrivalsSystem))]
public sealed class StationArrivalsComponent : Component
{
[DataField("shuttle")]

View File

@@ -0,0 +1,28 @@
using Content.Server.Shuttles.Components;
using Robust.Shared.Map;
namespace Content.Server.Shuttles;
/// <summary>
/// Stores the data for a valid docking configuration for the emergency shuttle
/// </summary>
public sealed class DockingConfig
{
/// <summary>
/// The pairs of docks that can connect.
/// </summary>
public List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)> Docks = new();
/// <summary>
/// Area relative to the target grid the emergency shuttle will spawn in on.
/// </summary>
public Box2 Area;
/// <summary>
/// Target grid for docking.
/// </summary>
public EntityUid TargetGrid;
public EntityCoordinates Coordinates;
public Angle Angle;
}

View File

@@ -280,7 +280,7 @@ public sealed class ArrivalsSystem : EntitySystem
// TODO: Need some kind of comp to shunt people off if they try to get on?
if (TryComp<TransformComponent>(arrivals, out var arrivalsXform))
{
while (query.MoveNext(out var comp, out var shuttle, out var xform))
while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform))
{
if (comp.NextTransfer > curTime || !TryComp<StationDataComponent>(comp.Station, out var data))
continue;
@@ -289,7 +289,7 @@ public sealed class ArrivalsSystem : EntitySystem
if (xform.MapUid != arrivalsXform.MapUid)
{
if (arrivals.IsValid())
_shuttles.FTLTravel(shuttle, arrivals, dock: true);
_shuttles.FTLTravel(uid, shuttle, arrivals, dock: true);
}
// Go to station
else
@@ -297,7 +297,7 @@ public sealed class ArrivalsSystem : EntitySystem
var targetGrid = _station.GetLargestGrid(data);
if (targetGrid != null)
_shuttles.FTLTravel(shuttle, targetGrid.Value, dock: true);
_shuttles.FTLTravel(uid, shuttle, targetGrid.Value, dock: true);
}
comp.NextTransfer += TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
@@ -395,7 +395,7 @@ public sealed class ArrivalsSystem : EntitySystem
var arrivalsComp = EnsureComp<ArrivalsShuttleComponent>(component.Shuttle);
arrivalsComp.Station = uid;
EnsureComp<ProtectedGridComponent>(uid);
_shuttles.FTLTravel(shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
_shuttles.FTLTravel(component.Shuttle, shuttleComp, arrivals, hyperspaceTime: 10f, dock: true);
arrivalsComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
}

View File

@@ -56,8 +56,8 @@ public sealed partial class DockingSystem
continue;
}
var worldPos = _transformSystem.GetWorldPosition(xform, xformQuery);
var otherWorldPos = _transformSystem.GetWorldPosition(otherXform, xformQuery);
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
var otherWorldPos = _transform.GetWorldPosition(otherXform, xformQuery);
if ((worldPos - otherWorldPos).Length < comp.Radius) continue;

View File

@@ -0,0 +1,267 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Shuttles.Components;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
namespace Content.Server.Shuttles.Systems;
public sealed partial class DockingSystem
{
/*
* Handles the shuttle side of FTL docking.
*/
public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
{
var (shuttlePos, shuttleRot) = _transform.GetWorldPositionRotation(xform, xformQuery);
var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform, xformQuery);
var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
Comp<PhysicsComponent>(uid).LocalCenter);
var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
Comp<PhysicsComponent>(targetUid).LocalCenter);
var mapDiff = shuttleCOM - targetCOM;
var angle = mapDiff.ToWorldAngle();
angle -= targetRot;
return angle;
}
/// <summary>
/// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
/// </summary>
public bool CanDock(
DockingComponent shuttleDock,
TransformComponent shuttleDockXform,
DockingComponent gridDock,
TransformComponent gridDockXform,
Angle targetGridRotation,
Box2 shuttleAABB,
MapGridComponent grid,
[NotNullWhen(true)] out Box2? shuttleDockedAABB,
out Matrix3 matty,
out Angle gridRotation)
{
gridRotation = Angle.Zero;
matty = Matrix3.Identity;
shuttleDockedAABB = null;
if (shuttleDock.Docked ||
gridDock.Docked ||
!shuttleDockXform.Anchored ||
!gridDockXform.Anchored)
{
return false;
}
// First, get the station dock's position relative to the shuttle, this is where we rotate it around
var stationDockPos = shuttleDockXform.LocalPosition +
shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
// Need to invert the grid's angle.
var shuttleDockAngle = shuttleDockXform.LocalRotation;
var gridDockAngle = gridDockXform.LocalRotation.Opposite();
var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
shuttleDockedAABB = matty.TransformBox(shuttleAABB);
// Rounding moment
shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
if (!ValidSpawn(grid, shuttleDockedAABB.Value))
return false;
gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
return true;
}
/// <summary>
/// Gets docking config between 2 specific docks.
/// </summary>
public DockingConfig? GetDockingConfig(
EntityUid shuttleUid,
EntityUid targetGrid,
EntityUid shuttleDockUid,
DockingComponent shuttleDock,
EntityUid gridDockUid,
DockingComponent gridDock)
{
var shuttleDocks = new List<(EntityUid, DockingComponent)>(1)
{
(shuttleDockUid, shuttleDock)
};
var gridDocks = new List<(EntityUid, DockingComponent)>(1)
{
(gridDockUid, gridDock)
};
return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks);
}
/// <summary>
/// Tries to get a valid docking configuration for the shuttle to the target grid.
/// </summary>
/// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
public DockingConfig? GetDockingConfig(EntityUid shuttleUid, EntityUid targetGrid, string? priorityTag = null)
{
var gridDocks = GetDocks(targetGrid);
var shuttleDocks = GetDocks(shuttleUid);
return GetDockingConfigPrivate(shuttleUid, targetGrid, shuttleDocks, gridDocks, priorityTag);
}
private DockingConfig? GetDockingConfigPrivate(
EntityUid shuttleUid,
EntityUid targetGrid,
List<(EntityUid, DockingComponent)> shuttleDocks,
List<(EntityUid, DockingComponent)> gridDocks,
string? priorityTag = null)
{
if (gridDocks.Count <= 0)
return null;
var xformQuery = GetEntityQuery<TransformComponent>();
var targetGridGrid = Comp<MapGridComponent>(targetGrid);
var targetGridXform = xformQuery.GetComponent(targetGrid);
var targetGridAngle = _transform.GetWorldRotation(targetGridXform).Reduced();
var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
var validDockConfigs = new List<DockingConfig>();
if (shuttleDocks.Count > 0)
{
// We'll try all combinations of shuttle docks and see which one is most suitable
foreach (var (dockUid, shuttleDock) in shuttleDocks)
{
var shuttleDockXform = xformQuery.GetComponent(dockUid);
foreach (var (gridDockUid, gridDock) in gridDocks)
{
var gridXform = xformQuery.GetComponent(gridDockUid);
if (!CanDock(
shuttleDock, shuttleDockXform,
gridDock, gridXform,
targetGridAngle,
shuttleAABB,
targetGridGrid,
out var dockedAABB,
out var matty,
out var targetAngle))
{
continue;
}
// Can't just use the AABB as we want to get bounds as tight as possible.
var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager, _transform));
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
// Check if there's no intersecting grids (AKA oh god it's docking at cargo).
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
dockedBounds).Any(o => o.Owner != targetGrid))
{
continue;
}
// Alright well the spawn is valid now to check how many we can connect
// Get the matrix for each shuttle dock and test it against the grid docks to see
// if the connected position / direction matches.
var dockedPorts = new List<(EntityUid DockAUid, EntityUid DockBUid, DockingComponent DockA, DockingComponent DockB)>()
{
(dockUid, gridDockUid, shuttleDock, gridDock),
};
// TODO: Check shuttle orientation as the tiebreaker.
foreach (var (otherUid, other) in shuttleDocks)
{
if (other == shuttleDock)
continue;
foreach (var (otherGridUid, otherGrid) in gridDocks)
{
if (otherGrid == gridDock)
continue;
if (!CanDock(
other,
xformQuery.GetComponent(otherUid),
otherGrid,
xformQuery.GetComponent(otherGridUid),
targetGridAngle,
shuttleAABB, targetGridGrid,
out var otherDockedAABB,
out _,
out var otherTargetAngle) ||
!otherDockedAABB.Equals(dockedAABB) ||
!targetAngle.Equals(otherTargetAngle))
{
continue;
}
dockedPorts.Add((otherUid, otherGridUid, other, otherGrid));
}
}
validDockConfigs.Add(new DockingConfig()
{
Docks = dockedPorts,
Area = dockedAABB.Value,
Coordinates = spawnPosition,
Angle = targetAngle,
});
}
}
}
if (validDockConfigs.Count <= 0)
return null;
// Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
validDockConfigs = validDockConfigs
.OrderByDescending(x => x.Docks.Any(docks =>
TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
priority.Tag?.Equals(priorityTag) == true))
.ThenByDescending(x => x.Docks.Count)
.ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
var location = validDockConfigs.First();
location.TargetGrid = targetGrid;
// TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
return location;
}
/// <summary>
/// Checks whether the emergency shuttle can warp to the specified position.
/// </summary>
private bool ValidSpawn(MapGridComponent grid, Box2 area)
{
return !grid.GetLocalTilesIntersecting(area).Any();
}
public List<(EntityUid Uid, DockingComponent Component)> GetDocks(EntityUid uid)
{
var result = new List<(EntityUid Uid, DockingComponent Component)>();
var query = AllEntityQuery<DockingComponent, TransformComponent>();
while (query.MoveNext(out var dockUid, out var dock, out var xform))
{
if (xform.ParentUid != uid || !dock.Enabled)
continue;
result.Add((dockUid, dock));
}
return result;
}
}

View File

@@ -25,7 +25,7 @@ namespace Content.Server.Shuttles.Systems
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly SharedJointSystem _jointSystem = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _sawmill = default!;
private const string DockingFixture = "docking";

View File

@@ -1,14 +1,11 @@
using System.Threading;
using Content.Server.Access.Systems;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Components;
using Content.Server.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.GameTicking;
using Content.Shared.Popups;
using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Events;
@@ -16,23 +13,16 @@ using Content.Shared.Shuttles.Systems;
using Robust.Shared.Audio;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.Shuttles.Systems;
public sealed partial class ShuttleSystem
public sealed partial class EmergencyShuttleSystem
{
/*
* Handles the emergency shuttle's console and early launching.
*/
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IdCardSystem _idSystem = default!;
[Dependency] private readonly AccessReaderSystem _reader = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
/// <summary>
/// Has the emergency shuttle arrived?
/// </summary>
@@ -43,7 +33,7 @@ public sealed partial class ShuttleSystem
/// <summary>
/// How much time remaining until the shuttle consoles for emergency shuttles are unlocked?
/// </summary>
private float _consoleAccumulator;
private float _consoleAccumulator = float.MinValue;
/// <summary>
/// How long after the transit is over to end the round.
@@ -70,6 +60,8 @@ public sealed partial class ShuttleSystem
/// </summary>
private bool _launchedShuttles;
private bool _leftShuttles;
/// <summary>
/// Have we announced the launch?
/// </summary>
@@ -84,6 +76,8 @@ public sealed partial class ShuttleSystem
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealMessage>(OnEmergencyRepeal);
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, EmergencyShuttleRepealAllMessage>(OnEmergencyRepealAll);
SubscribeLocalEvent<EmergencyShuttleConsoleComponent, ActivatableUIOpenAttemptEvent>(OnEmergencyOpenAttempt);
SubscribeLocalEvent<EscapePodComponent, EntityUnpausedEvent>(OnEscapeUnpaused);
}
private void OnEmergencyOpenAttempt(EntityUid uid, EmergencyShuttleConsoleComponent component, ActivatableUIOpenAttemptEvent args)
@@ -119,7 +113,14 @@ public sealed partial class ShuttleSystem
private void UpdateEmergencyConsole(float frameTime)
{
if (_consoleAccumulator <= 0f) return;
// Add some buffer time so eshuttle always first.
var minTime = -(TransitTime - (ShuttleSystem.DefaultStartupTime + ShuttleSystem.DefaultTravelTime + 1f));
// TODO: I know this is shit but I already just cleaned up a billion things.
if (_consoleAccumulator < minTime)
{
return;
}
_consoleAccumulator -= frameTime;
@@ -131,46 +132,76 @@ public sealed partial class ShuttleSystem
}
// Imminent departure
if (!_launchedShuttles && _consoleAccumulator <= DefaultStartupTime)
if (!_launchedShuttles && _consoleAccumulator <= ShuttleSystem.DefaultStartupTime)
{
_launchedShuttles = true;
if (CentComMap != null)
{
foreach (var comp in EntityQuery<StationDataComponent>(true))
var dataQuery = AllEntityQuery<StationDataComponent>();
while (dataQuery.MoveNext(out var comp))
{
if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle)) continue;
if (!TryComp<ShuttleComponent>(comp.EmergencyShuttle, out var shuttle))
continue;
if (Deleted(CentCom))
{
// TODO: Need to get non-overlapping positions.
FTLTravel(shuttle,
_shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
new EntityCoordinates(
_mapManager.GetMapEntityId(CentComMap.Value),
Vector2.One * 1000f), _consoleAccumulator, TransitTime);
_random.NextVector2(1000f)), _consoleAccumulator, TransitTime);
}
else
{
FTLTravel(shuttle,
_shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle,
CentCom.Value, _consoleAccumulator, TransitTime, true);
}
}
var podQuery = AllEntityQuery<EscapePodComponent>();
var podLaunchOffset = 0.5f;
// Stagger launches coz funny
while (podQuery.MoveNext(out _, out var pod))
{
pod.LaunchTime = _timing.CurTime + TimeSpan.FromSeconds(podLaunchOffset);
podLaunchOffset += _random.NextFloat(0.5f, 2.5f);
}
}
}
// Departed
if (_consoleAccumulator <= 0f)
var podLaunchQuery = EntityQueryEnumerator<EscapePodComponent, ShuttleComponent>();
while (podLaunchQuery.MoveNext(out var uid, out var pod, out var shuttle))
{
_launchedShuttles = true;
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 (!_leftShuttles && _consoleAccumulator <= 0f)
{
_leftShuttles = true;
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}")));
_roundEndCancelToken = new CancellationTokenSource();
Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token);
}
// All the others.
if (_consoleAccumulator < minTime)
{
// Guarantees that emergency shuttle arrives first before anyone else can FTL.
if (CentCom != null)
AddFTLDestination(CentCom.Value, true);
_shuttle.AddFTLDestination(CentCom.Value, true);
}
}
@@ -185,7 +216,8 @@ public sealed partial class ShuttleSystem
return;
}
if (component.AuthorizedEntities.Count == 0) return;
if (component.AuthorizedEntities.Count == 0)
return;
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle early launch REPEAL ALL by {args.Session:user}");
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-console-auth-revoked", ("remaining", component.AuthorizationsRequired)));
@@ -246,8 +278,9 @@ public sealed partial class ShuttleSystem
{
_announced = false;
_roundEndCancelToken = null;
_leftShuttles = false;
_launchedShuttles = false;
_consoleAccumulator = 0f;
_consoleAccumulator = float.MinValue;
EarlyLaunchAuthorized = false;
EmergencyShuttleArrived = false;
}
@@ -294,7 +327,7 @@ public sealed partial class ShuttleSystem
if (EarlyLaunchAuthorized || !EmergencyShuttleArrived || _consoleAccumulator <= _authorizeTime) return false;
_logger.Add(LogType.EmergencyShuttle, LogImpact.Extreme, $"Emergency shuttle launch authorized");
_consoleAccumulator =_authorizeTime;
_consoleAccumulator = _authorizeTime;
EarlyLaunchAuthorized = true;
RaiseLocalEvent(new EmergencyShuttleAuthorizedEvent());
AnnounceLaunch();

View File

@@ -1,33 +1,30 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Access.Systems;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Communications;
using Content.Server.GameTicking.Events;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Shared.Access.Systems;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Shuttles.Events;
using Content.Shared.Tiles;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Server.Shuttles.Systems;
public sealed partial class ShuttleSystem
public sealed partial class EmergencyShuttleSystem : EntitySystem
{
/*
* Handles the escape shuttle + CentCom.
@@ -36,12 +33,23 @@ public sealed partial class ShuttleSystem
[Dependency] private readonly IAdminLogManager _logger = default!;
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AccessReaderSystem _reader = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!;
[Dependency] private readonly DockingSystem _dockSystem = default!;
[Dependency] private readonly DockingSystem _dock = default!;
[Dependency] private readonly IdCardSystem _idSystem = default!;
[Dependency] private readonly MapLoaderSystem _map = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
private ISawmill _sawmill = default!;
public MapId? CentComMap { get; private set; }
public EntityUid? CentCom { get; private set; }
@@ -55,19 +63,22 @@ public sealed partial class ShuttleSystem
private bool _emergencyShuttleEnabled;
private void InitializeEscape()
public override void Initialize()
{
_sawmill = Logger.GetSawmill("shuttle.emergency");
_emergencyShuttleEnabled = _configManager.GetCVar(CCVars.EmergencyShuttleEnabled);
// Don't immediately invoke as roundstart will just handle it.
_configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationStartup);
SubscribeNetworkEvent<EmergencyShuttleRequestPositionMessage>(OnShuttleRequestPosition);
InitializeEmergencyConsole();
}
private void SetEmergencyShuttleEnabled(bool value)
{
if (_emergencyShuttleEnabled == value) return;
if (_emergencyShuttleEnabled == value)
return;
_emergencyShuttleEnabled = value;
if (value)
@@ -80,9 +91,16 @@ public sealed partial class ShuttleSystem
}
}
private void ShutdownEscape()
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateEmergencyConsole(frameTime);
}
public override void Shutdown()
{
_configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled);
ShutdownEmergencyConsole();
}
/// <summary>
@@ -90,18 +108,25 @@ public sealed partial class ShuttleSystem
/// </summary>
private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args)
{
if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) return;
if (!_admin.IsAdmin((IPlayerSession) args.SenderSession))
return;
var player = args.SenderSession.AttachedEntity;
if (player == null ||
!TryComp<StationDataComponent>(_station.GetOwningStation(player.Value), out var stationData) ||
!TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
!HasComp<ShuttleComponent>(stationData.EmergencyShuttle))
{
return;
}
var targetGrid = _station.GetLargestGrid(stationData);
if (targetGrid == null) return;
var config = GetDockingConfig(shuttle, targetGrid.Value);
if (config == null) return;
if (targetGrid == null)
return;
var config = _dock.GetDockingConfig(stationData.EmergencyShuttle.Value, targetGrid.Value);
if (config == null)
return;
RaiseNetworkEvent(new EmergencyShuttlePositionMessage()
{
@@ -117,7 +142,10 @@ public sealed partial class ShuttleSystem
{
if (!TryComp<StationDataComponent>(stationUid, out var stationData) ||
!TryComp<TransformComponent>(stationData.EmergencyShuttle, out var xform) ||
!TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle)) return;
!TryComp<ShuttleComponent>(stationData.EmergencyShuttle, out var shuttle))
{
return;
}
var targetGrid = _station.GetLargestGrid(stationData);
@@ -127,105 +155,38 @@ public sealed partial class ShuttleSystem
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}");
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
// TODO: Need filter extensions or something don't blame me.
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
return;
}
var xformQuery = GetEntityQuery<TransformComponent>();
if (TryFTLDock(shuttle, targetGrid.Value))
if (_shuttle.TryFTLDock(stationData.EmergencyShuttle.Value, shuttle, targetGrid.Value))
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
var angle = GetAngle(xform, targetXform, xformQuery);
var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations");
// TODO: Need filter extensions or something don't blame me.
SoundSystem.Play("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast());
_audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true);
}
else
{
if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
{
var angle = GetAngle(xform, targetXform, xformQuery);
var angle = _dock.GetAngle(stationData.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
_chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false);
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}");
// TODO: Need filter extensions or something don't blame me.
SoundSystem.Play("/Audio/Misc/notice1.ogg", Filter.Broadcast());
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
}
}
private Angle GetAngle(TransformComponent xform, TransformComponent targetXform, EntityQuery<TransformComponent> xformQuery)
{
var (shuttlePos, shuttleRot) = xform.GetWorldPositionRotation(xformQuery);
var (targetPos, targetRot) = targetXform.GetWorldPositionRotation(xformQuery);
var shuttleCOM = Robust.Shared.Physics.Transform.Mul(new Transform(shuttlePos, shuttleRot),
Comp<PhysicsComponent>(xform.Owner).LocalCenter);
var targetCOM = Robust.Shared.Physics.Transform.Mul(new Transform(targetPos, targetRot),
Comp<PhysicsComponent>(targetXform.Owner).LocalCenter);
var mapDiff = shuttleCOM - targetCOM;
var targetRotation = targetRot;
var angle = mapDiff.ToWorldAngle();
angle -= targetRotation;
return angle;
}
/// <summary>
/// Checks if 2 docks can be connected by moving the shuttle directly onto docks.
/// </summary>
private bool CanDock(
DockingComponent shuttleDock,
TransformComponent shuttleDockXform,
DockingComponent gridDock,
TransformComponent gridDockXform,
Angle targetGridRotation,
Box2 shuttleAABB,
EntityUid gridUid,
MapGridComponent grid,
[NotNullWhen(true)] out Box2? shuttleDockedAABB,
out Matrix3 matty,
out Angle gridRotation)
{
gridRotation = Angle.Zero;
matty = Matrix3.Identity;
shuttleDockedAABB = null;
if (shuttleDock.Docked ||
gridDock.Docked ||
!shuttleDockXform.Anchored ||
!gridDockXform.Anchored)
{
return false;
}
// First, get the station dock's position relative to the shuttle, this is where we rotate it around
var stationDockPos = shuttleDockXform.LocalPosition +
shuttleDockXform.LocalRotation.RotateVec(new Vector2(0f, -1f));
// Need to invert the grid's angle.
var shuttleDockAngle = shuttleDockXform.LocalRotation;
var gridDockAngle = gridDockXform.LocalRotation.Opposite();
var stationDockMatrix = Matrix3.CreateInverseTransform(stationDockPos, shuttleDockAngle);
var gridXformMatrix = Matrix3.CreateTransform(gridDockXform.LocalPosition, gridDockAngle);
Matrix3.Multiply(in stationDockMatrix, in gridXformMatrix, out matty);
shuttleDockedAABB = matty.TransformBox(shuttleAABB);
// Rounding moment
shuttleDockedAABB = shuttleDockedAABB.Value.Enlarged(-0.01f);
if (!ValidSpawn(gridUid, grid, shuttleDockedAABB.Value))
return false;
gridRotation = targetGridRotation + gridDockAngle - shuttleDockAngle;
return true;
}
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentStartup args)
{
AddEmergencyShuttle(component);
@@ -233,6 +194,7 @@ public sealed partial class ShuttleSystem
private void OnRoundStart(RoundStartingEvent ev)
{
CleanupEmergencyConsole();
SetupEmergencyShuttle();
}
@@ -241,7 +203,8 @@ public sealed partial class ShuttleSystem
/// </summary>
public void CallEmergencyShuttle()
{
if (EmergencyShuttleArrived) return;
if (EmergencyShuttleArrived)
return;
if (!_emergencyShuttleEnabled)
{
@@ -255,28 +218,16 @@ public sealed partial class ShuttleSystem
if (CentComMap != null)
_mapManager.SetMapPaused(CentComMap.Value, false);
foreach (var comp in EntityQuery<StationDataComponent>(true))
var query = AllEntityQuery<StationDataComponent>();
while (query.MoveNext(out var uid, out var comp))
{
CallEmergencyShuttle(comp.Owner);
CallEmergencyShuttle(uid);
}
_commsConsole.UpdateCommsConsoleInterface();
}
public List<DockingComponent> GetDocks(EntityUid uid)
{
var result = new List<DockingComponent>();
foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
{
if (xform.ParentUid != uid || !dock.Enabled) continue;
result.Add(dock);
}
return result;
}
private void SetupEmergencyShuttle()
{
if (!_emergencyShuttleEnabled || CentComMap != null && _mapManager.MapExists(CentComMap.Value)) return;
@@ -293,7 +244,7 @@ public sealed partial class ShuttleSystem
CentCom = centcomm;
if (CentCom != null)
AddFTLDestination(CentCom.Value, false);
_shuttle.AddFTLDestination(CentCom.Value, false);
}
else
{
@@ -332,7 +283,6 @@ public sealed partial class ShuttleSystem
_shuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer;
component.EmergencyShuttle = shuttle;
EnsureComp<ProtectedGridComponent>(shuttle.Value);
}
private void CleanupEmergencyShuttle()
@@ -354,27 +304,11 @@ public sealed partial class ShuttleSystem
_mapManager.DeleteMap(CentComMap.Value);
}
/// <summary>
/// Stores the data for a valid docking configuration for the emergency shuttle
/// </summary>
private sealed class DockingConfig
private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args)
{
/// <summary>
/// The pairs of docks that can connect.
/// </summary>
public List<(DockingComponent DockA, DockingComponent DockB)> Docks = new();
if (component.LaunchTime == null)
return;
/// <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;
component.LaunchTime = component.LaunchTime.Value + args.PausedTime;
}
}

View File

@@ -12,16 +12,17 @@ using Content.Shared.Shuttles.Events;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.Collections;
using Robust.Shared.GameStates;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.Shuttles.Systems
namespace Content.Server.Shuttles.Systems;
public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
{
public sealed class ShuttleConsoleSystem : SharedShuttleConsoleSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ActionBlockerSystem _blocker = default!;
[Dependency] private readonly AlertsSystem _alertsSystem = default!;
@@ -93,7 +94,9 @@ namespace Content.Server.Shuttles.Systems
return;
}
if (HasComp<FTLComponent>(xform.GridUid))
var shuttleUid = xform.GridUid.Value;
if (HasComp<FTLComponent>(shuttleUid))
{
_popup.PopupCursor(Loc.GetString("shuttle-console-in-ftl"), args.Session);
return;
@@ -109,7 +112,7 @@ namespace Content.Server.Shuttles.Systems
var tagEv = new FTLTagEvent();
RaiseLocalEvent(xform.GridUid.Value, ref tagEv);
_shuttle.FTLTravel(shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
_shuttle.FTLTravel(xform.GridUid.Value, shuttle, args.Destination, dock: dock, priorityTag: tagEv.Tag);
}
private void OnDock(DockEvent ev)
@@ -138,7 +141,7 @@ namespace Content.Server.Shuttles.Systems
while (query.MoveNext(out var uid, out var comp))
{
UpdateState(uid, comp, docks);
UpdateState(uid, docks);
}
}
@@ -170,12 +173,12 @@ namespace Content.Server.Shuttles.Systems
private void OnConsoleAnchorChange(EntityUid uid, ShuttleConsoleComponent component, ref AnchorStateChangedEvent args)
{
UpdateState(uid, component);
UpdateState(uid);
}
private void OnConsolePowerChange(EntityUid uid, ShuttleConsoleComponent component, ref PowerChangedEvent args)
{
UpdateState(uid, component);
UpdateState(uid);
}
private bool TryPilot(EntityUid user, EntityUid uid)
@@ -189,7 +192,7 @@ namespace Content.Server.Shuttles.Systems
return false;
}
var pilotComponent = EntityManager.EnsureComponent<PilotComponent>(user);
var pilotComponent = EnsureComp<PilotComponent>(user);
var console = pilotComponent.Console;
if (console != null)
@@ -240,7 +243,7 @@ namespace Content.Server.Shuttles.Systems
return result;
}
private void UpdateState(EntityUid consoleUid, ShuttleConsoleComponent component, List<DockingInterfaceState>? docks = null)
private void UpdateState(EntityUid consoleUid, List<DockingInterfaceState>? docks = null)
{
EntityUid? entity = consoleUid;
@@ -299,7 +302,7 @@ namespace Content.Server.Shuttles.Systems
var canTravel = !locked &&
comp.Enabled &&
(!TryComp<FTLComponent>(comp.Owner, out var ftl) || ftl.State == FTLState.Cooldown);
(!TryComp<FTLComponent>(destUid, out var ftl) || ftl.State == FTLState.Cooldown);
// Can't travel to same map (yet)
if (canTravel && consoleXform?.MapUid == Transform(destUid).MapUid)
@@ -328,7 +331,7 @@ namespace Content.Server.Shuttles.Systems
{
base.Update(frameTime);
var toRemove = new RemQueue<PilotComponent>();
var toRemove = new ValueList<(EntityUid, PilotComponent)>();
var query = EntityQueryEnumerator<PilotComponent>();
while (query.MoveNext(out var uid, out var comp))
@@ -338,13 +341,13 @@ namespace Content.Server.Shuttles.Systems
if (!_blocker.CanInteract(uid, comp.Console.Owner))
{
toRemove.Add(comp);
toRemove.Add((uid, comp));
}
}
foreach (var comp in toRemove)
foreach (var (uid, comp) in toRemove)
{
RemovePilot(comp.Owner, comp);
RemovePilot(uid, comp);
}
}
@@ -418,11 +421,12 @@ namespace Content.Server.Shuttles.Systems
eye.Zoom = new(1.0f, 1.0f);
}
if (!helmsman.SubscribedPilots.Remove(pilotComponent)) return;
if (!helmsman.SubscribedPilots.Remove(pilotComponent))
return;
_alertsSystem.ClearAlert(pilotUid, AlertType.PilotingShuttle);
pilotComponent.Owner.PopupMessage(Loc.GetString("shuttle-pilot-end"));
_popup.PopupEntity(Loc.GetString("shuttle-pilot-end"), pilotUid, pilotUid);
if (pilotComponent.LifeStage < ComponentLifeStage.Stopping)
EntityManager.RemoveComponent<PilotComponent>(pilotUid);
@@ -430,7 +434,8 @@ namespace Content.Server.Shuttles.Systems
public void RemovePilot(EntityUid entity)
{
if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent)) return;
if (!EntityManager.TryGetComponent(entity, out PilotComponent? pilotComponent))
return;
RemovePilot(entity, pilotComponent);
}
@@ -442,5 +447,4 @@ namespace Content.Server.Shuttles.Systems
RemovePilot(pilot.Owner, pilot);
}
}
}
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Server.Stunnable;
using Content.Shared.Parallax;
using Content.Shared.Shuttles.Systems;
using Content.Shared.StatusEffect;
@@ -29,18 +28,11 @@ public sealed partial class ShuttleSystem
* This is a way to move a shuttle from one location to another, via an intermediate map for fanciness.
*/
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly DoorSystem _doors = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly StunSystem _stuns = default!;
[Dependency] private readonly ThrusterSystem _thruster = default!;
private MapId? _hyperSpaceMap;
private const float DefaultStartupTime = 5.5f;
private const float DefaultTravelTime = 30f;
private const float DefaultArrivalTime = 5f;
public const float DefaultStartupTime = 5.5f;
public const float DefaultTravelTime = 30f;
public const float DefaultArrivalTime = 5f;
private const float FTLCooldown = 30f;
private const float ShuttleFTLRange = 100f;
@@ -106,14 +98,17 @@ public sealed partial class ShuttleSystem
return true;
}
var bounds = xform.WorldMatrix.TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
var bounds = _transform.GetWorldMatrix(xform).TransformBox(grid.LocalAABB).Enlarged(ShuttleFTLRange);
var bodyQuery = GetEntityQuery<PhysicsComponent>();
foreach (var other in _mapManager.FindGridsIntersecting(xform.MapID, bounds))
{
if (grid.Owner == other.Owner ||
if (uid == other.Owner ||
!bodyQuery.TryGetComponent(other.Owner, out var body) ||
body.Mass < ShuttleFTLMassThreshold) continue;
body.Mass < ShuttleFTLMassThreshold)
{
continue;
}
reason = Loc.GetString("shuttle-console-proximity");
return false;
@@ -127,7 +122,8 @@ public sealed partial class ShuttleSystem
/// </summary>
public FTLDestinationComponent AddFTLDestination(EntityUid uid, bool enabled)
{
if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled) return destination;
if (TryComp<FTLDestinationComponent>(uid, out var destination) && destination.Enabled == enabled)
return destination;
destination = EnsureComp<FTLDestinationComponent>(uid);
@@ -146,19 +142,22 @@ public sealed partial class ShuttleSystem
{
if (!RemComp<FTLDestinationComponent>(uid))
return;
_console.RefreshShuttleConsoles();
}
/// <summary>
/// Moves a shuttle from its current position to the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
public void FTLTravel(ShuttleComponent component,
public void FTLTravel(
EntityUid shuttleUid,
ShuttleComponent component,
EntityCoordinates coordinates,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime,
string? priorityTag = null)
{
if (!TrySetupFTL(component, out var hyperspace))
if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
hyperspace.StartupTime = startupTime;
@@ -173,14 +172,16 @@ public sealed partial class ShuttleSystem
/// <summary>
/// Moves a shuttle from its current position to docked on the target one. Goes through the hyperspace map while the timer is running.
/// </summary>
public void FTLTravel(ShuttleComponent component,
public void FTLTravel(
EntityUid shuttleUid,
ShuttleComponent component,
EntityUid target,
float startupTime = DefaultStartupTime,
float hyperspaceTime = DefaultTravelTime,
bool dock = false,
string? priorityTag = null)
{
if (!TrySetupFTL(component, out var hyperspace))
if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
hyperspace.StartupTime = startupTime;
@@ -192,9 +193,8 @@ public sealed partial class ShuttleSystem
_console.RefreshShuttleConsoles();
}
private bool TrySetupFTL(ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
private bool TrySetupFTL(EntityUid uid, ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
{
var uid = shuttle.Owner;
component = null;
if (HasComp<FTLComponent>(uid))
@@ -310,9 +310,9 @@ public sealed partial class ShuttleSystem
if (comp.TargetUid != null && shuttle != null)
{
if (comp.Dock)
TryFTLDock(shuttle, comp.TargetUid.Value, comp.PriorityTag);
TryFTLDock(uid, shuttle, comp.TargetUid.Value, comp.PriorityTag);
else
TryFTLProximity(shuttle, comp.TargetUid.Value);
TryFTLProximity(uid, shuttle, comp.TargetUid.Value);
mapId = Transform(comp.TargetUid.Value).MapID;
}
@@ -350,7 +350,7 @@ public sealed partial class ShuttleSystem
comp.TravelStream = null;
}
SoundSystem.Play(_arrivalSound.GetSound(), Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), _arrivalSound.Params);
_audio.PlayGlobal(_arrivalSound, Filter.Empty().AddInRange(Transform(uid).MapPosition, GetSoundRange(uid)), true);
if (TryComp<FTLDestinationComponent>(uid, out var dest))
{
@@ -380,7 +380,9 @@ public sealed partial class ShuttleSystem
{
foreach (var (dock, xform) in EntityQuery<DockingComponent, TransformComponent>(true))
{
if (xform.ParentUid != uid || dock.Enabled == enabled) continue;
if (xform.ParentUid != uid || dock.Enabled == enabled)
continue;
_dockSystem.Undock(dock);
dock.Enabled = enabled;
}
@@ -388,25 +390,30 @@ public sealed partial class ShuttleSystem
private void SetDockBolts(EntityUid uid, bool enabled)
{
foreach (var (_, door, xform) in EntityQuery<DockingComponent, AirlockComponent, TransformComponent>(true))
{
if (xform.ParentUid != uid) continue;
var query = AllEntityQuery<DockingComponent, AirlockComponent, TransformComponent>();
_doors.TryClose(door.Owner);
_airlock.SetBoltsWithAudio(door.Owner, door, enabled);
while (query.MoveNext(out var doorUid, out _, out var door, out var xform))
{
if (xform.ParentUid != uid)
continue;
_doors.TryClose(doorUid);
_airlock.SetBoltsWithAudio(doorUid, door, enabled);
}
}
private float GetSoundRange(EntityUid uid)
{
if (!_mapManager.TryGetGrid(uid, out var grid)) return 4f;
if (!_mapManager.TryGetGrid(uid, out var grid))
return 4f;
return MathF.Max(grid.LocalAABB.Width, grid.LocalAABB.Height) + 12.5f;
}
private void SetupHyperspace()
{
if (_hyperSpaceMap != null) return;
if (_hyperSpaceMap != null)
return;
_hyperSpaceMap = _mapManager.CreateMap();
_sawmill.Info($"Setup hyperspace map at {_hyperSpaceMap.Value}");
@@ -453,7 +460,8 @@ public sealed partial class ShuttleSystem
while (childEnumerator.MoveNext(out var child))
{
if (!buckleQuery.TryGetComponent(child.Value, out var buckle) || buckle.Buckled) continue;
if (!buckleQuery.TryGetComponent(child.Value, out var buckle) || buckle.Buckled)
continue;
toKnock.Add(child.Value);
}
@@ -462,10 +470,9 @@ public sealed partial class ShuttleSystem
/// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity.
/// </summary>
/// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
public bool TryFTLDock(ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
public bool TryFTLDock(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, string? priorityTag = null)
{
if (!TryComp<TransformComponent>(component.Owner, out var xform) ||
if (!TryComp<TransformComponent>(shuttleUid, out var shuttleXform) ||
!TryComp<TransformComponent>(targetUid, out var targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid())
@@ -473,42 +480,49 @@ public sealed partial class ShuttleSystem
return false;
}
var config = GetDockingConfig(component, targetUid, priorityTag);
var config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag);
if (config != null)
{
// Set position
xform.Coordinates = config.Coordinates;
xform.WorldRotation = config.Angle;
// Connect everything
foreach (var (dockA, dockB) in config.Docks)
{
_dockSystem.Dock(dockA.Owner, dockA, dockB.Owner, dockB);
}
FTLDock(config, shuttleXform);
return true;
}
TryFTLProximity(component, targetUid, xform, targetXform);
TryFTLProximity(shuttleUid, component, targetUid, shuttleXform, targetXform);
return false;
}
/// <summary>
/// Forces an FTL dock.
/// </summary>
public void FTLDock(DockingConfig config, TransformComponent shuttleXform)
{
// Set position
shuttleXform.Coordinates = config.Coordinates;
_transform.SetWorldRotation(shuttleXform, config.Angle);
// Connect everything
foreach (var (dockAUid, dockBUid, dockA, dockB) in config.Docks)
{
_dockSystem.Dock(dockAUid, dockA, dockBUid, dockB);
}
}
/// <summary>
/// Tries to arrive nearby without overlapping with other grids.
/// </summary>
public bool TryFTLProximity(ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
public bool TryFTLProximity(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
{
if (!Resolve(targetUid, ref targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid() ||
!Resolve(component.Owner, ref xform))
!Resolve(shuttleUid, ref xform))
{
return false;
}
var xformQuery = GetEntityQuery<TransformComponent>();
var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
Box2 targetLocalAABB;
// Spawn nearby.
@@ -534,7 +548,8 @@ public sealed partial class ShuttleSystem
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, targetAABB))
{
if (!nearbyGrids.Add(grid.Owner)) continue;
if (!nearbyGrids.Add(grid.Owner))
continue;
targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
.TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
@@ -557,7 +572,8 @@ public sealed partial class ShuttleSystem
foreach (var grid in _mapManager.GetAllGrids())
{
// Don't add anymore as it is irrelevant, but that doesn't mean we need to re-do existing work.
if (nearbyGrids.Contains(grid.Owner)) continue;
if (nearbyGrids.Contains(grid.Owner))
continue;
targetAABB = targetAABB.Union(_transform.GetWorldMatrix(grid.Owner, xformQuery)
.TransformBox(Comp<MapGridComponent>(grid.Owner).LocalAABB));
@@ -568,10 +584,10 @@ public sealed partial class ShuttleSystem
Vector2 spawnPos;
if (TryComp<PhysicsComponent>(component.Owner, out var shuttleBody))
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttleBody))
{
_physics.SetLinearVelocity(component.Owner, Vector2.Zero, body: shuttleBody);
_physics.SetAngularVelocity(component.Owner, 0f, body: shuttleBody);
_physics.SetLinearVelocity(shuttleUid, Vector2.Zero, body: shuttleBody);
_physics.SetAngularVelocity(shuttleUid, 0f, body: shuttleBody);
}
// TODO: This is pretty crude for multiple landings.
@@ -604,138 +620,4 @@ public sealed partial class ShuttleSystem
return true;
}
/// <summary>
/// Checks whether the emergency shuttle can warp to the specified position.
/// </summary>
private bool ValidSpawn(EntityUid gridUid, MapGridComponent grid, Box2 area)
{
// If the target is a map then any tile is valid.
// TODO: We already need the entities-under check
if (HasComp<MapComponent>(gridUid))
return true;
return !grid.GetLocalTilesIntersecting(area).Any();
}
/// <summary>
/// Tries to get a valid docking configuration for the shuttle to the target grid.
/// </summary>
/// <param name="priorityTag">Priority docking tag to prefer, e.g. for emergency shuttle</param>
private DockingConfig? GetDockingConfig(ShuttleComponent component, EntityUid targetGrid, string? priorityTag = null)
{
var gridDocks = GetDocks(targetGrid);
if (gridDocks.Count <= 0)
return null;
var xformQuery = GetEntityQuery<TransformComponent>();
var targetGridGrid = Comp<MapGridComponent>(targetGrid);
var targetGridXform = xformQuery.GetComponent(targetGrid);
var targetGridAngle = targetGridXform.WorldRotation.Reduced();
var shuttleDocks = GetDocks(component.Owner);
var shuttleAABB = Comp<MapGridComponent>(component.Owner).LocalAABB;
var validDockConfigs = new List<DockingConfig>();
if (shuttleDocks.Count > 0)
{
// We'll try all combinations of shuttle docks and see which one is most suitable
foreach (var shuttleDock in shuttleDocks)
{
var shuttleDockXform = xformQuery.GetComponent(shuttleDock.Owner);
foreach (var gridDock in gridDocks)
{
var gridXform = xformQuery.GetComponent(gridDock.Owner);
if (!CanDock(
shuttleDock, shuttleDockXform,
gridDock, gridXform,
targetGridAngle,
shuttleAABB,
targetGrid,
targetGridGrid,
out var dockedAABB,
out var matty,
out var targetAngle)) continue;
// Can't just use the AABB as we want to get bounds as tight as possible.
var spawnPosition = new EntityCoordinates(targetGrid, matty.Transform(Vector2.Zero));
spawnPosition = new EntityCoordinates(targetGridXform.MapUid!.Value, spawnPosition.ToMapPos(EntityManager));
var dockedBounds = new Box2Rotated(shuttleAABB.Translated(spawnPosition.Position), targetGridAngle, spawnPosition.Position);
// Check if there's no intersecting grids (AKA oh god it's docking at cargo).
if (_mapManager.FindGridsIntersecting(targetGridXform.MapID,
dockedBounds).Any(o => o.Owner != targetGrid))
{
continue;
}
// Alright well the spawn is valid now to check how many we can connect
// Get the matrix for each shuttle dock and test it against the grid docks to see
// if the connected position / direction matches.
var dockedPorts = new List<(DockingComponent DockA, DockingComponent DockB)>()
{
(shuttleDock, gridDock),
};
foreach (var other in shuttleDocks)
{
if (other == shuttleDock) continue;
foreach (var otherGrid in gridDocks)
{
if (otherGrid == gridDock) continue;
if (!CanDock(
other,
xformQuery.GetComponent(other.Owner),
otherGrid,
xformQuery.GetComponent(otherGrid.Owner),
targetGridAngle,
shuttleAABB,
targetGrid,
targetGridGrid,
out var otherDockedAABB,
out _,
out var otherTargetAngle) ||
!otherDockedAABB.Equals(dockedAABB) ||
!targetAngle.Equals(otherTargetAngle)) continue;
dockedPorts.Add((other, otherGrid));
}
}
validDockConfigs.Add(new DockingConfig()
{
Docks = dockedPorts,
Area = dockedAABB.Value,
Coordinates = spawnPosition,
Angle = targetAngle,
});
}
}
}
if (validDockConfigs.Count <= 0)
return null;
// Prioritise by priority docks, then by maximum connected ports, then by most similar angle.
validDockConfigs = validDockConfigs
.OrderByDescending(x => x.Docks.Any(docks =>
TryComp<PriorityDockComponent>(docks.DockB.Owner, out var priority) &&
priority.Tag?.Equals(priorityTag) == true))
.ThenByDescending(x => x.Docks.Count)
.ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList();
var location = validDockConfigs.First();
location.TargetGrid = targetGrid;
// TODO: Ideally do a hyperspace warpin, just have it run on like a 10 second timer.
return location;
}
}

View File

@@ -0,0 +1,69 @@
using Content.Server.Shuttles.Components;
namespace Content.Server.Shuttles.Systems;
public sealed partial class ShuttleSystem
{
private void InitializeGridFills()
{
SubscribeLocalEvent<GridFillComponent, MapInitEvent>(OnGridFillMapInit);
}
private void OnGridFillMapInit(EntityUid uid, GridFillComponent component, MapInitEvent args)
{
if (!TryComp<DockingComponent>(uid, out var dock) ||
!TryComp<TransformComponent>(uid, out var xform) ||
xform.GridUid == null)
{
return;
}
// Spawn on a dummy map and try to dock if possible, otherwise dump it.
var mapId = _mapManager.CreateMap();
var valid = false;
if (_loader.TryLoad(mapId, component.Path.ToString(), out var ent) &&
ent.Count == 1 &&
TryComp<TransformComponent>(ent[0], out var shuttleXform))
{
var escape = GetSingleDock(ent[0]);
if (escape != null)
{
var config = _dockSystem.GetDockingConfig(ent[0], xform.GridUid.Value, escape.Value.Entity, escape.Value.Component, uid, dock);
if (config != null)
{
FTLDock(config, shuttleXform);
valid = true;
}
}
}
if (!valid)
{
_sawmill.Error($"Error loading gridfill dock for {ToPrettyString(uid)} / {component.Path}");
}
_mapManager.DeleteMap(mapId);
}
private (EntityUid Entity, DockingComponent Component)? GetSingleDock(EntityUid uid)
{
var dockQuery = GetEntityQuery<DockingComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var rator = xform.ChildEnumerator;
while (rator.MoveNext(out var child))
{
if (!dockQuery.TryGetComponent(child, out var dock))
continue;
return (child.Value, dock);
}
return null;
}
}

View File

@@ -1,15 +1,16 @@
using Content.Server.Doors.Systems;
using Content.Server.Shuttles.Components;
using Content.Shared.CCVar;
using Content.Server.Stunnable;
using Content.Shared.GameTicking;
using Content.Shared.Shuttles.Systems;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Random;
namespace Content.Server.Shuttles.Systems
{
@@ -17,9 +18,18 @@ namespace Content.Server.Shuttles.Systems
public sealed partial class ShuttleSystem : SharedShuttleSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly AirlockSystem _airlock = default!;
[Dependency] private readonly DockingSystem _dockSystem = default!;
[Dependency] private readonly DoorSystem _doors = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly MapLoaderSystem _loader = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly ShuttleConsoleSystem _console = default!;
[Dependency] private readonly StunSystem _stuns = default!;
[Dependency] private readonly ThrusterSystem _thruster = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
private ISawmill _sawmill = default!;
@@ -34,9 +44,8 @@ namespace Content.Server.Shuttles.Systems
base.Initialize();
_sawmill = Logger.GetSawmill("shuttles");
InitializeEmergencyConsole();
InitializeEscape();
InitializeFTL();
InitializeGridFills();
InitializeIFF();
InitializeImpact();
@@ -53,24 +62,14 @@ namespace Content.Server.Shuttles.Systems
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateEmergencyConsole(frameTime);
UpdateHyperspace(frameTime);
}
private void OnRoundRestart(RoundRestartCleanupEvent ev)
{
CleanupEmergencyConsole();
CleanupEmergencyShuttle();
CleanupHyperspace();
}
public override void Shutdown()
{
base.Shutdown();
ShutdownEscape();
ShutdownEmergencyConsole();
}
private void OnShuttleAdd(EntityUid uid, ShuttleComponent component, ComponentAdd args)
{
// Easier than doing it in the comp and they don't have constructors.
@@ -83,7 +82,8 @@ namespace Content.Server.Shuttles.Systems
private void OnGridFixtureChange(GridFixtureChangeEvent args)
{
// Look this is jank but it's a placeholder until we design it.
if (args.NewFixtures.Count == 0) return;
if (args.NewFixtures.Count == 0)
return;
var uid = args.NewFixtures[0].Body.Owner;
var manager = Comp<FixturesComponent>(uid);
@@ -107,12 +107,12 @@ namespace Content.Server.Shuttles.Systems
private void OnShuttleStartup(EntityUid uid, ShuttleComponent component, ComponentStartup args)
{
if (!EntityManager.HasComponent<MapGridComponent>(component.Owner))
if (!EntityManager.HasComponent<MapGridComponent>(uid))
{
return;
}
if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
{
return;
}
@@ -125,7 +125,8 @@ namespace Content.Server.Shuttles.Systems
public void Toggle(EntityUid uid, ShuttleComponent component)
{
if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent)) return;
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
return;
component.Enabled = !component.Enabled;
@@ -164,7 +165,7 @@ namespace Content.Server.Shuttles.Systems
// None of the below is necessary for any cleanup if we're just deleting.
if (EntityManager.GetComponent<MetaDataComponent>(uid).EntityLifeStage >= EntityLifeStage.Terminating) return;
if (!EntityManager.TryGetComponent(component.Owner, out PhysicsComponent? physicsComponent))
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? physicsComponent))
{
return;
}

View File

@@ -28,6 +28,6 @@ public sealed class StationDataComponent : Component
/// <summary>
/// The emergency shuttle assigned to this station.
/// </summary>
[ViewVariables, Access(typeof(ShuttleSystem), Friend = AccessPermissions.ReadWrite)]
[ViewVariables, Access(typeof(ShuttleSystem), typeof(EmergencyShuttleSystem), Friend = AccessPermissions.ReadWrite)]
public EntityUid? EmergencyShuttle;
}

View File

@@ -68,12 +68,12 @@ namespace Content.Shared.Popups
public abstract void PopupEntity(string message, EntityUid uid, PopupType type=PopupType.Small);
/// <summary>
/// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
/// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
/// </summary>
public abstract void PopupEntity(string message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small);
/// <summary>
/// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shoes the popup only to some specific client.
/// Variant of <see cref="PopupEntity(string, EntityUid, PopupType)"/> that shows the popup only to some specific client.
/// </summary>
public abstract void PopupEntity(string message, EntityUid uid, ICommonSession recipient, PopupType type = PopupType.Small);

View File

@@ -0,0 +1,406 @@
meta:
format: 3
name: DemoStation
author: Space-Wizards
postmapinit: false
tilemap:
0: Space
1: FloorArcadeBlue
2: FloorArcadeBlue2
3: FloorArcadeRed
4: FloorAsteroidCoarseSand0
5: FloorAsteroidCoarseSandDug
6: FloorAsteroidIronsand1
7: FloorAsteroidIronsand2
8: FloorAsteroidIronsand3
9: FloorAsteroidIronsand4
10: FloorAsteroidSand
11: FloorAsteroidTile
12: FloorBar
13: FloorBasalt
14: FloorBlue
15: FloorBlueCircuit
16: FloorBoxing
17: FloorCarpetClown
18: FloorCarpetOffice
19: FloorCave
20: FloorCaveDrought
21: FloorClown
22: FloorDark
23: FloorDarkDiagonal
24: FloorDarkDiagonalMini
25: FloorDarkHerringbone
26: FloorDarkMini
27: FloorDarkMono
28: FloorDarkOffset
29: FloorDarkPavement
30: FloorDarkPavementVertical
31: FloorDarkPlastic
32: FloorDesert
33: FloorDirt
34: FloorEighties
35: FloorElevatorShaft
36: FloorFlesh
37: FloorFreezer
38: FloorGlass
39: FloorGold
40: FloorGrass
41: FloorGrassDark
42: FloorGrassJungle
43: FloorGrassLight
44: FloorGreenCircuit
45: FloorGym
46: FloorHydro
47: FloorKitchen
48: FloorLaundry
49: FloorLino
50: FloorLowDesert
51: FloorMetalDiamond
52: FloorMime
53: FloorMono
54: FloorPlanetDirt
55: FloorPlanetGrass
56: FloorPlastic
57: FloorRGlass
58: FloorReinforced
59: FloorRockVault
60: FloorShowroom
61: FloorShuttleBlue
62: FloorShuttleOrange
63: FloorShuttlePurple
64: FloorShuttleRed
65: FloorShuttleWhite
66: FloorSilver
67: FloorSnow
68: FloorSteel
69: FloorSteelDiagonal
70: FloorSteelDiagonalMini
71: FloorSteelDirty
72: FloorSteelHerringbone
73: FloorSteelMini
74: FloorSteelMono
75: FloorSteelOffset
76: FloorSteelPavement
77: FloorSteelPavementVertical
78: FloorTechMaint
79: FloorTechMaint2
80: FloorTechMaint3
81: FloorWhite
82: FloorWhiteDiagonal
83: FloorWhiteDiagonalMini
84: FloorWhiteHerringbone
85: FloorWhiteMini
86: FloorWhiteMono
87: FloorWhiteOffset
88: FloorWhitePavement
89: FloorWhitePavementVertical
90: FloorWhitePlastic
91: FloorWood
92: FloorWoodTile
93: Lattice
94: Plating
entities:
- uid: 0
components:
- type: MetaData
- type: EscapePod
- parent: invalid
type: Transform
- chunks:
-1,-1:
ind: -1,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4AAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAPQAAAA==
0,0:
ind: 0,0
tiles: XgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
-1,0:
ind: -1,0
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAPQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXQAAAF4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
0,-1:
ind: 0,-1
tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
type: MapGrid
- type: Broadphase
- angularDamping: 0.05
linearDamping: 0.05
fixedRotation: False
bodyType: Dynamic
type: Physics
- fixtures: []
type: Fixtures
- id: Empty
type: BecomesStation
- type: OccluderTree
- type: Shuttle
- nextUpdate: 88.8784752
type: GridPathfinding
- gravityShakeSound: !type:SoundPathSpecifier
path: /Audio/Effects/alert.ogg
type: Gravity
- chunkCollection:
version: 2
nodes: []
type: DecalGrid
- version: 2
data:
tiles:
-1,-1:
0: 52224
0,0:
0: 1
-1,0:
0: 12
0,-1:
0: 4352
uniqueMixes:
- volume: 2500
temperature: 293.15
moles:
- 21.824879
- 82.10312
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
chunkSize: 4
type: GridAtmosphere
- type: GasTileOverlay
- type: RadiationGridResistance
- uid: 1
type: WallShuttle
components:
- pos: -1.5,-0.5
parent: 0
type: Transform
- uid: 2
type: WallShuttle
components:
- pos: -1.5,0.5
parent: 0
type: Transform
- uid: 3
type: WallShuttle
components:
- pos: 0.5,-0.5
parent: 0
type: Transform
- uid: 4
type: WallShuttle
components:
- pos: 0.5,0.5
parent: 0
type: Transform
- uid: 5
type: WallShuttleDiagonal
components:
- pos: -1.5,1.5
parent: 0
type: Transform
- uid: 6
type: WallShuttleDiagonal
components:
- rot: -1.5707963267948966 rad
pos: 0.5,1.5
parent: 0
type: Transform
- uid: 7
type: ShuttleWindow
components:
- pos: -0.5,1.5
parent: 0
type: Transform
- uid: 8
type: Grille
components:
- pos: -0.5,1.5
parent: 0
type: Transform
- uid: 9
type: AtmosDeviceFanTiny
components:
- pos: -0.5,-1.5
parent: 0
type: Transform
- uid: 10
type: Thruster
components:
- rot: 3.141592653589793 rad
pos: -1.5,-1.5
parent: 0
type: Transform
- bodyType: Static
type: Physics
- uid: 11
type: Thruster
components:
- rot: 3.141592653589793 rad
pos: 0.5,-1.5
parent: 0
type: Transform
- bodyType: Static
type: Physics
- uid: 12
type: AirlockExternalShuttleLocked
components:
- pos: -0.5,-1.5
parent: 0
type: Transform
- fixtures:
- shape: !type:PolygonShape
radius: 0.01
vertices:
- 0.49,-0.49
- 0.49,0.49
- -0.49,0.49
- -0.49,-0.49
mask:
- Impassable
- MidImpassable
- HighImpassable
- LowImpassable
- InteractImpassable
layer:
- MidImpassable
- HighImpassable
- BulletImpassable
- InteractImpassable
- Opaque
density: 100
hard: True
restitution: 0
friction: 0.4
id: null
- shape: !type:PhysShapeCircle
radius: 0.2
position: 0,-0.5
mask: []
layer: []
density: 1
hard: False
restitution: 0
friction: 0.4
id: docking
type: Fixtures
- uid: 13
type: GeneratorWallmountAPU
components:
- pos: -1.5,-0.5
parent: 0
type: Transform
- uid: 14
type: SubstationWallBasic
components:
- pos: 0.5,-0.5
parent: 0
type: Transform
- uid: 15
type: APCBasic
components:
- rot: 1.5707963267948966 rad
pos: -1.5,0.5
parent: 0
type: Transform
- uid: 16
type: CableHV
components:
- pos: -1.5,-0.5
parent: 0
type: Transform
- enabled: True
type: AmbientSound
- uid: 17
type: CableHV
components:
- pos: -0.5,-0.5
parent: 0
type: Transform
- uid: 18
type: CableHV
components:
- pos: 0.5,-0.5
parent: 0
type: Transform
- enabled: True
type: AmbientSound
- uid: 19
type: CableMV
components:
- pos: 0.5,-0.5
parent: 0
type: Transform
- enabled: True
type: AmbientSound
- uid: 20
type: CableMV
components:
- pos: -0.5,-0.5
parent: 0
type: Transform
- uid: 21
type: CableMV
components:
- pos: -0.5,0.5
parent: 0
type: Transform
- uid: 22
type: CableMV
components:
- pos: -1.5,0.5
parent: 0
type: Transform
- enabled: True
type: AmbientSound
- uid: 23
type: CableApcExtension
components:
- pos: -1.5,0.5
parent: 0
type: Transform
- enabled: True
type: AmbientSound
- uid: 24
type: CableApcExtension
components:
- pos: -0.5,0.5
parent: 0
type: Transform
- uid: 25
type: CableApcExtension
components:
- pos: -0.5,-0.5
parent: 0
type: Transform
- uid: 26
type: ChairPilotSeat
components:
- rot: 3.141592653589793 rad
pos: -0.5,-0.5
parent: 0
type: Transform
- bodyType: Static
type: Physics
- uid: 27
type: ChairPilotSeat
components:
- rot: 3.141592653589793 rad
pos: -0.5,0.5
parent: 0
type: Transform
- bodyType: Static
type: Physics
- uid: 28
type: PoweredSmallLight
components:
- rot: 1.5707963267948966 rad
pos: -0.5,-0.5
parent: 0
type: Transform
- powerLoad: 0
type: ApcPowerReceiver
...

View File

@@ -740,6 +740,13 @@
- type: PriorityDock
tag: DockArrivals
- type: entity
parent: AirlockGlassShuttle
id: AirlockExternalGlassShuttleEscape
suffix: External, Escape 3x4, Glass, Docking
components:
- type: GridFill
#HighSecDoors
- type: entity
parent: HighSecDoor