+using System.Linq;
using System.Numerics;
using System.Threading;
using Content.Server.Access.Systems;
}
/// <summary>
- /// Attempts to dock the emergency shuttle to the station.
+ /// Attempts to dock a station's emergency shuttle.
/// </summary>
- public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null)
+ /// <seealso cref="DockEmergencyShuttle"/>
+ public ShuttleDockResult? DockSingleEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null)
{
if (!Resolve(stationUid, ref stationShuttle))
- return;
+ return null;
if (!TryComp(stationShuttle.EmergencyShuttle, out TransformComponent? xform) ||
!TryComp<ShuttleComponent>(stationShuttle.EmergencyShuttle, out var shuttle))
{
Log.Error($"Attempted to call an emergency shuttle for an uninitialized station? Station: {ToPrettyString(stationUid)}. Shuttle: {ToPrettyString(stationShuttle.EmergencyShuttle)}");
- return;
+ return null;
}
var targetGrid = _station.GetLargestGrid(Comp<StationDataComponent>(stationUid));
// UHH GOOD LUCK
if (targetGrid == null)
{
- _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}");
- _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false);
- // TODO: Need filter extensions or something don't blame me.
- _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
- return;
+ _logger.Add(
+ LogType.EmergencyShuttle,
+ LogImpact.High,
+ $"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}");
+
+ return new ShuttleDockResult
+ {
+ Station = (stationUid, stationShuttle),
+ ResultType = ShuttleDockResultType.GoodLuck,
+ };
}
- var xformQuery = GetEntityQuery<TransformComponent>();
+ ShuttleDockResultType resultType;
+ if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, out var config, DockTag))
+ {
+ _logger.Add(
+ LogType.EmergencyShuttle,
+ LogImpact.High,
+ $"Emergency shuttle {ToPrettyString(stationUid)} docked with stations");
+
+ resultType = _dock.IsConfigPriority(config, DockTag)
+ ? ShuttleDockResultType.PriorityDock
+ : ShuttleDockResultType.OtherDock;
+ }
+ else
+ {
+ _logger.Add(
+ LogType.EmergencyShuttle,
+ LogImpact.High,
+ $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
- if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag))
+ resultType = ShuttleDockResultType.NoDock;
+ }
+
+ return new ShuttleDockResult
{
- if (TryComp(targetGrid.Value, out TransformComponent? targetXform))
- {
- var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
- var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
- var location = FormattedMessage.RemoveMarkupPermissive(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
- _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false);
- }
+ Station = (stationUid, stationShuttle),
+ DockingConfig = config,
+ ResultType = resultType,
+ TargetGrid = targetGrid,
+ };
+ }
- // shuttle timers
- var time = TimeSpan.FromSeconds(_consoleAccumulator);
- if (TryComp<DeviceNetworkComponent>(stationShuttle.EmergencyShuttle.Value, out var netComp))
- {
- var payload = new NetworkPayload
- {
- [ShuttleTimerMasks.ShuttleMap] = stationShuttle.EmergencyShuttle.Value,
- [ShuttleTimerMasks.SourceMap] = targetXform?.MapUid,
- [ShuttleTimerMasks.DestMap] = _roundEnd.GetCentcomm(),
- [ShuttleTimerMasks.ShuttleTime] = time,
- [ShuttleTimerMasks.SourceTime] = time,
- [ShuttleTimerMasks.DestTime] = time + TimeSpan.FromSeconds(TransitTime),
- [ShuttleTimerMasks.Docked] = true
- };
- _deviceNetworkSystem.QueuePacket(stationShuttle.EmergencyShuttle.Value, null, payload, netComp.TransmitFrequency);
- }
+ /// <summary>
+ /// Do post-shuttle-dock setup. Announce to the crew and set up shuttle timers.
+ /// </summary>
+ public void AnnounceShuttleDock(ShuttleDockResult result, bool extended)
+ {
+ var shuttle = result.Station.Comp.EmergencyShuttle;
- _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} docked with stations");
- // TODO: Need filter extensions or something don't blame me.
- _audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true);
- }
- else
+ DebugTools.Assert(shuttle != null);
+
+ if (result.ResultType == ShuttleDockResultType.GoodLuck)
{
- if (TryComp<TransformComponent>(targetGrid.Value, out var targetXform))
- {
- var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
- var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
- var location = FormattedMessage.RemoveMarkupPermissive(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
- _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefaultSound: false);
- }
+ _chatSystem.DispatchStationAnnouncement(
+ result.Station,
+ Loc.GetString("emergency-shuttle-good-luck"),
+ playDefaultSound: false);
- _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
+ return;
}
+
+ DebugTools.Assert(result.TargetGrid != null);
+
+ // Send station announcement.
+
+ var targetXform = Transform(result.TargetGrid.Value);
+ var angle = _dock.GetAngle(
+ shuttle.Value,
+ Transform(shuttle.Value),
+ result.TargetGrid.Value,
+ targetXform);
+
+ var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
+ var location = FormattedMessage.RemoveMarkupPermissive(
+ _navMap.GetNearestBeaconString((shuttle.Value, Transform(shuttle.Value))));
+
+ var extendedText = extended ? Loc.GetString("emergency-shuttle-extended") : "";
+ var locKey = result.ResultType == ShuttleDockResultType.NoDock
+ ? "emergency-shuttle-nearby"
+ : "emergency-shuttle-docked";
+
+ _chatSystem.DispatchStationAnnouncement(
+ result.Station,
+ Loc.GetString(
+ locKey,
+ ("time", $"{_consoleAccumulator:0}"),
+ ("direction", direction),
+ ("location", location),
+ ("extended", extendedText)),
+ playDefaultSound: false);
+
+ // Trigger shuttle timers on the shuttle.
+
+ var time = TimeSpan.FromSeconds(_consoleAccumulator);
+ if (TryComp<DeviceNetworkComponent>(shuttle, out var netComp))
+ {
+ var payload = new NetworkPayload
+ {
+ [ShuttleTimerMasks.ShuttleMap] = shuttle,
+ [ShuttleTimerMasks.SourceMap] = targetXform.MapUid,
+ [ShuttleTimerMasks.DestMap] = _roundEnd.GetCentcomm(),
+ [ShuttleTimerMasks.ShuttleTime] = time,
+ [ShuttleTimerMasks.SourceTime] = time,
+ [ShuttleTimerMasks.DestTime] = time + TimeSpan.FromSeconds(TransitTime),
+ [ShuttleTimerMasks.Docked] = true,
+ };
+ _deviceNetworkSystem.QueuePacket(shuttle.Value, null, payload, netComp.TransmitFrequency);
+ }
+
+ // Play announcement audio.
+
+ var audioFile = result.ResultType == ShuttleDockResultType.NoDock
+ ? "/Audio/Misc/notice1.ogg"
+ : "/Audio/Announcements/shuttle_dock.ogg";
+
+ // TODO: Need filter extensions or something don't blame me.
+ _audio.PlayGlobal(audioFile, Filter.Broadcast(), true);
}
private void OnStationInit(EntityUid uid, StationCentcommComponent component, MapInitEvent args)
}
/// <summary>
- /// Spawns the emergency shuttle for each station and starts the countdown until controls unlock.
+ /// Teleports the emergency shuttle to its station and starts the countdown until it launches.
/// </summary>
- public void CallEmergencyShuttle()
+ /// <remarks>
+ /// If the emergency shuttle is disabled, this immediately ends the round.
+ /// </remarks>
+ public void DockEmergencyShuttle()
{
if (EmergencyShuttleArrived)
return;
var query = AllEntityQuery<StationEmergencyShuttleComponent>();
+ var dockResults = new List<ShuttleDockResult>();
+
while (query.MoveNext(out var uid, out var comp))
{
- CallEmergencyShuttle(uid, comp);
+ if (DockSingleEmergencyShuttle(uid, comp) is { } dockResult)
+ dockResults.Add(dockResult);
+ }
+
+ // Make the shuttle wait longer if it couldn't dock in the normal spot.
+ // We have to handle the possibility of there being multiple stations, so since the shuttle timer is global,
+ // use the WORST value we have.
+ var worstResult = dockResults.Max(x => x.ResultType);
+ var multiplier = worstResult switch
+ {
+ ShuttleDockResultType.OtherDock => _configManager.GetCVar(
+ CCVars.EmergencyShuttleDockTimeMultiplierOtherDock),
+ ShuttleDockResultType.NoDock => _configManager.GetCVar(
+ CCVars.EmergencyShuttleDockTimeMultiplierNoDock),
+ // GoodLuck doesn't get a multiplier.
+ // Quite frankly at that point the round is probably so fucked that you'd rather it be over ASAP.
+ _ => 1,
+ };
+
+ _consoleAccumulator *= multiplier;
+
+ foreach (var shuttleDockResult in dockResults)
+ {
+ AnnounceShuttleDock(shuttleDockResult, multiplier > 1);
}
_commsConsole.UpdateCommsConsoleInterface();
return _transformSystem.GetWorldMatrix(shuttleXform).TransformBox(grid.LocalAABB).Contains(_transformSystem.GetWorldPosition(xform));
}
+
+ /// <summary>
+ /// A result of a shuttle dock operation done by <see cref="EmergencyShuttleSystem.DockSingleEmergencyShuttle"/>.
+ /// </summary>
+ /// <seealso cref="ShuttleDockResultType"/>
+ public sealed class ShuttleDockResult
+ {
+ /// <summary>
+ /// The station for which the emergency shuttle got docked.
+ /// </summary>
+ public Entity<StationEmergencyShuttleComponent> Station;
+
+ /// <summary>
+ /// The target grid of the station that the shuttle tried to dock to.
+ /// </summary>
+ /// <remarks>
+ /// Not present if <see cref="ResultType"/> is <see cref="ShuttleDockResultType.GoodLuck"/>.
+ /// </remarks>
+ public EntityUid? TargetGrid;
+
+ /// <summary>
+ /// Enum code describing the dock result.
+ /// </summary>
+ public ShuttleDockResultType ResultType;
+
+ /// <summary>
+ /// The docking config used to actually dock to the station.
+ /// </summary>
+ /// <remarks>
+ /// Only present if <see cref="ResultType"/> is <see cref="ShuttleDockResultType.PriorityDock"/>
+ /// or <see cref="ShuttleDockResultType.NoDock"/>.
+ /// </remarks>
+ public DockingConfig? DockingConfig;
+ }
+
+ /// <summary>
+ /// Emergency shuttle dock result codes used by <see cref="ShuttleDockResult"/>.
+ /// </summary>
+ public enum ShuttleDockResultType : byte
+ {
+ // This enum is ordered from "best" to "worst". This is used to sort the results.
+
+ /// <summary>
+ /// The shuttle was docked at a priority dock, which is the intended destination.
+ /// </summary>
+ PriorityDock,
+
+ /// <summary>
+ /// The shuttle docked at another dock on the station then the intended priority dock.
+ /// </summary>
+ OtherDock,
+
+ /// <summary>
+ /// The shuttle couldn't find any suitable dock on the station at all, it did not dock.
+ /// </summary>
+ NoDock,
+
+ /// <summary>
+ /// No station grid was found at all, shuttle did not get moved.
+ /// </summary>
+ GoodLuck,
+ }
}