using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Events; using Content.Shared.UserInterface; using Content.Shared.Access.Systems; using Content.Shared.MachineLinking; using Content.Shared.TextScreen; using Robust.Server.GameObjects; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; namespace Content.Server.DeviceLinking.Systems; public sealed class SignalTimerSystem : EntitySystem { [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly UserInterfaceSystem _ui = default!; [Dependency] private readonly AccessReaderSystem _accessReader = default!; /// /// Per-tick timer cache. /// private List> _timers = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnAfterActivatableUIOpen); SubscribeLocalEvent(OnTextChangedMessage); SubscribeLocalEvent(OnDelayChangedMessage); SubscribeLocalEvent(OnTimerStartMessage); SubscribeLocalEvent(OnSignalReceived); } private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args) { _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label); _signalSystem.EnsureSinkPorts(uid, component.Trigger); } private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent component, AfterActivatableUIOpenEvent args) { var time = TryComp(uid, out var active) ? active.TriggerTime : TimeSpan.Zero; if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui)) { _ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(component.Label, TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"), TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"), component.CanEditLabel, time, active != null, _accessReader.IsAllowed(args.User, uid))); } } /// /// Finishes a timer, triggering its main port, and removing its . /// public void Trigger(EntityUid uid, SignalTimerComponent signalTimer) { RemComp(uid); if (TryComp(uid, out var appearance)) { _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, signalTimer.Label, appearance); } _audio.PlayPvs(signalTimer.DoneSound, uid); _signalSystem.InvokePort(uid, signalTimer.TriggerPort); if (_ui.TryGetUi(uid, SignalTimerUiKey.Key, out var bui)) { _ui.SetUiState(bui, new SignalTimerBoundUserInterfaceState(signalTimer.Label, TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"), TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"), signalTimer.CanEditLabel, TimeSpan.Zero, false, true)); } } public override void Update(float frameTime) { base.Update(frameTime); UpdateTimer(); } private void UpdateTimer() { _timers.Clear(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var active, out var timer)) { if (active.TriggerTime > _gameTiming.CurTime) continue; _timers.Add((uid, timer)); } foreach (var timer in _timers) { // Exploded or the likes. if (!Exists(timer.Owner)) continue; Trigger(timer.Owner, timer.Comp); } } /// /// Checks if a UI is allowed to be sent by the user. /// /// The entity that is interacted with. private bool IsMessageValid(EntityUid uid, BoundUserInterfaceMessage message) { if (message.Session.AttachedEntity is not { Valid: true } mob) return false; if (!_accessReader.IsAllowed(mob, uid)) return false; return true; } /// /// Called by to both /// change the default component label, and propagate that change to the TextScreen. /// private void OnTextChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerTextChangedMessage args) { if (!IsMessageValid(uid, args)) return; component.Label = args.Text[..Math.Min(5, args.Text.Length)]; if (!HasComp(uid)) _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label); } /// /// Called by to change the /// delay, and propagate that change to a textscreen. /// private void OnDelayChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerDelayChangedMessage args) { if (!IsMessageValid(uid, args)) return; component.Delay = args.Delay.TotalSeconds; _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, component.Delay); } /// /// Called by to instantiate an , /// clear , propagate those changes, and invoke the start port. /// private void OnTimerStartMessage(EntityUid uid, SignalTimerComponent component, SignalTimerStartMessage args) { if (!IsMessageValid(uid, args)) return; OnStartTimer(uid, component); } private void OnSignalReceived(EntityUid uid, SignalTimerComponent component, ref SignalReceivedEvent args) { if (args.Port == component.Trigger) { OnStartTimer(uid, component); } } public void OnStartTimer(EntityUid uid, SignalTimerComponent component) { TryComp(uid, out var appearance); var timer = EnsureComp(uid); timer.TriggerTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Delay); if (appearance != null) { _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, timer.TriggerTime, appearance); _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, string.Empty, appearance); } _signalSystem.InvokePort(uid, component.StartPort); } }