From c346640adeea2028deae1597f82f9043527d6801 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:00:08 +0000 Subject: [PATCH] escape + kill objective changes (#17890) * 3 space indent at start of line jumpscare * _leftShuttles -> ShuttlesLeft * stuff * RequireDead override for the future * fix 50% logic * rouge * pod 1984 * technically more "difficult" --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../Conditions/EscapeShuttleCondition.cs | 43 +- .../Conditions/KillPersonCondition.cs | 40 +- .../Systems/EmergencyShuttleSystem.Console.cs | 11 +- .../Systems/EmergencyShuttleSystem.cs | 735 +++++++++--------- .../conditions/escape-shuttle-condition.ftl | 2 +- .../conditions/kill-person-condition.ftl | 4 +- 6 files changed, 441 insertions(+), 394 deletions(-) diff --git a/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs b/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs index a41d891ff9..f26d256446 100644 --- a/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs +++ b/Content.Server/Objectives/Conditions/EscapeShuttleCondition.cs @@ -1,10 +1,8 @@ using Content.Server.Mind; using Content.Server.Objectives.Interfaces; -using Content.Server.Shuttles.Components; -using Content.Server.Station.Components; +using Content.Server.Shuttles.Systems; using Content.Shared.Cuffs.Components; using JetBrains.Annotations; -using Robust.Shared.Map.Components; using Robust.Shared.Utility; namespace Content.Server.Objectives.Conditions @@ -28,22 +26,6 @@ namespace Content.Server.Objectives.Conditions public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Structures/Furniture/chairs.rsi"), "shuttle"); - private bool IsAgentOnShuttle(TransformComponent agentXform, EntityUid? shuttle) - { - if (shuttle == null) - return false; - - var entMan = IoCManager.Resolve(); - - if (!entMan.TryGetComponent(shuttle, out var shuttleGrid) || - !entMan.TryGetComponent(shuttle, out var shuttleXform)) - { - return false; - } - - return shuttleXform.WorldMatrix.TransformBox(shuttleGrid.LocalAABB).Contains(agentXform.WorldPosition); - } - public float Progress { get { @@ -54,25 +36,22 @@ namespace Content.Server.Objectives.Conditions || !entMan.TryGetComponent(_mind.OwnedEntity, out var xform)) return 0f; - var shuttleContainsAgent = false; - var agentIsAlive = !mindSystem.IsCharacterDeadIc(_mind); - var agentIsEscaping = true; + if (mindSystem.IsCharacterDeadIc(_mind)) + return 0f; if (entMan.TryGetComponent(_mind.OwnedEntity, out var cuffed) && cuffed.CuffedHandCount > 0) - // You're not escaping if you're restrained! - agentIsEscaping = false; - - // Any emergency shuttle counts for this objective. - foreach (var stationData in entMan.EntityQuery()) { - if (IsAgentOnShuttle(xform, stationData.EmergencyShuttle)) { - shuttleContainsAgent = true; - break; - } + // You're not escaping if you're restrained! + return 0f; } - return (shuttleContainsAgent && agentIsAlive && agentIsEscaping) ? 1f : 0f; + // Any emergency shuttle counts for this objective, but not pods. + var emergencyShuttle = entMan.System(); + if (!emergencyShuttle.IsTargetEscaping(_mind.OwnedEntity.Value)) + return 0f; + + return 1f; } } diff --git a/Content.Server/Objectives/Conditions/KillPersonCondition.cs b/Content.Server/Objectives/Conditions/KillPersonCondition.cs index cdfd25174e..a1025ab256 100644 --- a/Content.Server/Objectives/Conditions/KillPersonCondition.cs +++ b/Content.Server/Objectives/Conditions/KillPersonCondition.cs @@ -1,6 +1,9 @@ using Content.Server.Mind; using Content.Server.Objectives.Interfaces; +using Content.Server.Shuttles.Systems; +using Content.Shared.CCVar; using Content.Shared.Mobs.Systems; +using Robust.Shared.Configuration; using Robust.Shared.Utility; namespace Content.Server.Objectives.Conditions @@ -12,6 +15,11 @@ namespace Content.Server.Objectives.Conditions protected Mind.Mind? Target; public abstract IObjectiveCondition GetAssigned(Mind.Mind mind); + /// + /// Whether the target must be truly dead, ignores missing evac. + /// + protected bool RequireDead = false; + public string Title { get @@ -37,13 +45,37 @@ namespace Content.Server.Objectives.Conditions { get { - var entityManager = IoCManager.Resolve(); - var mindSystem = entityManager.System(); - return Target == null || mindSystem.IsCharacterDeadIc(Target) ? 1f : 0f; + if (Target == null || Target.OwnedEntity == null) + return 1f; + + var entMan = IoCManager.Resolve(); + var mindSystem = entMan.System(); + if (mindSystem.IsCharacterDeadIc(Target)) + return 1f; + + if (RequireDead) + return 0f; + + // if evac is disabled then they really do have to be dead + var configMan = IoCManager.Resolve(); + if (!configMan.GetCVar(CCVars.EmergencyShuttleEnabled)) + return 0f; + + // target is escaping so you fail + var emergencyShuttle = entMan.System(); + if (emergencyShuttle.IsTargetEscaping(Target.OwnedEntity.Value)) + return 0f; + + // evac has left without the target, greentext since the target is afk in space with a full oxygen tank and coordinates off. + if (emergencyShuttle.ShuttlesLeft) + return 1f; + + // if evac is still here and target hasn't boarded, show 50% to give you an indicator that you are doing good + return emergencyShuttle.EmergencyShuttleArrived ? 0f : 0.5f; } } - public float Difficulty => 2f; + public float Difficulty => 1.75f; public bool Equals(IObjectiveCondition? other) { diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs index 842bf62688..adff4bc1b8 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs @@ -71,7 +71,10 @@ public sealed partial class EmergencyShuttleSystem /// private bool _launchedShuttles; - private bool _leftShuttles; + /// + /// Have the emergency shuttles left for CentCom? + /// + public bool ShuttlesLeft; /// /// Have we announced the launch? @@ -209,9 +212,9 @@ public sealed partial class EmergencyShuttleSystem } // Departed - if (!_leftShuttles && _consoleAccumulator <= 0f) + if (!ShuttlesLeft && _consoleAccumulator <= 0f) { - _leftShuttles = true; + ShuttlesLeft = true; _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}"))); Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken?.Token ?? default); @@ -309,7 +312,7 @@ public sealed partial class EmergencyShuttleSystem { _announced = false; _roundEndCancelToken = null; - _leftShuttles = false; + ShuttlesLeft = false; _launchedShuttles = false; _consoleAccumulator = float.MinValue; EarlyLaunchAuthorized = false; diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 9b0a5f85e6..6287295061 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -31,355 +31,388 @@ namespace Content.Server.Shuttles.Systems; public sealed partial class EmergencyShuttleSystem : EntitySystem { - /* - * Handles the escape shuttle + CentCom. - */ - - [Dependency] private readonly IAdminLogManager _logger = default!; - [Dependency] private readonly IAdminManager _admin = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; - [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly AccessReaderSystem _reader = default!; - [Dependency] private readonly ChatSystem _chatSystem = default!; - [Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!; - [Dependency] private readonly DockingSystem _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!; - - private const float ShuttleSpawnBuffer = 1f; - - private bool _emergencyShuttleEnabled; - - private const string DockTag = "DockEmergency"; - - public override void Initialize() - { - _sawmill = Logger.GetSawmill("shuttle.emergency"); - _emergencyShuttleEnabled = _configManager.GetCVar(CCVars.EmergencyShuttleEnabled); - // Don't immediately invoke as roundstart will just handle it. - _configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); - SubscribeLocalEvent(OnRoundStart); - SubscribeLocalEvent(OnStationStartup); - SubscribeLocalEvent(OnCentcommShutdown); - SubscribeLocalEvent(OnCentcommInit); - SubscribeNetworkEvent(OnShuttleRequestPosition); - InitializeEmergencyConsole(); - } - - private void OnRoundStart(RoundStartingEvent ev) - { - CleanupEmergencyConsole(); - _roundEndCancelToken?.Cancel(); - _roundEndCancelToken = new CancellationTokenSource(); - } - - private void OnCentcommShutdown(EntityUid uid, StationCentcommComponent component, ComponentShutdown args) - { - QueueDel(component.Entity); - component.Entity = EntityUid.Invalid; - - if (_mapManager.MapExists(component.MapId)) - _mapManager.DeleteMap(component.MapId); - - component.MapId = MapId.Nullspace; - } - - private void SetEmergencyShuttleEnabled(bool value) - { - if (_emergencyShuttleEnabled == value) - return; - - _emergencyShuttleEnabled = value; - - if (value) - { - SetupEmergencyShuttle(); - } - else - { - CleanupEmergencyShuttle(); - } - } - - private void CleanupEmergencyShuttle() - { - var query = AllEntityQuery(); - - while (query.MoveNext(out var uid, out _)) - { - RemCompDeferred(uid); - } - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - UpdateEmergencyConsole(frameTime); - } - - public override void Shutdown() - { - _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); - ShutdownEmergencyConsole(); - } - - /// - /// If the client is requesting debug info on where an emergency shuttle would dock. - /// - private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args) - { - if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) - return; - - var player = args.SenderSession.AttachedEntity; - if (player is null) - return; - - var station = _station.GetOwningStation(player.Value); - - if (!TryComp(station, out var stationShuttle) || - !HasComp(stationShuttle.EmergencyShuttle)) - { - return; - } - - var targetGrid = _station.GetLargestGrid(Comp(station.Value)); - if (targetGrid == null) - return; - - var config = _dock.GetDockingConfig(stationShuttle.EmergencyShuttle.Value, targetGrid.Value, DockTag); - if (config == null) - return; - - RaiseNetworkEvent(new EmergencyShuttlePositionMessage() - { - StationUid = targetGrid, - Position = config.Area, - }); - } - - /// - /// Calls the emergency shuttle for the station. - /// - public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) - { - if (!Resolve(stationUid, ref stationShuttle) || - !TryComp(stationShuttle.EmergencyShuttle, out var xform) || - !TryComp(stationShuttle.EmergencyShuttle, out var shuttle)) - { - return; - } - - var targetGrid = _station.GetLargestGrid(Comp(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; - } - - var xformQuery = GetEntityQuery(); - - if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag)) - { - if (TryComp(targetGrid.Value, out var targetXform)) - { - var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); - } - - _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 - { - if (TryComp(targetGrid.Value, out var targetXform)) - { - var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), 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); - } - } - - private void OnCentcommInit(EntityUid uid, StationCentcommComponent component, ComponentInit args) - { - if (!_emergencyShuttleEnabled) - return; - - // Post mapinit? fancy - if (TryComp(component.Entity, out var xform)) - { - component.MapId = xform.MapID; - return; - } - - AddCentcomm(component); - } - - private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args) - { - AddEmergencyShuttle(uid, component); - } - - /// - /// Spawns the emergency shuttle for each station and starts the countdown until controls unlock. - /// - public void CallEmergencyShuttle() - { - if (EmergencyShuttleArrived) - return; - - if (!_emergencyShuttleEnabled) - { - _roundEnd.EndRound(); - return; - } - - _consoleAccumulator = _configManager.GetCVar(CCVars.EmergencyShuttleDockTime); - EmergencyShuttleArrived = true; - - var query = AllEntityQuery(); - - while (query.MoveNext(out var uid, out var comp)) - { - CallEmergencyShuttle(uid, comp); - } - - _commsConsole.UpdateCommsConsoleInterface(); - } - - private void SetupEmergencyShuttle() - { - if (!_emergencyShuttleEnabled) - return; - - var centcommQuery = AllEntityQuery(); - - while (centcommQuery.MoveNext(out var centcomm)) - { - AddCentcomm(centcomm); - } - - var query = AllEntityQuery(); - - while (query.MoveNext(out var uid, out var comp)) - { - AddEmergencyShuttle(uid, comp); - } - } - - private void AddCentcomm(StationCentcommComponent component) - { - // Check for existing centcomms and just point to that - var query = AllEntityQuery(); - - while (query.MoveNext(out var otherComp)) - { - if (otherComp == component) - continue; - - component.MapId = otherComp.MapId; - component.ShuttleIndex = otherComp.ShuttleIndex; - return; - } - - var mapId = _mapManager.CreateMap(); - component.MapId = mapId; - - if (!string.IsNullOrEmpty(component.Map.ToString())) - { - var ent = _map.LoadGrid(mapId, component.Map.ToString()); - - if (ent != null) - { - component.Entity = ent.Value; - _shuttle.AddFTLDestination(ent.Value, false); - } - } - else - { - _sawmill.Warning("No CentComm map found, skipping setup."); - } - } - - public HashSet GetCentcommMaps() - { - var query = AllEntityQuery(); - var maps = new HashSet(Count()); - - while (query.MoveNext(out var comp)) - { - maps.Add(comp.MapId); - } - - return maps; - } - - private void AddEmergencyShuttle(EntityUid uid, StationEmergencyShuttleComponent component) - { - if (!_emergencyShuttleEnabled - || component.EmergencyShuttle != null || - !TryComp(uid, out var centcomm)) - { - return; - } - - // Load escape shuttle - var shuttlePath = component.EmergencyShuttlePath; - var shuttle = _map.LoadGrid(centcomm.MapId, shuttlePath.ToString(), new MapLoadOptions() - { - // Should be far enough... right? I'm too lazy to bounds check CentCom rn. - Offset = new Vector2(500f + centcomm.ShuttleIndex, 0f) - }); - - if (shuttle == null) - { - _sawmill.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(uid)}"); - return; - } - - centcomm.ShuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; - - // Update indices for all centcomm comps pointing to same map - var query = AllEntityQuery(); - - while (query.MoveNext(out var comp)) - { - if (comp == centcomm || comp.MapId != centcomm.MapId) - continue; - - comp.ShuttleIndex = centcomm.ShuttleIndex; - } - - component.EmergencyShuttle = shuttle; - EnsureComp(shuttle.Value); - EnsureComp(shuttle.Value); - } - - private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args) - { - if (component.LaunchTime == null) - return; - - component.LaunchTime = component.LaunchTime.Value + args.PausedTime; - } + /* + * Handles the escape shuttle + CentCom. + */ + + [Dependency] private readonly IAdminLogManager _logger = default!; + [Dependency] private readonly IAdminManager _admin = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AccessReaderSystem _reader = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly CommunicationsConsoleSystem _commsConsole = default!; + [Dependency] private readonly DockingSystem _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!; + + private const float ShuttleSpawnBuffer = 1f; + + private bool _emergencyShuttleEnabled; + + private const string DockTag = "DockEmergency"; + + public override void Initialize() + { + _sawmill = Logger.GetSawmill("shuttle.emergency"); + _emergencyShuttleEnabled = _configManager.GetCVar(CCVars.EmergencyShuttleEnabled); + // Don't immediately invoke as roundstart will just handle it. + _configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); + SubscribeLocalEvent(OnRoundStart); + SubscribeLocalEvent(OnStationStartup); + SubscribeLocalEvent(OnCentcommShutdown); + SubscribeLocalEvent(OnCentcommInit); + SubscribeNetworkEvent(OnShuttleRequestPosition); + InitializeEmergencyConsole(); + } + + private void OnRoundStart(RoundStartingEvent ev) + { + CleanupEmergencyConsole(); + _roundEndCancelToken?.Cancel(); + _roundEndCancelToken = new CancellationTokenSource(); + } + + private void OnCentcommShutdown(EntityUid uid, StationCentcommComponent component, ComponentShutdown args) + { + QueueDel(component.Entity); + component.Entity = EntityUid.Invalid; + + if (_mapManager.MapExists(component.MapId)) + _mapManager.DeleteMap(component.MapId); + + component.MapId = MapId.Nullspace; + } + + private void SetEmergencyShuttleEnabled(bool value) + { + if (_emergencyShuttleEnabled == value) + return; + + _emergencyShuttleEnabled = value; + + if (value) + { + SetupEmergencyShuttle(); + } + else + { + CleanupEmergencyShuttle(); + } + } + + private void CleanupEmergencyShuttle() + { + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out _)) + { + RemCompDeferred(uid); + } + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + UpdateEmergencyConsole(frameTime); + } + + public override void Shutdown() + { + _configManager.UnsubValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); + ShutdownEmergencyConsole(); + } + + /// + /// If the client is requesting debug info on where an emergency shuttle would dock. + /// + private void OnShuttleRequestPosition(EmergencyShuttleRequestPositionMessage msg, EntitySessionEventArgs args) + { + if (!_admin.IsAdmin((IPlayerSession) args.SenderSession)) + return; + + var player = args.SenderSession.AttachedEntity; + if (player is null) + return; + + var station = _station.GetOwningStation(player.Value); + + if (!TryComp(station, out var stationShuttle) || + !HasComp(stationShuttle.EmergencyShuttle)) + { + return; + } + + var targetGrid = _station.GetLargestGrid(Comp(station.Value)); + if (targetGrid == null) + return; + + var config = _dock.GetDockingConfig(stationShuttle.EmergencyShuttle.Value, targetGrid.Value, DockTag); + if (config == null) + return; + + RaiseNetworkEvent(new EmergencyShuttlePositionMessage() + { + StationUid = targetGrid, + Position = config.Area, + }); + } + + /// + /// Calls the emergency shuttle for the station. + /// + public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) + { + if (!Resolve(stationUid, ref stationShuttle) || + !TryComp(stationShuttle.EmergencyShuttle, out var xform) || + !TryComp(stationShuttle.EmergencyShuttle, out var shuttle)) + { + return; + } + + var targetGrid = _station.GetLargestGrid(Comp(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; + } + + var xformQuery = GetEntityQuery(); + + if (_shuttle.TryFTLDock(stationShuttle.EmergencyShuttle.Value, shuttle, targetGrid.Value, DockTag)) + { + if (TryComp(targetGrid.Value, out var targetXform)) + { + var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); + } + + _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 + { + if (TryComp(targetGrid.Value, out var targetXform)) + { + var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), 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); + } + } + + private void OnCentcommInit(EntityUid uid, StationCentcommComponent component, ComponentInit args) + { + if (!_emergencyShuttleEnabled) + return; + + // Post mapinit? fancy + if (TryComp(component.Entity, out var xform)) + { + component.MapId = xform.MapID; + return; + } + + AddCentcomm(component); + } + + private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args) + { + AddEmergencyShuttle(uid, component); + } + + /// + /// Spawns the emergency shuttle for each station and starts the countdown until controls unlock. + /// + public void CallEmergencyShuttle() + { + if (EmergencyShuttleArrived) + return; + + if (!_emergencyShuttleEnabled) + { + _roundEnd.EndRound(); + return; + } + + _consoleAccumulator = _configManager.GetCVar(CCVars.EmergencyShuttleDockTime); + EmergencyShuttleArrived = true; + + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var comp)) + { + CallEmergencyShuttle(uid, comp); + } + + _commsConsole.UpdateCommsConsoleInterface(); + } + + private void SetupEmergencyShuttle() + { + if (!_emergencyShuttleEnabled) + return; + + var centcommQuery = AllEntityQuery(); + + while (centcommQuery.MoveNext(out var centcomm)) + { + AddCentcomm(centcomm); + } + + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var comp)) + { + AddEmergencyShuttle(uid, comp); + } + } + + private void AddCentcomm(StationCentcommComponent component) + { + // Check for existing centcomms and just point to that + var query = AllEntityQuery(); + + while (query.MoveNext(out var otherComp)) + { + if (otherComp == component) + continue; + + component.MapId = otherComp.MapId; + component.ShuttleIndex = otherComp.ShuttleIndex; + return; + } + + var mapId = _mapManager.CreateMap(); + component.MapId = mapId; + + if (!string.IsNullOrEmpty(component.Map.ToString())) + { + var ent = _map.LoadGrid(mapId, component.Map.ToString()); + + if (ent != null) + { + component.Entity = ent.Value; + _shuttle.AddFTLDestination(ent.Value, false); + } + } + else + { + _sawmill.Warning("No CentComm map found, skipping setup."); + } + } + + public HashSet GetCentcommMaps() + { + var query = AllEntityQuery(); + var maps = new HashSet(Count()); + + while (query.MoveNext(out var comp)) + { + maps.Add(comp.MapId); + } + + return maps; + } + + private void AddEmergencyShuttle(EntityUid uid, StationEmergencyShuttleComponent component) + { + if (!_emergencyShuttleEnabled + || component.EmergencyShuttle != null || + !TryComp(uid, out var centcomm)) + { + return; + } + + // Load escape shuttle + var shuttlePath = component.EmergencyShuttlePath; + var shuttle = _map.LoadGrid(centcomm.MapId, shuttlePath.ToString(), new MapLoadOptions() + { + // Should be far enough... right? I'm too lazy to bounds check CentCom rn. + Offset = new Vector2(500f + centcomm.ShuttleIndex, 0f) + }); + + if (shuttle == null) + { + _sawmill.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(uid)}"); + return; + } + + centcomm.ShuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; + + // Update indices for all centcomm comps pointing to same map + var query = AllEntityQuery(); + + while (query.MoveNext(out var comp)) + { + if (comp == centcomm || comp.MapId != centcomm.MapId) + continue; + + comp.ShuttleIndex = centcomm.ShuttleIndex; + } + + component.EmergencyShuttle = shuttle; + EnsureComp(shuttle.Value); + EnsureComp(shuttle.Value); + } + + private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args) + { + if (component.LaunchTime == null) + return; + + component.LaunchTime = component.LaunchTime.Value + args.PausedTime; + } + + /// + /// Returns whether a target is escaping on the emergency shuttle, but only if evac has arrived. + /// + public bool IsTargetEscaping(EntityUid target) + { + // if evac isn't here then sitting in a pod doesn't return true + if (!EmergencyShuttleArrived) + return false; + + // check each emergency shuttle + var xform = Transform(target); + foreach (var stationData in EntityQuery()) + { + if (stationData.EmergencyShuttle == null) + continue; + + if (IsOnGrid(xform, stationData.EmergencyShuttle.Value)) + { + return true; + } + } + + return false; + } + + private bool IsOnGrid(TransformComponent xform, EntityUid shuttle, MapGridComponent? grid = null, TransformComponent? shuttleXform = null) + { + if (!Resolve(shuttle, ref grid, ref shuttleXform)) + return false; + + return shuttleXform.WorldMatrix.TransformBox(grid.LocalAABB).Contains(xform.WorldPosition); + } } diff --git a/Resources/Locale/en-US/objectives/conditions/escape-shuttle-condition.ftl b/Resources/Locale/en-US/objectives/conditions/escape-shuttle-condition.ftl index 7749cf7754..5f950444aa 100644 --- a/Resources/Locale/en-US/objectives/conditions/escape-shuttle-condition.ftl +++ b/Resources/Locale/en-US/objectives/conditions/escape-shuttle-condition.ftl @@ -1,2 +1,2 @@ -objective-condition-escape-shuttle-title = Escape on the emergency shuttle alive and unrestrained. +objective-condition-escape-shuttle-title = Escape to centcom alive and unrestrained. objective-condition-escape-shuttle-description = One of our undercover agents will debrief you when you arrive. Don't show up in cuffs. diff --git a/Resources/Locale/en-US/objectives/conditions/kill-person-condition.ftl b/Resources/Locale/en-US/objectives/conditions/kill-person-condition.ftl index 2947bf2ec6..8d25f44606 100644 --- a/Resources/Locale/en-US/objectives/conditions/kill-person-condition.ftl +++ b/Resources/Locale/en-US/objectives/conditions/kill-person-condition.ftl @@ -1,2 +1,2 @@ -objective-condition-kill-person-title = Kill {$targetName}, {CAPITALIZE($job)} -objective-condition-kill-person-description = Do it however you like, just make sure they don't last the shift. +objective-condition-kill-person-title = Kill or maroon {$targetName}, {CAPITALIZE($job)} +objective-condition-kill-person-description = Do it however you like, just make sure they don't make it to centcom.