1
0

SignalTimerSystem.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using Content.Server.DeviceLinking.Components;
  2. using Content.Server.DeviceLinking.Events;
  3. using Content.Shared.UserInterface;
  4. using Content.Shared.Access.Systems;
  5. using Content.Shared.MachineLinking;
  6. using Content.Shared.TextScreen;
  7. using Robust.Server.GameObjects;
  8. using Robust.Shared.Audio.Systems;
  9. using Robust.Shared.Timing;
  10. namespace Content.Server.DeviceLinking.Systems;
  11. public sealed class SignalTimerSystem : EntitySystem
  12. {
  13. [Dependency] private readonly SharedAudioSystem _audio = default!;
  14. [Dependency] private readonly IGameTiming _gameTiming = default!;
  15. [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
  16. [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
  17. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  18. [Dependency] private readonly AccessReaderSystem _accessReader = default!;
  19. /// <summary>
  20. /// Per-tick timer cache.
  21. /// </summary>
  22. private List<Entity<SignalTimerComponent>> _timers = new();
  23. public override void Initialize()
  24. {
  25. base.Initialize();
  26. SubscribeLocalEvent<SignalTimerComponent, ComponentInit>(OnInit);
  27. SubscribeLocalEvent<SignalTimerComponent, AfterActivatableUIOpenEvent>(OnAfterActivatableUIOpen);
  28. SubscribeLocalEvent<SignalTimerComponent, SignalTimerTextChangedMessage>(OnTextChangedMessage);
  29. SubscribeLocalEvent<SignalTimerComponent, SignalTimerDelayChangedMessage>(OnDelayChangedMessage);
  30. SubscribeLocalEvent<SignalTimerComponent, SignalTimerStartMessage>(OnTimerStartMessage);
  31. SubscribeLocalEvent<SignalTimerComponent, SignalReceivedEvent>(OnSignalReceived);
  32. }
  33. private void OnInit(EntityUid uid, SignalTimerComponent component, ComponentInit args)
  34. {
  35. _appearanceSystem.SetData(uid, TextScreenVisuals.DefaultText, component.Label);
  36. _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
  37. _signalSystem.EnsureSinkPorts(uid, component.Trigger);
  38. }
  39. private void OnAfterActivatableUIOpen(EntityUid uid, SignalTimerComponent component, AfterActivatableUIOpenEvent args)
  40. {
  41. var time = TryComp<ActiveSignalTimerComponent>(uid, out var active) ? active.TriggerTime : TimeSpan.Zero;
  42. if (_ui.HasUi(uid, SignalTimerUiKey.Key))
  43. {
  44. _ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(component.Label,
  45. TimeSpan.FromSeconds(component.Delay).Minutes.ToString("D2"),
  46. TimeSpan.FromSeconds(component.Delay).Seconds.ToString("D2"),
  47. component.CanEditLabel,
  48. time,
  49. active != null,
  50. _accessReader.IsAllowed(args.User, uid)));
  51. }
  52. }
  53. /// <summary>
  54. /// Finishes a timer, triggering its main port, and removing its <see cref="ActiveSignalTimerComponent"/>.
  55. /// </summary>
  56. public void Trigger(EntityUid uid, SignalTimerComponent signalTimer)
  57. {
  58. RemComp<ActiveSignalTimerComponent>(uid);
  59. _audio.PlayPvs(signalTimer.DoneSound, uid);
  60. _signalSystem.InvokePort(uid, signalTimer.TriggerPort);
  61. if (_ui.HasUi(uid, SignalTimerUiKey.Key))
  62. {
  63. _ui.SetUiState(uid, SignalTimerUiKey.Key, new SignalTimerBoundUserInterfaceState(signalTimer.Label,
  64. TimeSpan.FromSeconds(signalTimer.Delay).Minutes.ToString("D2"),
  65. TimeSpan.FromSeconds(signalTimer.Delay).Seconds.ToString("D2"),
  66. signalTimer.CanEditLabel,
  67. TimeSpan.Zero,
  68. false,
  69. true));
  70. }
  71. }
  72. public override void Update(float frameTime)
  73. {
  74. base.Update(frameTime);
  75. UpdateTimer();
  76. }
  77. private void UpdateTimer()
  78. {
  79. _timers.Clear();
  80. var query = EntityQueryEnumerator<ActiveSignalTimerComponent, SignalTimerComponent>();
  81. while (query.MoveNext(out var uid, out var active, out var timer))
  82. {
  83. if (active.TriggerTime > _gameTiming.CurTime)
  84. continue;
  85. _timers.Add((uid, timer));
  86. }
  87. foreach (var timer in _timers)
  88. {
  89. // Exploded or the likes.
  90. if (!Exists(timer.Owner))
  91. continue;
  92. Trigger(timer.Owner, timer.Comp);
  93. }
  94. }
  95. /// <summary>
  96. /// Checks if a UI <paramref name="message"/> is allowed to be sent by the user.
  97. /// </summary>
  98. /// <param name="uid">The entity that is interacted with.</param>
  99. private bool IsMessageValid(EntityUid uid, BoundUserInterfaceMessage message)
  100. {
  101. if (!_accessReader.IsAllowed(message.Actor, uid))
  102. return false;
  103. return true;
  104. }
  105. /// <summary>
  106. /// Called by <see cref="SignalTimerTextChangedMessage"/> to both
  107. /// change the default component label, and propagate that change to the TextScreen.
  108. /// </summary>
  109. private void OnTextChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerTextChangedMessage args)
  110. {
  111. if (!IsMessageValid(uid, args))
  112. return;
  113. component.Label = args.Text[..Math.Min(component.MaxLength, args.Text.Length)];
  114. if (!HasComp<ActiveSignalTimerComponent>(uid))
  115. {
  116. // could maybe move the defaulttext update out of this block,
  117. // if you delved deep into appearance update batching
  118. _appearanceSystem.SetData(uid, TextScreenVisuals.DefaultText, component.Label);
  119. _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, component.Label);
  120. }
  121. }
  122. /// <summary>
  123. /// Called by <see cref="SignalTimerDelayChangedMessage"/> to change the <see cref="SignalTimerComponent"/>
  124. /// delay, and propagate that change to a textscreen.
  125. /// </summary>
  126. private void OnDelayChangedMessage(EntityUid uid, SignalTimerComponent component, SignalTimerDelayChangedMessage args)
  127. {
  128. if (!IsMessageValid(uid, args))
  129. return;
  130. component.Delay = Math.Min(args.Delay.TotalSeconds, component.MaxDuration);
  131. _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, component.Delay);
  132. }
  133. /// <summary>
  134. /// Called by <see cref="SignalTimerStartMessage"/> to instantiate an <see cref="ActiveSignalTimerComponent"/>,
  135. /// clear <see cref="TextScreenVisuals.ScreenText"/>, propagate those changes, and invoke the start port.
  136. /// </summary>
  137. private void OnTimerStartMessage(EntityUid uid, SignalTimerComponent component, SignalTimerStartMessage args)
  138. {
  139. if (!IsMessageValid(uid, args))
  140. return;
  141. // feedback received: pressing the timer button while a timer is running should cancel the timer.
  142. if (HasComp<ActiveSignalTimerComponent>(uid))
  143. {
  144. _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, _gameTiming.CurTime);
  145. Trigger(uid, component);
  146. }
  147. else
  148. OnStartTimer(uid, component);
  149. }
  150. private void OnSignalReceived(EntityUid uid, SignalTimerComponent component, ref SignalReceivedEvent args)
  151. {
  152. if (args.Port == component.Trigger)
  153. {
  154. OnStartTimer(uid, component);
  155. }
  156. }
  157. public void OnStartTimer(EntityUid uid, SignalTimerComponent component)
  158. {
  159. TryComp<AppearanceComponent>(uid, out var appearance);
  160. var timer = EnsureComp<ActiveSignalTimerComponent>(uid);
  161. timer.TriggerTime = _gameTiming.CurTime + TimeSpan.FromSeconds(component.Delay);
  162. if (appearance != null)
  163. {
  164. _appearanceSystem.SetData(uid, TextScreenVisuals.TargetTime, timer.TriggerTime, appearance);
  165. _appearanceSystem.SetData(uid, TextScreenVisuals.ScreenText, string.Empty, appearance);
  166. }
  167. _signalSystem.InvokePort(uid, component.StartPort);
  168. }
  169. }