From 5a6a3371dc63d20ddad925e2c6d0b2b6d8aee5bc Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 9 Sep 2024 20:10:28 +0200 Subject: [PATCH] Extend shuttle dock time if the shuttle doesn't dock at evac. (#31496) * Extend shuttle dock time if the shuttle doesn't dock at evac. If the shuttle can't dock at evac for *some reason*, it will instead try to dock at another port on the station. And if that fails it goes somewhere random on the station. Because of the chaos and confusion caused by this, many people will frequently not get to the shuttle in time under these circumstances. This sucks for everybody. To alleviate this, the shuttle launch timer will now be extended if the station doesn't dock at its ideal spot. The default values (controlled via CVar) are 1.667x and 2x respectively for "wrong dock" and "no dock at all" scenarios. The code around here was a mess, so I fixed that too. "CallEmergencyShuttle" has been renamed to "DockEmergencyShuttle", the overload that did the actual docking has been renamed to DockSingleEmergencyShuttle. Code has been split into separate dock -> announce methods so we can calculate shuttle delay in between the case of multiple stations. Also made the "shuttle couldn't find a dock" text announce the time until it launches and fix the shuttle timers not triggering for it. * Minor review --------- --- Content.Server/RoundEnd/RoundEndSystem.cs | 2 +- .../Commands/DockEmergencyShuttleCommand.cs | 2 +- .../Shuttles/Systems/DockingSystem.Shuttle.cs | 13 +- .../Systems/EmergencyShuttleSystem.cs | 258 ++++++++++++++---- .../Systems/ShuttleSystem.FasterThanLight.cs | 24 +- Content.Shared/CCVar/CCVars.cs | 12 + Resources/Locale/en-US/shuttles/emergency.ftl | 5 +- 7 files changed, 256 insertions(+), 60 deletions(-) diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs index 42783f163b..82bdb78816 100644 --- a/Content.Server/RoundEnd/RoundEndSystem.cs +++ b/Content.Server/RoundEnd/RoundEndSystem.cs @@ -194,7 +194,7 @@ namespace Content.Server.RoundEnd ExpectedCountdownEnd = _gameTiming.CurTime + countdownTime; // TODO full game saves - Timer.Spawn(countdownTime, _shuttle.CallEmergencyShuttle, _countdownTokenSource.Token); + Timer.Spawn(countdownTime, _shuttle.DockEmergencyShuttle, _countdownTokenSource.Token); ActivateCooldown(); RaiseLocalEvent(RoundEndSystemChangedEvent.Default); diff --git a/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs b/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs index 8febe51f5a..f219602bcb 100644 --- a/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs +++ b/Content.Server/Shuttles/Commands/DockEmergencyShuttleCommand.cs @@ -19,6 +19,6 @@ public sealed class DockEmergencyShuttleCommand : IConsoleCommand public void Execute(IConsoleShell shell, string argStr, string[] args) { var system = _sysManager.GetEntitySystem(); - system.CallEmergencyShuttle(); + system.DockEmergencyShuttle(); } } diff --git a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs index 597d74dcc7..aabfaa31dd 100644 --- a/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs +++ b/Content.Server/Shuttles/Systems/DockingSystem.Shuttle.cs @@ -17,7 +17,7 @@ public sealed partial class DockingSystem private const int DockRoundingDigits = 2; - public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform, EntityQuery xformQuery) + public Angle GetAngle(EntityUid uid, TransformComponent xform, EntityUid targetUid, TransformComponent targetXform) { var (shuttlePos, shuttleRot) = _transform.GetWorldPositionRotation(xform); var (targetPos, targetRot) = _transform.GetWorldPositionRotation(targetXform); @@ -288,9 +288,7 @@ public sealed partial class DockingSystem // Prioritise by priority docks, then by maximum connected ports, then by most similar angle. validDockConfigs = validDockConfigs - .OrderByDescending(x => x.Docks.Any(docks => - TryComp(docks.DockBUid, out var priority) && - priority.Tag?.Equals(priorityTag) == true)) + .OrderByDescending(x => IsConfigPriority(x, priorityTag)) .ThenByDescending(x => x.Docks.Count) .ThenBy(x => Math.Abs(Angle.ShortestDistance(x.Angle.Reduced(), targetGridAngle).Theta)).ToList(); @@ -301,6 +299,13 @@ public sealed partial class DockingSystem return location; } + public bool IsConfigPriority(DockingConfig config, string? priorityTag) + { + return config.Docks.Any(docks => + TryComp(docks.DockBUid, out var priority) + && priority.Tag?.Equals(priorityTag) == true); + } + /// /// Checks whether the shuttle can warp to the specified position. /// diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 4c13a2cc82..6c4bdc0814 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Numerics; using System.Threading; using Content.Server.Access.Systems; @@ -255,18 +256,19 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem } /// - /// Attempts to dock the emergency shuttle to the station. + /// Attempts to dock a station's emergency shuttle. /// - public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) + /// + public ShuttleDockResult? DockSingleEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) { if (!Resolve(stationUid, ref stationShuttle)) - return; + return null; if (!TryComp(stationShuttle.EmergencyShuttle, out TransformComponent? xform) || !TryComp(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(stationUid)); @@ -274,60 +276,126 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem // 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(); + 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(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); - } + /// + /// Do post-shuttle-dock setup. Announce to the crew and set up shuttle timers. + /// + 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(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(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) @@ -353,9 +421,12 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem } /// - /// 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. /// - public void CallEmergencyShuttle() + /// + /// If the emergency shuttle is disabled, this immediately ends the round. + /// + public void DockEmergencyShuttle() { if (EmergencyShuttleArrived) return; @@ -371,9 +442,34 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem var query = AllEntityQuery(); + var dockResults = new List(); + 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(); @@ -579,4 +675,66 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem return _transformSystem.GetWorldMatrix(shuttleXform).TransformBox(grid.LocalAABB).Contains(_transformSystem.GetWorldPosition(xform)); } + + /// + /// A result of a shuttle dock operation done by . + /// + /// + public sealed class ShuttleDockResult + { + /// + /// The station for which the emergency shuttle got docked. + /// + public Entity Station; + + /// + /// The target grid of the station that the shuttle tried to dock to. + /// + /// + /// Not present if is . + /// + public EntityUid? TargetGrid; + + /// + /// Enum code describing the dock result. + /// + public ShuttleDockResultType ResultType; + + /// + /// The docking config used to actually dock to the station. + /// + /// + /// Only present if is + /// or . + /// + public DockingConfig? DockingConfig; + } + + /// + /// Emergency shuttle dock result codes used by . + /// + public enum ShuttleDockResultType : byte + { + // This enum is ordered from "best" to "worst". This is used to sort the results. + + /// + /// The shuttle was docked at a priority dock, which is the intended destination. + /// + PriorityDock, + + /// + /// The shuttle docked at another dock on the station then the intended priority dock. + /// + OtherDock, + + /// + /// The shuttle couldn't find any suitable dock on the station at all, it did not dock. + /// + NoDock, + + /// + /// No station grid was found at all, shuttle did not get moved. + /// + GoodLuck, + } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 8da7aaa641..f30cab253a 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -669,8 +669,28 @@ public sealed partial class ShuttleSystem /// Tries to dock with the target grid, otherwise falls back to proximity. /// This bypasses FTL travel time. /// - public bool TryFTLDock(EntityUid shuttleUid, ShuttleComponent component, EntityUid targetUid, string? priorityTag = null) + public bool TryFTLDock( + EntityUid shuttleUid, + ShuttleComponent component, + EntityUid targetUid, + string? priorityTag = null) + { + return TryFTLDock(shuttleUid, component, targetUid, out _, priorityTag); + } + + /// + /// Tries to dock with the target grid, otherwise falls back to proximity. + /// This bypasses FTL travel time. + /// + public bool TryFTLDock( + EntityUid shuttleUid, + ShuttleComponent component, + EntityUid targetUid, + [NotNullWhen(true)] out DockingConfig? config, + string? priorityTag = null) { + config = null; + if (!_xformQuery.TryGetComponent(shuttleUid, out var shuttleXform) || !_xformQuery.TryGetComponent(targetUid, out var targetXform) || targetXform.MapUid == null || @@ -679,7 +699,7 @@ public sealed partial class ShuttleSystem return false; } - var config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag); + config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag); if (config != null) { diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 95fb7bd692..9e95231c84 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1556,6 +1556,18 @@ namespace Content.Shared.CCVar public static readonly CVarDef EmergencyShuttleDockTime = CVarDef.Create("shuttle.emergency_dock_time", 180f, CVar.SERVERONLY); + /// + /// If the emergency shuttle can't dock at a priority port, the dock time will be multiplied with this value. + /// + public static readonly CVarDef EmergencyShuttleDockTimeMultiplierOtherDock = + CVarDef.Create("shuttle.emergency_dock_time_multiplier_other_dock", 1.6667f, CVar.SERVERONLY); + + /// + /// If the emergency shuttle can't dock at all, the dock time will be multiplied with this value. + /// + public static readonly CVarDef EmergencyShuttleDockTimeMultiplierNoDock = + CVarDef.Create("shuttle.emergency_dock_time_multiplier_no_dock", 2f, CVar.SERVERONLY); + /// /// How long after the console is authorized for the shuttle to early launch. /// diff --git a/Resources/Locale/en-US/shuttles/emergency.ftl b/Resources/Locale/en-US/shuttles/emergency.ftl index be3f0962fa..ef3582c623 100644 --- a/Resources/Locale/en-US/shuttles/emergency.ftl +++ b/Resources/Locale/en-US/shuttles/emergency.ftl @@ -13,9 +13,10 @@ emergency-shuttle-command-launch-desc = Early launches the emergency shuttle if # Emergency shuttle emergency-shuttle-left = The Emergency Shuttle has left the station. Estimate {$transitTime} seconds until the shuttle arrives at CentComm. emergency-shuttle-launch-time = The emergency shuttle will launch in {$consoleAccumulator} seconds. -emergency-shuttle-docked = The Emergency Shuttle has docked {$direction} of the station, {$location}. It will leave in {$time} seconds. +emergency-shuttle-docked = The Emergency Shuttle has docked {$direction} of the station, {$location}. It will leave in {$time} seconds.{$extended} emergency-shuttle-good-luck = The Emergency Shuttle is unable to find a station. Good luck. -emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped in {$direction} of the station, {$location}. +emergency-shuttle-nearby = The Emergency Shuttle is unable to find a valid docking port. It has warped in {$direction} of the station, {$location}. It will leave in {$time} seconds.{$extended} +emergency-shuttle-extended = {" "}Launch time has been extended due to inconvenient circumstances. # Emergency shuttle console popup / announcement emergency-shuttle-console-no-early-launches = Early launch is disabled -- 2.51.2