using Content.Server.Screens.Components; using Content.Shared.DeviceNetwork.Components; using Content.Shared.DeviceNetwork.Events; using Content.Shared.RoundEnd; using Content.Shared.Screens; using Content.Shared.TextScreen; using Robust.Shared.Timing; namespace Content.Server.Screens.Systems; /// /// Controls the wallmounted screens on stations and shuttles displaying e.g. FTL duration, ETA /// public sealed partial class ScreenSystem : EntitySystem { [Dependency] private IGameTiming _gameTiming = default!; [Dependency] private SharedAppearanceSystem _appearance = default!; [Dependency] private EntityQuery _appearanceQuery; /// /// Send a text update to every screen on the same map as the originating device. /// [SubscribeLocalEvent] private void OnScreenText(Entity ent, ref DeviceNetworkPacketEvent args) { var text = args.Data.Text; if (text == null) return; if (!_appearanceQuery.TryComp(ent, out var appearance)) return; // don't allow text updates if there's an active timer // (and just check here so the server doesn't have to track them) if (_appearance.TryGetData(ent, TextScreenVisuals.TargetTime, out TimeSpan target, appearance) && target > _gameTiming.CurTime) return; var screenMap = Transform(ent).MapUid; if (screenMap == null) return; _appearance.SetData(ent, TextScreenVisuals.ScreenText, text, appearance); _appearance.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime, appearance); } /// /// Determines if/how a timer packet affects this screen. /// /// /// Currently there are 2 broadcast domains: Arrivals, and every other screen. /// Domain is determined by the on each timer. /// Each broadcast domain is divided into subnets. Screen MapUid determines subnet. /// Subnets are the shuttle, source, and dest. Source/dest change each jump. /// This is required to send different timers to the shuttle/terminal/station. /// [SubscribeLocalEvent] private void OnShuttleTimer(Entity ent, ref DeviceNetworkPacketEvent args) { var payload = args.Data; var timerXform = Transform(ent); // no false positives. if (timerXform.MapUid == null) return; // Need to have have the AppearanceComponent to do anything. if (!_appearanceQuery.TryComp(ent, out var appearance)) return; var text = payload.OverrideText; TimeSpan time; switch (timerXform.MapUid) { // sometimes the timer transforms on FTL shuttles have a hyperspace mapent, so matching by grid works as a fallback. case var local when local == payload.Shuttle || timerXform.GridUid == payload.Shuttle: time = payload.ShuttleTime; text ??= GetShuttleScreenText(ref args); break; case var origin when origin == payload.SourceMap: time = payload.SourceTime; text ??= GetShuttleScreenText(ref args); break; case var remote when remote == payload.DestinationMap: time = payload.DestinationTime; text ??= ShuttleTimerMasks.ETA; break; default: return; } _appearance.SetData(ent, TextScreenVisuals.TargetTime, _gameTiming.CurTime + time, appearance); _appearance.SetData(ent, TextScreenVisuals.ScreenTextTime, _gameTiming.CurTime, appearance); _appearance.SetData(ent, TextScreenVisuals.ScreenText, text, appearance); if (payload.OverrideColor != null) _appearance.SetData(ent, TextScreenVisuals.Color, payload.OverrideColor, appearance); } /// /// Returns the localization key for ETA or ETD, depending on if the shuttle is currently docked. /// private string GetShuttleScreenText(ref DeviceNetworkPacketEvent args) { return args.Data.Docked ? ShuttleTimerMasks.ETD : ShuttleTimerMasks.ETA; } }