using Content.Shared.Trigger.Components; using Content.Shared.Trigger.Components.Triggers; using Content.Shared.Examine; using Content.Shared.Verbs; namespace Content.Shared.Trigger.Systems; public sealed partial class TriggerSystem { private void InitializeTimer() { SubscribeLocalEvent(OnRepeatInit); SubscribeLocalEvent(OnRandomInit); SubscribeLocalEvent(OnTimerShutdown); SubscribeLocalEvent(OnTimerExamined); SubscribeLocalEvent(OnTimerTriggered); SubscribeLocalEvent>(OnTimerGetAltVerbs); } // set the time of the first trigger after being spawned private void OnRepeatInit(Entity ent, ref MapInitEvent args) { ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay; Dirty(ent); } private void OnRandomInit(Entity ent, ref MapInitEvent args) { if (_net.IsClient) // Nextfloat will mispredict, so we set it on the server and dirty it return; if (!TryComp(ent, out var timerTriggerComp)) return; timerTriggerComp.Delay = TimeSpan.FromSeconds(_random.NextFloat(ent.Comp.Min, ent.Comp.Max)); Dirty(ent.Owner, timerTriggerComp); } private void OnTimerShutdown(Entity ent, ref ComponentShutdown args) { RemComp(ent); } private void OnTimerExamined(Entity ent, ref ExaminedEvent args) { if (args.IsInDetailsRange && ent.Comp.Examinable) args.PushText(Loc.GetString("timer-trigger-examine", ("time", ent.Comp.Delay.TotalSeconds))); } private void OnTimerTriggered(Entity ent, ref TriggerEvent args) { if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key)) return; args.Handled |= ActivateTimerTrigger(ent.AsNullable(), args.User); } /// /// Add an alt-click interaction that cycles through delays. /// private void OnTimerGetAltVerbs(Entity ent, ref GetVerbsEvent args) { if (!args.CanInteract || !args.CanAccess || args.Hands == null) return; if (ent.Comp.DelayOptions.Count <= 1) return; var user = args.User; args.Verbs.Add(new AlternativeVerb { Category = TimerOptions, Text = Loc.GetString("timer-trigger-verb-cycle"), Act = () => CycleDelay(ent, user), Priority = 1 }); foreach (var option in ent.Comp.DelayOptions) { if (MathHelper.CloseTo(option.TotalSeconds, ent.Comp.Delay.TotalSeconds)) { args.Verbs.Add(new AlternativeVerb { Category = TimerOptions, Text = Loc.GetString("timer-trigger-verb-set-current", ("time", option.TotalSeconds)), Disabled = true, Priority = -100 * (int)option.TotalSeconds }); } else { args.Verbs.Add(new AlternativeVerb { Category = TimerOptions, Text = Loc.GetString("timer-trigger-verb-set", ("time", option.TotalSeconds)), Priority = -100 * (int)option.TotalSeconds, Act = () => { ent.Comp.Delay = option; Dirty(ent); _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option.TotalSeconds)), user, user); } }); } } } public static readonly VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png"); /// /// Select the next entry from the DelayOptions. /// private void CycleDelay(Entity ent, EntityUid? user) { if (ent.Comp.DelayOptions.Count <= 1) return; // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short. ent.Comp.DelayOptions.Sort(); Dirty(ent); if (ent.Comp.DelayOptions[^1] <= ent.Comp.Delay) { ent.Comp.Delay = ent.Comp.DelayOptions[0]; _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", ent.Comp.Delay)), ent.Owner, user); return; } foreach (var option in ent.Comp.DelayOptions) { if (option > ent.Comp.Delay) { ent.Comp.Delay = option; _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option)), ent.Owner, user); return; } } } private void UpdateRepeat() { var curTime = _timing.CurTime; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (comp.NextTrigger > curTime) continue; comp.NextTrigger += comp.Delay; Dirty(uid, comp); Trigger(uid, null, comp.KeyOut); } } private void UpdateTimer() { var curTime = _timing.CurTime; var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out _, out var timer)) { if (_net.IsServer && timer.BeepSound != null && timer.NextBeep <= curTime) { _audio.PlayPvs(timer.BeepSound, uid); timer.NextBeep += timer.BeepInterval; } if (timer.NextTrigger <= curTime) { Trigger(uid, timer.User, timer.KeyOut); // Remove after triggering to prevent it from starting the timer again RemComp(uid); if (TryComp(uid, out var appearance)) _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance); } } } }