using Content.Shared.CCVar; using Content.Shared.Damage.Components; using Content.Shared.GameTicking; using Content.Shared.Movement.Components; using Content.Shared.Shuttles.Components; using Content.Shared.Spawners.Components; using Content.Shared.Spawners.EntitySystems; using Content.Shared.Station.Systems; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Random; using Robust.Shared.Timing; namespace Content.Shared.Shuttles.Systems; /// /// Handles the arrivals shuttle spawning system, and the arrivals shuttle. /// public abstract partial class ArrivalsSystem : EntitySystem { [Dependency] protected IConfigurationManager Cfg = default!; [Dependency] protected IGameTiming Timing = default!; [Dependency] protected IRobustRandom Random = default!; [Dependency] protected GameTicker Ticker = default!; [Dependency] private StationSpawningSystem _stationSpawning = default!; /// /// If enabled then spawns players on an alternate map so they can take a shuttle to the station. /// public bool Enabled { get; protected set; } /// /// Flags if all players spawning at the departure terminal have godmode until they leave the terminal. /// public bool ArrivalsGodmode { get; private set; } public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnShuttleStartup); // Don't invoke immediately as it will get set in the natural course of things. Enabled = Cfg.GetCVar(CCVars.ArrivalsShuttles); ArrivalsGodmode = Cfg.GetCVar(CCVars.GodmodeArrivals); Cfg.OnValueChanged(CCVars.GodmodeArrivals, b => ArrivalsGodmode = b); } [SubscribeLocalEvent(before: new []{typeof(SpawnPointSystem)}, after: new []{typeof(ContainerSpawnPointSystem)})] public void HandlePlayerSpawning(PlayerSpawningEvent ev) { if (ev.SpawnResult != null) return; // We use arrivals as the default spawn so don't check for job prio. // Only works on latejoin even if enabled. if (!Enabled || Ticker.RunLevel != GameRunLevel.InRound) return; if (!HasComp(ev.Station)) return; TryGetArrivals(out var arrivals); if (!TryComp(arrivals, out TransformComponent? arrivalsXform)) return; var mapId = arrivalsXform.MapID; var points = EntityQueryEnumerator(); var possiblePositions = new List(); while (points.MoveNext(out _, out var spawnPoint, out var xform)) { if (spawnPoint.SpawnType != SpawnPointType.LateJoin || xform.MapID != mapId) continue; possiblePositions.Add(xform.Coordinates); } if (possiblePositions.Count <= 0) return; var spawnLoc = Random.Pick(possiblePositions); ev.SpawnResult = _stationSpawning.SpawnPlayerMob( spawnLoc, ev.Job, ev.HumanoidCharacterProfile, ev.Station); EnsureComp(ev.SpawnResult.Value); EnsureComp(ev.SpawnResult.Value); // If you're forced to spawn, you're invincible until you leave wherever you were forced to spawn. if (ArrivalsGodmode) EnsureComp(ev.SpawnResult.Value); } private void OnShuttleStartup(EntityUid uid, ArrivalsShuttleComponent component, ComponentStartup args) { EnsureComp(uid); } [PublicAPI] public bool TryGetArrivals(out EntityUid uid) { var arrivalsQuery = EntityQueryEnumerator(); while (arrivalsQuery.MoveNext(out uid, out _)) { return true; } return false; } /// /// Check if an entity is on the arrivals grid. /// /// Entity to check. /// True if the entity is on the arrivals grid. Returns false if not on arrivals, or there is no arrivals grid. [PublicAPI] public bool IsOnArrivals(Entity entity) { if (!Resolve(entity, ref entity.Comp)) return false; if (!TryGetArrivals(out var arrivals)) return false; var arrivalsGridUid = Transform(arrivals).GridUid; if (!arrivalsGridUid.HasValue) return false; return entity.Comp.GridUid == Transform(arrivals).GridUid; } public TimeSpan? NextShuttleArrival() { var query = EntityQueryEnumerator(); var time = TimeSpan.MaxValue; while (query.MoveNext(out _, out var comp)) { if (comp.NextArrivalsTime < time) time = comp.NextArrivalsTime; } var duration = Timing.CurTime; return (time < duration) ? null : time - duration; } }