AnomalySynchronizerSystem.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using System.Linq;
  2. using System.Numerics;
  3. using Content.Server.Anomaly.Components;
  4. using Content.Server.DeviceLinking.Systems;
  5. using Content.Server.Power.Components;
  6. using Content.Server.Power.EntitySystems;
  7. using Content.Shared.Anomaly.Components;
  8. using Content.Shared.Examine;
  9. using Content.Shared.Interaction;
  10. using Content.Shared.Popups;
  11. using Content.Shared.Power;
  12. using Robust.Shared.Audio.Systems;
  13. using Content.Shared.Verbs;
  14. using Robust.Shared.Timing;
  15. namespace Content.Server.Anomaly;
  16. /// <summary>
  17. /// a device that allows you to translate anomaly activity into multitool signals.
  18. /// </summary>
  19. public sealed partial class AnomalySynchronizerSystem : EntitySystem
  20. {
  21. [Dependency] private readonly AnomalySystem _anomaly = default!;
  22. [Dependency] private readonly SharedAudioSystem _audio = default!;
  23. [Dependency] private readonly EntityLookupSystem _entityLookup = default!;
  24. [Dependency] private readonly DeviceLinkSystem _signal = default!;
  25. [Dependency] private readonly SharedTransformSystem _transform = default!;
  26. [Dependency] private readonly SharedPopupSystem _popup = default!;
  27. [Dependency] private readonly PowerReceiverSystem _power = default!;
  28. [Dependency] private readonly IGameTiming _timing = default!;
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<AnomalySynchronizerComponent, InteractHandEvent>(OnInteractHand);
  33. SubscribeLocalEvent<AnomalySynchronizerComponent, PowerChangedEvent>(OnPowerChanged);
  34. SubscribeLocalEvent<AnomalySynchronizerComponent, ExaminedEvent>(OnExamined);
  35. SubscribeLocalEvent<AnomalySynchronizerComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
  36. SubscribeLocalEvent<AnomalyPulseEvent>(OnAnomalyPulse);
  37. SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnAnomalySeverityChanged);
  38. SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
  39. }
  40. public override void Update(float frameTime)
  41. {
  42. base.Update(frameTime);
  43. var query = EntityQueryEnumerator<AnomalySynchronizerComponent, TransformComponent>();
  44. while (query.MoveNext(out var uid, out var sync, out var xform))
  45. {
  46. if (sync.ConnectedAnomaly is null)
  47. continue;
  48. if (_timing.CurTime < sync.NextCheckTime)
  49. continue;
  50. sync.NextCheckTime += sync.CheckFrequency;
  51. if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid)
  52. {
  53. DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
  54. continue;
  55. }
  56. if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance))
  57. continue;
  58. if (distance > sync.AttachRange)
  59. DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
  60. }
  61. }
  62. /// <summary>
  63. /// If powered, try to attach a nearby anomaly.
  64. /// </summary>
  65. public bool TryAttachNearbyAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid? user = null)
  66. {
  67. if (!_power.IsPowered(ent))
  68. {
  69. if (user is not null)
  70. _popup.PopupEntity(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent)), ent, user.Value);
  71. return false;
  72. }
  73. var coords = _transform.GetMapCoordinates(ent);
  74. var anomaly = _entityLookup.GetEntitiesInRange<AnomalyComponent>(coords, ent.Comp.AttachRange).FirstOrDefault();
  75. if (anomaly.Owner is { Valid: false }) // no anomaly in range
  76. {
  77. if (user is not null)
  78. _popup.PopupEntity(Loc.GetString("anomaly-sync-no-anomaly"), ent, user.Value);
  79. return false;
  80. }
  81. ConnectToAnomaly(ent, anomaly);
  82. return true;
  83. }
  84. private void OnPowerChanged(Entity<AnomalySynchronizerComponent> ent, ref PowerChangedEvent args)
  85. {
  86. if (args.Powered)
  87. return;
  88. if (ent.Comp.ConnectedAnomaly is null)
  89. return;
  90. DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value);
  91. }
  92. private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
  93. {
  94. args.PushMarkup(Loc.GetString(ent.Comp.ConnectedAnomaly.HasValue ? "anomaly-sync-examine-connected" : "anomaly-sync-examine-not-connected"));
  95. }
  96. private void OnGetInteractionVerbs(Entity<AnomalySynchronizerComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
  97. {
  98. if (!args.CanAccess || !args.CanInteract || args.Hands is null || ent.Comp.ConnectedAnomaly.HasValue)
  99. return;
  100. var user = args.User;
  101. args.Verbs.Add(new()
  102. {
  103. Act = () =>
  104. {
  105. TryAttachNearbyAnomaly(ent, user);
  106. },
  107. Message = Loc.GetString("anomaly-sync-connect-verb-message", ("machine", ent)),
  108. Text = Loc.GetString("anomaly-sync-connect-verb-text"),
  109. });
  110. }
  111. private void OnInteractHand(Entity<AnomalySynchronizerComponent> ent, ref InteractHandEvent args)
  112. {
  113. TryAttachNearbyAnomaly(ent, args.User);
  114. }
  115. private void ConnectToAnomaly(Entity<AnomalySynchronizerComponent> ent, Entity<AnomalyComponent> anomaly)
  116. {
  117. if (ent.Comp.ConnectedAnomaly == anomaly)
  118. return;
  119. ent.Comp.ConnectedAnomaly = anomaly;
  120. //move the anomaly to the center of the synchronizer, for aesthetics.
  121. var targetXform = _transform.GetWorldPosition(ent);
  122. _transform.SetWorldPosition(anomaly, targetXform);
  123. if (ent.Comp.PulseOnConnect)
  124. _anomaly.DoAnomalyPulse(anomaly, anomaly);
  125. _popup.PopupEntity(Loc.GetString("anomaly-sync-connected"), ent, PopupType.Medium);
  126. _audio.PlayPvs(ent.Comp.ConnectedSound, ent);
  127. }
  128. //TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
  129. //Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
  130. private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid other)
  131. {
  132. if (ent.Comp.ConnectedAnomaly == null)
  133. return;
  134. if (TryComp<AnomalyComponent>(other, out var anomaly))
  135. {
  136. if (ent.Comp.PulseOnDisconnect)
  137. _anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
  138. }
  139. _popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
  140. _audio.PlayPvs(ent.Comp.ConnectedSound, ent);
  141. ent.Comp.ConnectedAnomaly = null;
  142. }
  143. private void OnAnomalyPulse(ref AnomalyPulseEvent args)
  144. {
  145. var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
  146. while (query.MoveNext(out var uid, out var component))
  147. {
  148. if (args.Anomaly != component.ConnectedAnomaly)
  149. continue;
  150. if (!_power.IsPowered(uid))
  151. continue;
  152. _signal.InvokePort(uid, component.PulsePort);
  153. }
  154. }
  155. private void OnAnomalySeverityChanged(ref AnomalySeverityChangedEvent args)
  156. {
  157. var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
  158. while (query.MoveNext(out var ent, out var component))
  159. {
  160. if (args.Anomaly != component.ConnectedAnomaly)
  161. continue;
  162. if (!_power.IsPowered(ent))
  163. continue;
  164. //The superscritical port is invoked not at the AnomalySupercriticalEvent,
  165. //but at the moment the growth animation starts. Otherwise, there is no point in this port.
  166. //ATTENTION! the console command supercriticalanomaly does not work here,
  167. //as it forcefully causes growth to start without increasing severity.
  168. if (args.Severity >= 1)
  169. _signal.InvokePort(ent, component.SupercritPort);
  170. }
  171. }
  172. private void OnAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
  173. {
  174. Entity<AnomalyComponent> anomaly = (args.Anomaly, Comp<AnomalyComponent>(args.Anomaly));
  175. var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
  176. while (query.MoveNext(out var ent, out var component))
  177. {
  178. if (component.ConnectedAnomaly != anomaly)
  179. continue;
  180. if (!_power.IsPowered(ent))
  181. continue;
  182. if (args.Stability < anomaly.Comp.DecayThreshold)
  183. {
  184. _signal.InvokePort(ent, component.DecayingPort);
  185. }
  186. else if (args.Stability > anomaly.Comp.GrowthThreshold)
  187. {
  188. _signal.InvokePort(ent, component.GrowingPort);
  189. }
  190. else
  191. {
  192. _signal.InvokePort(ent, component.StabilizePort);
  193. }
  194. }
  195. }
  196. }