1
0

ArrivalsSystem.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Server.Administration;
  4. using Content.Server.Chat.Managers;
  5. using Content.Server.DeviceNetwork.Components;
  6. using Content.Server.DeviceNetwork.Systems;
  7. using Content.Server.GameTicking;
  8. using Content.Server.GameTicking.Events;
  9. using Content.Server.Parallax;
  10. using Content.Server.Screens.Components;
  11. using Content.Server.Shuttles.Components;
  12. using Content.Server.Shuttles.Events;
  13. using Content.Server.Spawners.Components;
  14. using Content.Server.Spawners.EntitySystems;
  15. using Content.Server.Station.Components;
  16. using Content.Server.Station.Events;
  17. using Content.Server.Station.Systems;
  18. using Content.Shared.Administration;
  19. using Content.Shared.CCVar;
  20. using Content.Shared.Damage.Components;
  21. using Content.Shared.DeviceNetwork;
  22. using Content.Shared.GameTicking;
  23. using Content.Shared.Mobs.Components;
  24. using Content.Shared.Movement.Components;
  25. using Content.Shared.Parallax.Biomes;
  26. using Content.Shared.Salvage;
  27. using Content.Shared.Shuttles.Components;
  28. using Content.Shared.Tiles;
  29. using Robust.Server.GameObjects;
  30. using Robust.Shared.Collections;
  31. using Robust.Shared.Configuration;
  32. using Robust.Shared.Console;
  33. using Robust.Shared.EntitySerialization;
  34. using Robust.Shared.EntitySerialization.Systems;
  35. using Robust.Shared.Map;
  36. using Robust.Shared.Map.Components;
  37. using Robust.Shared.Player;
  38. using Robust.Shared.Prototypes;
  39. using Robust.Shared.Random;
  40. using Robust.Shared.Timing;
  41. using Robust.Shared.Utility;
  42. using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
  43. namespace Content.Server.Shuttles.Systems;
  44. /// <summary>
  45. /// If enabled spawns players on a separate arrivals station before they can transfer to the main station.
  46. /// </summary>
  47. public sealed class ArrivalsSystem : EntitySystem
  48. {
  49. [Dependency] private readonly IChatManager _chat = default!;
  50. [Dependency] private readonly IConfigurationManager _cfgManager = default!;
  51. [Dependency] private readonly IConsoleHost _console = default!;
  52. [Dependency] private readonly IGameTiming _timing = default!;
  53. [Dependency] private readonly IPrototypeManager _protoManager = default!;
  54. [Dependency] private readonly IRobustRandom _random = default!;
  55. [Dependency] private readonly ActorSystem _actor = default!;
  56. [Dependency] private readonly BiomeSystem _biomes = default!;
  57. [Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
  58. [Dependency] private readonly GameTicker _ticker = default!;
  59. [Dependency] private readonly MapLoaderSystem _loader = default!;
  60. [Dependency] private readonly MetaDataSystem _metaData = default!;
  61. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  62. [Dependency] private readonly SharedTransformSystem _transform = default!;
  63. [Dependency] private readonly ShuttleSystem _shuttles = default!;
  64. [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
  65. [Dependency] private readonly StationSystem _station = default!;
  66. private EntityQuery<PendingClockInComponent> _pendingQuery;
  67. private EntityQuery<ArrivalsBlacklistComponent> _blacklistQuery;
  68. private EntityQuery<MobStateComponent> _mobQuery;
  69. /// <summary>
  70. /// If enabled then spawns players on an alternate map so they can take a shuttle to the station.
  71. /// </summary>
  72. public bool Enabled { get; private set; }
  73. /// <summary>
  74. /// Flags if all players spawning at the departure terminal have godmode until they leave the terminal.
  75. /// </summary>
  76. public bool ArrivalsGodmode { get; private set; }
  77. /// <summary>
  78. /// The first arrival is a little early, to save everyone 10s
  79. /// </summary>
  80. private const float RoundStartFTLDuration = 10f;
  81. private readonly List<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
  82. {
  83. "Grasslands",
  84. "LowDesert",
  85. "Snow",
  86. };
  87. public override void Initialize()
  88. {
  89. base.Initialize();
  90. SubscribeLocalEvent<PlayerSpawningEvent>(HandlePlayerSpawning, before: new []{ typeof(SpawnPointSystem)}, after: new [] { typeof(ContainerSpawnPointSystem)});
  91. SubscribeLocalEvent<StationArrivalsComponent, StationPostInitEvent>(OnStationPostInit);
  92. SubscribeLocalEvent<ArrivalsShuttleComponent, ComponentStartup>(OnShuttleStartup);
  93. SubscribeLocalEvent<ArrivalsShuttleComponent, FTLTagEvent>(OnShuttleTag);
  94. SubscribeLocalEvent<RoundStartingEvent>(OnRoundStarting);
  95. SubscribeLocalEvent<ArrivalsShuttleComponent, FTLStartedEvent>(OnArrivalsFTL);
  96. SubscribeLocalEvent<ArrivalsShuttleComponent, FTLCompletedEvent>(OnArrivalsDocked);
  97. SubscribeLocalEvent<PlayerSpawnCompleteEvent>(SendDirections);
  98. _pendingQuery = GetEntityQuery<PendingClockInComponent>();
  99. _blacklistQuery = GetEntityQuery<ArrivalsBlacklistComponent>();
  100. _mobQuery = GetEntityQuery<MobStateComponent>();
  101. // Don't invoke immediately as it will get set in the natural course of things.
  102. Enabled = _cfgManager.GetCVar(CCVars.ArrivalsShuttles);
  103. ArrivalsGodmode = _cfgManager.GetCVar(CCVars.GodmodeArrivals);
  104. _cfgManager.OnValueChanged(CCVars.ArrivalsShuttles, SetArrivals);
  105. _cfgManager.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b);
  106. // Command so admins can set these for funsies
  107. _console.RegisterCommand("arrivals", ArrivalsCommand, ArrivalsCompletion);
  108. }
  109. private void OnShuttleTag(EntityUid uid, ArrivalsShuttleComponent component, ref FTLTagEvent args)
  110. {
  111. if (args.Handled)
  112. return;
  113. // Just saves mappers forgetting. (v2 boogaloo)
  114. args.Handled = true;
  115. args.Tag = "DockArrivals";
  116. }
  117. private CompletionResult ArrivalsCompletion(IConsoleShell shell, string[] args)
  118. {
  119. if (args.Length != 1)
  120. return CompletionResult.Empty;
  121. return new CompletionResult(new CompletionOption[]
  122. {
  123. // Enables and disable are separate comms in case you don't want to accidentally toggle it, compared to
  124. // returns which doesn't have an immediate effect
  125. new("enable", Loc.GetString("cmd-arrivals-enable-hint")),
  126. new("disable", Loc.GetString("cmd-arrivals-disable-hint")),
  127. new("returns", Loc.GetString("cmd-arrivals-returns-hint")),
  128. new ("force", Loc.GetString("cmd-arrivals-force-hint"))
  129. }, "Option");
  130. }
  131. [AdminCommand(AdminFlags.Fun)]
  132. private void ArrivalsCommand(IConsoleShell shell, string argstr, string[] args)
  133. {
  134. if (args.Length != 1)
  135. {
  136. shell.WriteError(Loc.GetString("cmd-arrivals-invalid"));
  137. return;
  138. }
  139. switch (args[0])
  140. {
  141. case "enable":
  142. _cfgManager.SetCVar(CCVars.ArrivalsShuttles, true);
  143. break;
  144. case "disable":
  145. _cfgManager.SetCVar(CCVars.ArrivalsShuttles, false);
  146. break;
  147. case "returns":
  148. var existing = _cfgManager.GetCVar(CCVars.ArrivalsReturns);
  149. _cfgManager.SetCVar(CCVars.ArrivalsReturns, !existing);
  150. shell.WriteLine(Loc.GetString("cmd-arrivals-returns", ("value", !existing)));
  151. break;
  152. case "force":
  153. var query = AllEntityQuery<PendingClockInComponent, TransformComponent>();
  154. var spawnPoints = EntityQuery<SpawnPointComponent, TransformComponent>().ToList();
  155. TryGetArrivals(out var arrivalsUid);
  156. while (query.MoveNext(out var uid, out _, out var pendingXform))
  157. {
  158. _random.Shuffle(spawnPoints);
  159. foreach (var (point, xform) in spawnPoints)
  160. {
  161. if (point.SpawnType != SpawnPointType.LateJoin || xform.GridUid == arrivalsUid)
  162. continue;
  163. _transform.SetCoordinates(uid, pendingXform, xform.Coordinates);
  164. break;
  165. }
  166. RemCompDeferred<AutoOrientComponent>(uid);
  167. RemCompDeferred<PendingClockInComponent>(uid);
  168. shell.WriteLine(Loc.GetString("cmd-arrivals-forced", ("uid", ToPrettyString(uid))));
  169. }
  170. break;
  171. default:
  172. shell.WriteError(Loc.GetString($"cmd-arrivals-invalid"));
  173. break;
  174. }
  175. }
  176. /// <summary>
  177. /// First sends shuttle timer data, then kicks people off the shuttle if it isn't leaving the arrivals terminal
  178. /// </summary>
  179. private void OnArrivalsFTL(EntityUid shuttleUid, ArrivalsShuttleComponent component, ref FTLStartedEvent args)
  180. {
  181. if (!TryGetArrivals(out EntityUid arrivals))
  182. return;
  183. if (TryComp<DeviceNetworkComponent>(shuttleUid, out var netComp))
  184. {
  185. TryComp<FTLComponent>(shuttleUid, out var ftlComp);
  186. var ftlTime = TimeSpan.FromSeconds(ftlComp?.TravelTime ?? _shuttles.DefaultTravelTime);
  187. var payload = new NetworkPayload
  188. {
  189. [ShuttleTimerMasks.ShuttleMap] = shuttleUid,
  190. [ShuttleTimerMasks.ShuttleTime] = ftlTime
  191. };
  192. // unfortunate levels of spaghetti due to roundstart arrivals ftl behavior
  193. EntityUid? sourceMap;
  194. var arrivalsDelay = _cfgManager.GetCVar(CCVars.ArrivalsCooldown);
  195. if (component.FirstRun)
  196. {
  197. var station = _station.GetLargestGrid(Comp<StationDataComponent>(component.Station));
  198. sourceMap = station == null ? null : Transform(station.Value)?.MapUid;
  199. arrivalsDelay += RoundStartFTLDuration;
  200. component.FirstRun = false;
  201. payload.Add(ShuttleTimerMasks.DestMap, Transform(args.TargetCoordinates.EntityId).MapUid);
  202. payload.Add(ShuttleTimerMasks.DestTime, ftlTime);
  203. }
  204. else
  205. sourceMap = args.FromMapUid;
  206. payload.Add(ShuttleTimerMasks.SourceMap, sourceMap);
  207. payload.Add(ShuttleTimerMasks.SourceTime, ftlTime + TimeSpan.FromSeconds(arrivalsDelay));
  208. _deviceNetworkSystem.QueuePacket(shuttleUid, null, payload, netComp.TransmitFrequency);
  209. }
  210. // Don't do anything here when leaving arrivals.
  211. var arrivalsMapUid = Transform(arrivals).MapUid;
  212. if (args.FromMapUid == arrivalsMapUid)
  213. return;
  214. // Any mob then yeet them off the shuttle.
  215. if (!_cfgManager.GetCVar(CCVars.ArrivalsReturns) && args.FromMapUid != null)
  216. DumpChildren(shuttleUid, ref args);
  217. var pendingQuery = AllEntityQuery<PendingClockInComponent, TransformComponent>();
  218. // We're heading from the station back to arrivals (if leaving arrivals, would have returned above).
  219. // Process everyone who holds a PendingClockInComponent
  220. // Note, due to way DumpChildren works, anyone who doesn't have a PendingClockInComponent gets left in space
  221. // and will not warp. This is intended behavior.
  222. while (pendingQuery.MoveNext(out var pUid, out _, out var xform))
  223. {
  224. if (xform.GridUid == shuttleUid)
  225. {
  226. // Warp all players who are still on this shuttle to a spawn point. This doesn't let them return to
  227. // arrivals. It also ensures noobs, slow players or AFK players safely leave the shuttle.
  228. TryTeleportToMapSpawn(pUid, component.Station, xform);
  229. }
  230. // Players who have remained at arrivals keep their warp coupon (PendingClockInComponent) for now.
  231. if (xform.MapUid == arrivalsMapUid)
  232. continue;
  233. // The player has successfully left arrivals and is also not on the shuttle. Remove their warp coupon.
  234. RemCompDeferred<PendingClockInComponent>(pUid);
  235. RemCompDeferred<AutoOrientComponent>(pUid);
  236. if (ArrivalsGodmode)
  237. RemCompDeferred<GodmodeComponent>(pUid);
  238. }
  239. }
  240. private void OnArrivalsDocked(EntityUid uid, ArrivalsShuttleComponent component, ref FTLCompletedEvent args)
  241. {
  242. var dockTime = component.NextTransfer - _timing.CurTime + TimeSpan.FromSeconds(_shuttles.DefaultStartupTime);
  243. if (TryComp<DeviceNetworkComponent>(uid, out var netComp))
  244. {
  245. var payload = new NetworkPayload
  246. {
  247. [ShuttleTimerMasks.ShuttleMap] = uid,
  248. [ShuttleTimerMasks.ShuttleTime] = dockTime,
  249. [ShuttleTimerMasks.SourceMap] = args.MapUid,
  250. [ShuttleTimerMasks.SourceTime] = dockTime,
  251. [ShuttleTimerMasks.Docked] = true
  252. };
  253. _deviceNetworkSystem.QueuePacket(uid, null, payload, netComp.TransmitFrequency);
  254. }
  255. }
  256. private void DumpChildren(EntityUid uid, ref FTLStartedEvent args)
  257. {
  258. var toDump = new List<Entity<TransformComponent>>();
  259. FindDumpChildren(uid, toDump);
  260. foreach (var (ent, xform) in toDump)
  261. {
  262. var rotation = xform.LocalRotation;
  263. _transform.SetCoordinates(ent, new EntityCoordinates(args.FromMapUid!.Value, Vector2.Transform(xform.LocalPosition, args.FTLFrom)));
  264. _transform.SetWorldRotation(ent, args.FromRotation + rotation);
  265. if (_actor.TryGetSession(ent, out var session))
  266. {
  267. _chat.DispatchServerMessage(session!, Loc.GetString("latejoin-arrivals-dumped-from-shuttle"));
  268. }
  269. }
  270. }
  271. private void FindDumpChildren(EntityUid uid, List<Entity<TransformComponent>> toDump)
  272. {
  273. if (_pendingQuery.HasComponent(uid))
  274. return;
  275. var xform = Transform(uid);
  276. if (_mobQuery.HasComponent(uid) || _blacklistQuery.HasComponent(uid))
  277. {
  278. toDump.Add((uid, xform));
  279. return;
  280. }
  281. var children = xform.ChildEnumerator;
  282. while (children.MoveNext(out var child))
  283. {
  284. FindDumpChildren(child, toDump);
  285. }
  286. }
  287. public void HandlePlayerSpawning(PlayerSpawningEvent ev)
  288. {
  289. if (ev.SpawnResult != null)
  290. return;
  291. // We use arrivals as the default spawn so don't check for job prio.
  292. // Only works on latejoin even if enabled.
  293. if (!Enabled || _ticker.RunLevel != GameRunLevel.InRound)
  294. return;
  295. if (!HasComp<StationArrivalsComponent>(ev.Station))
  296. return;
  297. TryGetArrivals(out var arrivals);
  298. if (!TryComp(arrivals, out TransformComponent? arrivalsXform))
  299. return;
  300. var mapId = arrivalsXform.MapID;
  301. var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
  302. var possiblePositions = new List<EntityCoordinates>();
  303. while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
  304. {
  305. if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId)
  306. continue;
  307. possiblePositions.Add(xform.Coordinates);
  308. }
  309. if (possiblePositions.Count <= 0)
  310. return;
  311. var spawnLoc = _random.Pick(possiblePositions);
  312. ev.SpawnResult = _stationSpawning.SpawnPlayerMob(
  313. spawnLoc,
  314. ev.Job,
  315. ev.HumanoidCharacterProfile,
  316. ev.Station);
  317. EnsureComp<PendingClockInComponent>(ev.SpawnResult.Value);
  318. EnsureComp<AutoOrientComponent>(ev.SpawnResult.Value);
  319. // If you're forced to spawn, you're invincible until you leave wherever you were forced to spawn.
  320. if (ArrivalsGodmode)
  321. EnsureComp<GodmodeComponent>(ev.SpawnResult.Value);
  322. }
  323. private void SendDirections(PlayerSpawnCompleteEvent ev)
  324. {
  325. if (!Enabled || !ev.LateJoin || ev.Silent || !_pendingQuery.HasComp(ev.Mob))
  326. return;
  327. var arrival = NextShuttleArrival();
  328. var message = arrival is not null
  329. ? Loc.GetString("latejoin-arrivals-direction-time", ("time", $"{arrival:mm\\:ss}"))
  330. : Loc.GetString("latejoin-arrivals-direction");
  331. _chat.DispatchServerMessage(ev.Player, message);
  332. }
  333. private bool TryTeleportToMapSpawn(EntityUid player, EntityUid stationId, TransformComponent? transform = null)
  334. {
  335. if (!Resolve(player, ref transform))
  336. return false;
  337. var points = EntityQueryEnumerator<SpawnPointComponent, TransformComponent>();
  338. var possiblePositions = new ValueList<EntityCoordinates>(32);
  339. // Find a spawnpoint on the same map as the player is already docked with now.
  340. while (points.MoveNext(out var uid, out var spawnPoint, out var xform))
  341. {
  342. if (spawnPoint.SpawnType == SpawnPointType.LateJoin &&
  343. _station.GetOwningStation(uid, xform) == stationId)
  344. {
  345. // Add to list of possible spawn locations
  346. possiblePositions.Add(xform.Coordinates);
  347. }
  348. }
  349. if (possiblePositions.Count > 0)
  350. {
  351. // Move the player to a random late-join spawnpoint.
  352. _transform.SetCoordinates(player, transform, _random.Pick(possiblePositions));
  353. if (_actor.TryGetSession(player, out var session))
  354. {
  355. _chat.DispatchServerMessage(session!, Loc.GetString("latejoin-arrivals-teleport-to-spawn"));
  356. }
  357. return true;
  358. }
  359. return false;
  360. }
  361. private void OnShuttleStartup(EntityUid uid, ArrivalsShuttleComponent component, ComponentStartup args)
  362. {
  363. EnsureComp<PreventPilotComponent>(uid);
  364. }
  365. private bool TryGetArrivals(out EntityUid uid)
  366. {
  367. var arrivalsQuery = EntityQueryEnumerator<ArrivalsSourceComponent>();
  368. while (arrivalsQuery.MoveNext(out uid, out _))
  369. {
  370. return true;
  371. }
  372. return false;
  373. }
  374. public TimeSpan? NextShuttleArrival()
  375. {
  376. var query = EntityQueryEnumerator<ArrivalsShuttleComponent>();
  377. var time = TimeSpan.MaxValue;
  378. while (query.MoveNext(out var uid, out var comp))
  379. {
  380. if (comp.NextArrivalsTime < time)
  381. time = comp.NextArrivalsTime;
  382. }
  383. var duration = _timing.CurTime;
  384. return (time < duration) ? null : time - duration;
  385. }
  386. public override void Update(float frameTime)
  387. {
  388. base.Update(frameTime);
  389. var query = EntityQueryEnumerator<ArrivalsShuttleComponent, ShuttleComponent, TransformComponent>();
  390. var curTime = _timing.CurTime;
  391. TryGetArrivals(out var arrivals);
  392. if (TryComp(arrivals, out TransformComponent? arrivalsXform))
  393. {
  394. while (query.MoveNext(out var uid, out var comp, out var shuttle, out var xform))
  395. {
  396. if (comp.NextTransfer > curTime || !TryComp<StationDataComponent>(comp.Station, out var data))
  397. continue;
  398. var tripTime = _shuttles.DefaultTravelTime + _shuttles.DefaultStartupTime;
  399. // Go back to arrivals source
  400. if (xform.MapUid != arrivalsXform.MapUid)
  401. {
  402. if (arrivals.IsValid())
  403. _shuttles.FTLToDock(uid, shuttle, arrivals);
  404. comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(tripTime);
  405. }
  406. // Go to station
  407. else
  408. {
  409. var targetGrid = _station.GetLargestGrid(data);
  410. if (targetGrid != null)
  411. _shuttles.FTLToDock(uid, shuttle, targetGrid.Value);
  412. // The ArrivalsCooldown includes the trip there, so we only need to add the time taken for
  413. // the trip back.
  414. comp.NextArrivalsTime = _timing.CurTime + TimeSpan.FromSeconds(
  415. _cfgManager.GetCVar(CCVars.ArrivalsCooldown) + tripTime);
  416. }
  417. comp.NextTransfer += TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
  418. }
  419. }
  420. }
  421. private void OnRoundStarting(RoundStartingEvent ev)
  422. {
  423. // Setup arrivals station
  424. if (!Enabled)
  425. return;
  426. SetupArrivalsStation();
  427. }
  428. private void SetupArrivalsStation()
  429. {
  430. var path = new ResPath(_cfgManager.GetCVar(CCVars.ArrivalsMap));
  431. _mapSystem.CreateMap(out var mapId, runMapInit: false);
  432. var mapUid = _mapSystem.GetMap(mapId);
  433. if (!_loader.TryLoadGrid(mapId, path, out var grid))
  434. return;
  435. _metaData.SetEntityName(mapUid, Loc.GetString("map-name-terminal"));
  436. EnsureComp<ArrivalsSourceComponent>(grid.Value);
  437. EnsureComp<ProtectedGridComponent>(grid.Value);
  438. EnsureComp<PreventPilotComponent>(grid.Value);
  439. // Setup planet arrivals if relevant
  440. if (_cfgManager.GetCVar(CCVars.ArrivalsPlanet))
  441. {
  442. var template = _random.Pick(_arrivalsBiomeOptions);
  443. _biomes.EnsurePlanet(mapUid, _protoManager.Index(template));
  444. var restricted = new RestrictedRangeComponent
  445. {
  446. Range = 32f
  447. };
  448. AddComp(mapUid, restricted);
  449. }
  450. _mapSystem.InitializeMap(mapId);
  451. // Handle roundstart stations.
  452. var query = AllEntityQuery<StationArrivalsComponent>();
  453. while (query.MoveNext(out var uid, out var comp))
  454. {
  455. SetupShuttle(uid, comp);
  456. }
  457. }
  458. private void SetArrivals(bool obj)
  459. {
  460. Enabled = obj;
  461. if (Enabled)
  462. {
  463. SetupArrivalsStation();
  464. var query = AllEntityQuery<StationArrivalsComponent>();
  465. while (query.MoveNext(out var sUid, out var comp))
  466. {
  467. SetupShuttle(sUid, comp);
  468. }
  469. }
  470. else
  471. {
  472. var sourceQuery = AllEntityQuery<ArrivalsSourceComponent>();
  473. while (sourceQuery.MoveNext(out var uid, out _))
  474. {
  475. QueueDel(uid);
  476. }
  477. var shuttleQuery = AllEntityQuery<ArrivalsShuttleComponent>();
  478. while (shuttleQuery.MoveNext(out var uid, out _))
  479. {
  480. QueueDel(uid);
  481. }
  482. }
  483. }
  484. private void OnStationPostInit(EntityUid uid, StationArrivalsComponent component, ref StationPostInitEvent args)
  485. {
  486. if (!Enabled)
  487. return;
  488. // If it's a latespawn station then this will fail but that's okey
  489. SetupShuttle(uid, component);
  490. }
  491. private void SetupShuttle(EntityUid uid, StationArrivalsComponent component)
  492. {
  493. if (!Deleted(component.Shuttle))
  494. return;
  495. // Spawn arrivals on a dummy map then dock it to the source.
  496. var dummpMapEntity = _mapSystem.CreateMap(out var dummyMapId);
  497. if (TryGetArrivals(out var arrivals) &&
  498. _loader.TryLoadGrid(dummyMapId, component.ShuttlePath, out var shuttle))
  499. {
  500. component.Shuttle = shuttle.Value;
  501. var shuttleComp = Comp<ShuttleComponent>(component.Shuttle);
  502. var arrivalsComp = EnsureComp<ArrivalsShuttleComponent>(component.Shuttle);
  503. arrivalsComp.Station = uid;
  504. EnsureComp<ProtectedGridComponent>(uid);
  505. _shuttles.FTLToDock(component.Shuttle, shuttleComp, arrivals, hyperspaceTime: RoundStartFTLDuration);
  506. arrivalsComp.NextTransfer = _timing.CurTime + TimeSpan.FromSeconds(_cfgManager.GetCVar(CCVars.ArrivalsCooldown));
  507. }
  508. // Don't start the arrivals shuttle immediately docked so power has a time to stabilise?
  509. var timer = AddComp<TimedDespawnComponent>(dummpMapEntity);
  510. timer.Lifetime = 15f;
  511. }
  512. }