1
0

GatewaySystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. using Content.Server.Gateway.Components;
  2. using Content.Server.Station.Systems;
  3. using Content.Shared.UserInterface;
  4. using Content.Shared.Access.Systems;
  5. using Content.Shared.Gateway;
  6. using Content.Shared.Popups;
  7. using Content.Shared.Teleportation.Components;
  8. using Content.Shared.Teleportation.Systems;
  9. using Content.Shared.Verbs;
  10. using Robust.Server.GameObjects;
  11. using Robust.Shared.Audio;
  12. using Robust.Shared.Audio.Systems;
  13. using Robust.Shared.GameObjects;
  14. using Robust.Shared.Timing;
  15. using Robust.Shared.Utility;
  16. namespace Content.Server.Gateway.Systems;
  17. public sealed class GatewaySystem : EntitySystem
  18. {
  19. [Dependency] private readonly AccessReaderSystem _accessReader = default!;
  20. [Dependency] private readonly IGameTiming _timing = default!;
  21. [Dependency] private readonly LinkedEntitySystem _linkedEntity = default!;
  22. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  23. [Dependency] private readonly SharedAudioSystem _audio = default!;
  24. [Dependency] private readonly MetaDataSystem _metadata = default!;
  25. [Dependency] private readonly StationSystem _stations = default!;
  26. [Dependency] private readonly SharedPopupSystem _popup = default!;
  27. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  28. public override void Initialize()
  29. {
  30. base.Initialize();
  31. SubscribeLocalEvent<GatewayComponent, ComponentStartup>(OnStartup);
  32. SubscribeLocalEvent<GatewayComponent, ActivatableUIOpenAttemptEvent>(OnGatewayOpenAttempt);
  33. SubscribeLocalEvent<GatewayComponent, BoundUIOpenedEvent>(UpdateUserInterface);
  34. SubscribeLocalEvent<GatewayComponent, GatewayOpenPortalMessage>(OnOpenPortal);
  35. }
  36. public void SetEnabled(EntityUid uid, bool value, GatewayComponent? component = null)
  37. {
  38. if (!Resolve(uid, ref component) || component.Enabled == value)
  39. return;
  40. component.Enabled = value;
  41. UpdateAllGateways();
  42. }
  43. private void OnStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
  44. {
  45. // no need to update ui since its just been created, just do portal
  46. UpdateAppearance(uid);
  47. }
  48. private void OnGatewayOpenAttempt(EntityUid uid, GatewayComponent component, ref ActivatableUIOpenAttemptEvent args)
  49. {
  50. if (!component.Enabled || !component.Interactable)
  51. args.Cancel();
  52. }
  53. private void UpdateUserInterface<T>(EntityUid uid, GatewayComponent comp, T args)
  54. {
  55. UpdateUserInterface(uid, comp);
  56. }
  57. public void UpdateAllGateways()
  58. {
  59. var query = AllEntityQuery<GatewayComponent, TransformComponent>();
  60. while (query.MoveNext(out var uid, out var comp, out var xform))
  61. {
  62. UpdateUserInterface(uid, comp, xform);
  63. }
  64. }
  65. private void UpdateUserInterface(EntityUid uid, GatewayComponent comp, TransformComponent? xform = null)
  66. {
  67. if (!Resolve(uid, ref xform))
  68. return;
  69. var destinations = new List<GatewayDestinationData>();
  70. var query = AllEntityQuery<GatewayComponent, TransformComponent>();
  71. var nextUnlock = TimeSpan.Zero;
  72. var unlockTime = TimeSpan.Zero;
  73. // Next unlock is based off of:
  74. // - Our station's unlock timer (if we have a station)
  75. // - If our map is a generated destination then use the generator that made it
  76. if (TryComp(_stations.GetOwningStation(uid), out GatewayGeneratorComponent? generatorComp) ||
  77. (TryComp(xform.MapUid, out GatewayGeneratorDestinationComponent? generatorDestination) &&
  78. TryComp(generatorDestination.Generator, out generatorComp)))
  79. {
  80. nextUnlock = generatorComp.NextUnlock;
  81. unlockTime = generatorComp.UnlockCooldown;
  82. }
  83. while (query.MoveNext(out var destUid, out var dest, out var destXform))
  84. {
  85. if (!dest.Enabled || destUid == uid)
  86. continue;
  87. // Show destination if either no destination comp on the map or it's ours.
  88. TryComp<GatewayGeneratorDestinationComponent>(destXform.MapUid, out var gatewayDestination);
  89. destinations.Add(new GatewayDestinationData()
  90. {
  91. Entity = GetNetEntity(destUid),
  92. // Fallback to grid's ID if applicable.
  93. Name = dest.Name.IsEmpty && destXform.GridUid != null ? FormattedMessage.FromUnformatted(MetaData(destXform.GridUid.Value).EntityName) : dest.Name ,
  94. Portal = HasComp<PortalComponent>(destUid),
  95. // If NextUnlock < CurTime it's unlocked, however
  96. // we'll always send the client if it's locked
  97. // It can just infer unlock times locally and not have to worry about it here.
  98. Locked = gatewayDestination != null && gatewayDestination.Locked
  99. });
  100. }
  101. _linkedEntity.GetLink(uid, out var current);
  102. var state = new GatewayBoundUserInterfaceState(
  103. destinations,
  104. GetNetEntity(current),
  105. comp.NextReady,
  106. comp.Cooldown,
  107. nextUnlock,
  108. unlockTime
  109. );
  110. _ui.SetUiState(uid, GatewayUiKey.Key, state);
  111. }
  112. private void UpdateAppearance(EntityUid uid)
  113. {
  114. _appearance.SetData(uid, GatewayVisuals.Active, HasComp<PortalComponent>(uid));
  115. }
  116. private void OnOpenPortal(EntityUid uid, GatewayComponent comp, GatewayOpenPortalMessage args)
  117. {
  118. if (GetNetEntity(uid) == args.Destination ||
  119. !comp.Enabled || !comp.Interactable)
  120. {
  121. return;
  122. }
  123. // if the gateway has an access reader check it before allowing opening
  124. var user = args.Actor;
  125. if (CheckAccess(user, uid, comp))
  126. return;
  127. // can't link if portal is already open on either side, the destination is invalid or on cooldown
  128. var desto = GetEntity(args.Destination);
  129. // If it's already open / not enabled / we're not ready DENY.
  130. if (!TryComp<GatewayComponent>(desto, out var dest) ||
  131. !dest.Enabled ||
  132. _timing.CurTime < _metadata.GetPauseTime(uid) + comp.NextReady)
  133. {
  134. return;
  135. }
  136. // TODO: admin log???
  137. ClosePortal(uid, comp, false);
  138. OpenPortal(uid, comp, desto, dest);
  139. }
  140. private void OpenPortal(EntityUid uid, GatewayComponent comp, EntityUid dest, GatewayComponent destComp, TransformComponent? destXform = null)
  141. {
  142. if (!Resolve(dest, ref destXform) || destXform.MapUid == null)
  143. return;
  144. var ev = new AttemptGatewayOpenEvent(destXform.MapUid.Value, dest);
  145. RaiseLocalEvent(destXform.MapUid.Value, ref ev);
  146. if (ev.Cancelled)
  147. return;
  148. _linkedEntity.OneWayLink(uid, dest);
  149. var sourcePortal = EnsureComp<PortalComponent>(uid);
  150. var targetPortal = EnsureComp<PortalComponent>(dest);
  151. sourcePortal.CanTeleportToOtherMaps = true;
  152. targetPortal.CanTeleportToOtherMaps = true;
  153. sourcePortal.RandomTeleport = false;
  154. targetPortal.RandomTeleport = false;
  155. var openEv = new GatewayOpenEvent(destXform.MapUid.Value, dest);
  156. RaiseLocalEvent(destXform.MapUid.Value, ref openEv);
  157. // for ui
  158. comp.NextReady = _timing.CurTime + comp.Cooldown;
  159. _audio.PlayPvs(comp.OpenSound, uid);
  160. _audio.PlayPvs(comp.OpenSound, dest);
  161. UpdateUserInterface(uid, comp);
  162. UpdateAppearance(uid);
  163. UpdateAppearance(dest);
  164. }
  165. private void ClosePortal(EntityUid uid, GatewayComponent? comp = null, bool update = true)
  166. {
  167. if (!Resolve(uid, ref comp))
  168. return;
  169. RemComp<PortalComponent>(uid);
  170. if (!_linkedEntity.GetLink(uid, out var dest))
  171. return;
  172. if (TryComp<GatewayComponent>(dest, out var destComp))
  173. {
  174. // portals closed, put it on cooldown and let it eventually be opened again
  175. destComp.NextReady = _timing.CurTime + destComp.Cooldown;
  176. }
  177. _audio.PlayPvs(comp.CloseSound, uid);
  178. _audio.PlayPvs(comp.CloseSound, dest.Value);
  179. _linkedEntity.TryUnlink(uid, dest.Value);
  180. RemComp<PortalComponent>(dest.Value);
  181. if (update)
  182. {
  183. UpdateUserInterface(uid, comp);
  184. UpdateAppearance(uid);
  185. UpdateAppearance(dest.Value);
  186. }
  187. }
  188. private void OnDestinationStartup(EntityUid uid, GatewayComponent comp, ComponentStartup args)
  189. {
  190. var query = AllEntityQuery<GatewayComponent>();
  191. while (query.MoveNext(out var gatewayUid, out var gateway))
  192. {
  193. UpdateUserInterface(gatewayUid, gateway);
  194. }
  195. UpdateAppearance(uid);
  196. }
  197. private void OnDestinationShutdown(EntityUid uid, GatewayComponent comp, ComponentShutdown args)
  198. {
  199. var query = AllEntityQuery<GatewayComponent>();
  200. while (query.MoveNext(out var gatewayUid, out var gateway))
  201. {
  202. UpdateUserInterface(gatewayUid, gateway);
  203. }
  204. }
  205. private void TryClose(EntityUid uid, EntityUid user)
  206. {
  207. // portal already closed so cant close it
  208. if (!_linkedEntity.GetLink(uid, out var source))
  209. return;
  210. // not allowed to close it
  211. if (CheckAccess(user, source.Value))
  212. return;
  213. ClosePortal(source.Value);
  214. }
  215. /// <summary>
  216. /// Checks the user's access. Makes popup and plays sound if missing access.
  217. /// Returns whether access was missing.
  218. /// </summary>
  219. private bool CheckAccess(EntityUid user, EntityUid uid, GatewayComponent? comp = null)
  220. {
  221. if (!Resolve(uid, ref comp))
  222. return false;
  223. if (_accessReader.IsAllowed(user, uid))
  224. return false;
  225. _popup.PopupEntity(Loc.GetString("gateway-access-denied"), user);
  226. _audio.PlayPvs(comp.AccessDeniedSound, uid);
  227. return true;
  228. }
  229. public void SetDestinationName(EntityUid gatewayUid, FormattedMessage gatewayName, GatewayComponent? gatewayComp = null)
  230. {
  231. if (!Resolve(gatewayUid, ref gatewayComp))
  232. return;
  233. gatewayComp.Name = gatewayName;
  234. }
  235. }
  236. /// <summary>
  237. /// Raised directed on the target map when a GatewayDestination is attempted to be opened.
  238. /// </summary>
  239. [ByRefEvent]
  240. public record struct AttemptGatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid)
  241. {
  242. public readonly EntityUid MapUid = MapUid;
  243. public readonly EntityUid GatewayDestinationUid = GatewayDestinationUid;
  244. public bool Cancelled = false;
  245. }
  246. /// <summary>
  247. /// Raised directed on the target map when a gateway is opened.
  248. /// </summary>
  249. [ByRefEvent]
  250. public readonly record struct GatewayOpenEvent(EntityUid MapUid, EntityUid GatewayDestinationUid);