using System.Numerics; using Content.Server._DV.Station.Components; using Content.Server.Chat.Systems; using Content.Server.Communications; using Content.Server.DeviceNetwork.Systems; using Content.Server.Pinpointer; using Content.Server.Screens.Components; using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Systems; using Content.Server.Station.Systems; using Content.Shared.DeviceNetwork.Components; using Content.Shared.DeviceNetwork; using Content.Shared.Localizations; using Content.Shared.Shuttles.Components; using Content.Shared.Station.Components; using Content.Shared.Tiles; using Robust.Shared.Audio; using Robust.Shared.EntitySerialization.Systems; using Robust.Shared.Map.Components; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Content.Server._DV.Station.Systems; public sealed class StationExfiltrationSystem : EntitySystem { [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!; [Dependency] private readonly DockingSystem _docking = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly MapLoaderSystem _loader = default!; [Dependency] private readonly ShuttleSystem _shuttle = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly NavMapSystem _navMap = default!; [Dependency] private readonly CommunicationsConsoleSystem _communicationsConsole = default!; public override void Initialize() { base.Initialize(); } public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (_timing.CurTime >= comp.ArrivalTime && comp.SpawnedShuttle is not null) { DockShuttle((uid, comp)); } if (_timing.CurTime >= comp.ImpendingDepartureAnnouncementTime) { _chat.DispatchGlobalAnnouncement( Loc.GetString(comp.AboutToLeaveAnnouncement, ("time", comp.DepartureWarningTime.TotalSeconds), ("station", Name(uid))), sender: Loc.GetString(comp.Sender), colorOverride: Color.Red); comp.ImpendingDepartureAnnouncementTime = null; } if (_timing.CurTime >= comp.DepartureTime) { _chat.DispatchGlobalAnnouncement( Loc.GetString(comp.LeftAnnouncement, ("station", Name(uid))), sender: Loc.GetString(comp.Sender), colorOverride: Color.Gold); comp.DepartureTime = null; QueueDel(comp.SpawnedShuttle); comp.SpawnedShuttle = null; } } } private bool PrepareShuttle(Entity ent) { if (ent.Comp.SpawnedShuttle is not null) return true; if (!TryComp(ent, out var centcomm) || !TryComp(centcomm.MapEntity, out var centcommMap)) return false; if (!_loader.TryLoadGrid(centcommMap.MapId, ent.Comp.ShuttlePath, out var shuttle, offset: new Vector2(-1000f, 0f))) { Log.Error($"Unable to spawn exfiltration shuttle {ent.Comp.ShuttlePath} for {ToPrettyString(ent)}"); return false; } ent.Comp.SpawnedShuttle = shuttle.Value; EnsureComp(shuttle.Value); EnsureComp(shuttle.Value); return true; } private void DockShuttle(Entity ent) { if (ent.Comp.SpawnedShuttle is not { } shuttle) return; if (!TryComp(shuttle, out var shuttleComp)) return; if (!TryComp(ent, out var stationData)) return; if (_station.GetLargestGrid((ent, stationData)) is not { } grid) return; if (!TryComp(ent, out var centcomm)) return; ent.Comp.ArrivalTime = null; var ok = _shuttle.TryFTLDock(shuttle, shuttleComp, grid, out var config, ent.Comp.DockTo); var angle = _docking.GetAngle(shuttle, Transform(shuttle), grid, Transform(grid)); var direction = ContentLocalizationManager.FormatDirection(angle.GetDir()); var location = FormattedMessage.RemoveMarkupPermissive( _navMap.GetNearestBeaconString((shuttle, Transform(shuttle)))); var locKey = ok ? ent.Comp.DockedAtStationAnnouncement : ent.Comp.DockedNearbyStationAnnouncement; _chat.DispatchStationAnnouncement( ent, Loc.GetString( locKey, ("time", $"{ent.Comp.LeaveTime.TotalSeconds}"), ("direction", direction), ("location", location), ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold); if (TryComp(shuttle, out var netComp)) { var payload = new NetworkPayload { [ShuttleTimerMasks.ShuttleMap] = shuttle, [ShuttleTimerMasks.SourceMap] = Transform(grid).MapUid, [ShuttleTimerMasks.DestMap] = centcomm.MapEntity, [ShuttleTimerMasks.ShuttleTime] = ent.Comp.LeaveTime, [ShuttleTimerMasks.SourceTime] = ent.Comp.LeaveTime, [ShuttleTimerMasks.DestTime] = ent.Comp.LeaveTime, [ShuttleTimerMasks.Docked] = true, }; _deviceNetwork.QueuePacket(shuttle, null, payload, netComp.TransmitFrequency); } ent.Comp.ImpendingDepartureAnnouncementTime = _timing.CurTime + ent.Comp.LeaveTime - ent.Comp.DepartureWarningTime; ent.Comp.DepartureTime = _timing.CurTime + ent.Comp.LeaveTime; } public void Call(Entity ent) { if (!Resolve(ent.Owner, ref ent.Comp, false)) return; if (!PrepareShuttle((ent.Owner, ent.Comp))) { _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.FailedAnnouncement, ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold); } else { ent.Comp.ArrivalTime = _timing.CurTime + ent.Comp.TravelTime; _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.CalledAnnouncement, ("time", ent.Comp.TravelTime.TotalSeconds), ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold); } _communicationsConsole.UpdateCommsConsoleInterface(); } public void Recall(Entity ent) { if (!Resolve(ent.Owner, ref ent.Comp, false)) return; ent.Comp.ArrivalTime = null; _chat.DispatchStationAnnouncement(ent, Loc.GetString(ent.Comp.RecalledAnnouncement, ("station", Name(ent))), sender: Loc.GetString(ent.Comp.Sender), colorOverride: Color.Gold); _communicationsConsole.UpdateCommsConsoleInterface(); } }