DisposalTubeSystem.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. using System.Linq;
  2. using System.Text;
  3. using Content.Server.Atmos.EntitySystems;
  4. using Content.Server.Construction.Completions;
  5. using Content.Server.Disposal.Tube.Components;
  6. using Content.Server.Disposal.Unit.Components;
  7. using Content.Server.Disposal.Unit.EntitySystems;
  8. using Content.Server.Popups;
  9. using Content.Shared.Destructible;
  10. using Content.Shared.Disposal.Components;
  11. using Robust.Server.GameObjects;
  12. using Robust.Shared.Audio;
  13. using Robust.Shared.Audio.Systems;
  14. using Robust.Shared.Containers;
  15. using Robust.Shared.Map.Components;
  16. using Robust.Shared.Physics;
  17. using Robust.Shared.Physics.Components;
  18. using Robust.Shared.Random;
  19. using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
  20. using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
  21. namespace Content.Server.Disposal.Tube
  22. {
  23. public sealed class DisposalTubeSystem : EntitySystem
  24. {
  25. [Dependency] private readonly IRobustRandom _random = default!;
  26. [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
  27. [Dependency] private readonly PopupSystem _popups = default!;
  28. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  29. [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
  30. [Dependency] private readonly DisposableSystem _disposableSystem = default!;
  31. [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
  32. [Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
  33. [Dependency] private readonly TransformSystem _transform = default!;
  34. [Dependency] private readonly SharedMapSystem _map = default!;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<DisposalTubeComponent, ComponentInit>(OnComponentInit);
  39. SubscribeLocalEvent<DisposalTubeComponent, ComponentRemove>(OnComponentRemove);
  40. SubscribeLocalEvent<DisposalTubeComponent, AnchorStateChangedEvent>(OnAnchorChange);
  41. SubscribeLocalEvent<DisposalTubeComponent, BreakageEventArgs>(OnBreak);
  42. SubscribeLocalEvent<DisposalTubeComponent, ComponentStartup>(OnStartup);
  43. SubscribeLocalEvent<DisposalTubeComponent, ConstructionBeforeDeleteEvent>(OnDeconstruct);
  44. SubscribeLocalEvent<DisposalBendComponent, GetDisposalsConnectableDirectionsEvent>(OnGetBendConnectableDirections);
  45. SubscribeLocalEvent<DisposalBendComponent, GetDisposalsNextDirectionEvent>(OnGetBendNextDirection);
  46. SubscribeLocalEvent<DisposalEntryComponent, GetDisposalsConnectableDirectionsEvent>(OnGetEntryConnectableDirections);
  47. SubscribeLocalEvent<DisposalEntryComponent, GetDisposalsNextDirectionEvent>(OnGetEntryNextDirection);
  48. SubscribeLocalEvent<DisposalJunctionComponent, GetDisposalsConnectableDirectionsEvent>(OnGetJunctionConnectableDirections);
  49. SubscribeLocalEvent<DisposalJunctionComponent, GetDisposalsNextDirectionEvent>(OnGetJunctionNextDirection);
  50. SubscribeLocalEvent<DisposalRouterComponent, GetDisposalsConnectableDirectionsEvent>(OnGetRouterConnectableDirections);
  51. SubscribeLocalEvent<DisposalRouterComponent, GetDisposalsNextDirectionEvent>(OnGetRouterNextDirection);
  52. SubscribeLocalEvent<DisposalTransitComponent, GetDisposalsConnectableDirectionsEvent>(OnGetTransitConnectableDirections);
  53. SubscribeLocalEvent<DisposalTransitComponent, GetDisposalsNextDirectionEvent>(OnGetTransitNextDirection);
  54. SubscribeLocalEvent<DisposalTaggerComponent, GetDisposalsConnectableDirectionsEvent>(OnGetTaggerConnectableDirections);
  55. SubscribeLocalEvent<DisposalTaggerComponent, GetDisposalsNextDirectionEvent>(OnGetTaggerNextDirection);
  56. Subs.BuiEvents<DisposalRouterComponent>(DisposalRouterUiKey.Key, subs =>
  57. {
  58. subs.Event<BoundUIOpenedEvent>(OnOpenRouterUI);
  59. subs.Event<SharedDisposalRouterComponent.UiActionMessage>(OnUiAction);
  60. });
  61. Subs.BuiEvents<DisposalTaggerComponent>(DisposalTaggerUiKey.Key, subs =>
  62. {
  63. subs.Event<BoundUIOpenedEvent>(OnOpenTaggerUI);
  64. subs.Event<SharedDisposalTaggerComponent.UiActionMessage>(OnUiAction);
  65. });
  66. }
  67. /// <summary>
  68. /// Handles ui messages from the client. For things such as button presses
  69. /// which interact with the world and require server action.
  70. /// </summary>
  71. /// <param name="msg">A user interface message from the client.</param>
  72. private void OnUiAction(EntityUid uid, DisposalTaggerComponent tagger, SharedDisposalTaggerComponent.UiActionMessage msg)
  73. {
  74. if (TryComp<PhysicsComponent>(uid, out var physBody) && physBody.BodyType != BodyType.Static)
  75. return;
  76. //Check for correct message and ignore maleformed strings
  77. if (msg.Action == SharedDisposalTaggerComponent.UiAction.Ok && SharedDisposalTaggerComponent.TagRegex.IsMatch(msg.Tag))
  78. {
  79. tagger.Tag = msg.Tag.Trim();
  80. _audioSystem.PlayPvs(tagger.ClickSound, uid, AudioParams.Default.WithVolume(-2f));
  81. }
  82. }
  83. /// <summary>
  84. /// Handles ui messages from the client. For things such as button presses
  85. /// which interact with the world and require server action.
  86. /// </summary>
  87. /// <param name="msg">A user interface message from the client.</param>
  88. private void OnUiAction(EntityUid uid, DisposalRouterComponent router, SharedDisposalRouterComponent.UiActionMessage msg)
  89. {
  90. if (!EntityManager.EntityExists(msg.Actor))
  91. return;
  92. if (TryComp<PhysicsComponent>(uid, out var physBody) && physBody.BodyType != BodyType.Static)
  93. return;
  94. //Check for correct message and ignore maleformed strings
  95. if (msg.Action == SharedDisposalRouterComponent.UiAction.Ok && SharedDisposalRouterComponent.TagRegex.IsMatch(msg.Tags))
  96. {
  97. router.Tags.Clear();
  98. foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
  99. {
  100. var trimmed = tag.Trim();
  101. if (trimmed == "")
  102. continue;
  103. router.Tags.Add(trimmed);
  104. }
  105. _audioSystem.PlayPvs(router.ClickSound, uid, AudioParams.Default.WithVolume(-2f));
  106. }
  107. }
  108. private void OnComponentInit(EntityUid uid, DisposalTubeComponent tube, ComponentInit args)
  109. {
  110. tube.Contents = _containerSystem.EnsureContainer<Container>(uid, tube.ContainerId);
  111. }
  112. private void OnComponentRemove(EntityUid uid, DisposalTubeComponent tube, ComponentRemove args)
  113. {
  114. DisconnectTube(uid, tube);
  115. }
  116. private void OnGetBendConnectableDirections(EntityUid uid, DisposalBendComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  117. {
  118. var direction = Transform(uid).LocalRotation;
  119. var side = new Angle(MathHelper.DegreesToRadians(direction.Degrees - 90));
  120. args.Connectable = new[] { direction.GetDir(), side.GetDir() };
  121. }
  122. private void OnGetBendNextDirection(EntityUid uid, DisposalBendComponent component, ref GetDisposalsNextDirectionEvent args)
  123. {
  124. var ev = new GetDisposalsConnectableDirectionsEvent();
  125. RaiseLocalEvent(uid, ref ev);
  126. var previousDF = args.Holder.PreviousDirectionFrom;
  127. if (previousDF == Direction.Invalid)
  128. {
  129. args.Next = ev.Connectable[0];
  130. return;
  131. }
  132. args.Next = previousDF == ev.Connectable[0] ? ev.Connectable[1] : ev.Connectable[0];
  133. }
  134. private void OnGetEntryConnectableDirections(EntityUid uid, DisposalEntryComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  135. {
  136. args.Connectable = new[] { Transform(uid).LocalRotation.GetDir() };
  137. }
  138. private void OnGetEntryNextDirection(EntityUid uid, DisposalEntryComponent component, ref GetDisposalsNextDirectionEvent args)
  139. {
  140. // Ejects contents when they come from the same direction the entry is facing.
  141. if (args.Holder.PreviousDirectionFrom != Direction.Invalid)
  142. {
  143. args.Next = Direction.Invalid;
  144. return;
  145. }
  146. var ev = new GetDisposalsConnectableDirectionsEvent();
  147. RaiseLocalEvent(uid, ref ev);
  148. args.Next = ev.Connectable[0];
  149. }
  150. private void OnGetJunctionConnectableDirections(EntityUid uid, DisposalJunctionComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  151. {
  152. var direction = Transform(uid).LocalRotation;
  153. args.Connectable = component.Degrees
  154. .Select(degree => new Angle(degree.Theta + direction.Theta).GetDir())
  155. .ToArray();
  156. }
  157. private void OnGetJunctionNextDirection(EntityUid uid, DisposalJunctionComponent component, ref GetDisposalsNextDirectionEvent args)
  158. {
  159. var next = Transform(uid).LocalRotation.GetDir();
  160. var ev = new GetDisposalsConnectableDirectionsEvent();
  161. RaiseLocalEvent(uid, ref ev);
  162. var directions = ev.Connectable.Skip(1).ToArray();
  163. if (args.Holder.PreviousDirectionFrom == Direction.Invalid ||
  164. args.Holder.PreviousDirectionFrom == next)
  165. {
  166. args.Next = _random.Pick(directions);
  167. return;
  168. }
  169. args.Next = next;
  170. }
  171. private void OnGetRouterConnectableDirections(EntityUid uid, DisposalRouterComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  172. {
  173. OnGetJunctionConnectableDirections(uid, component, ref args);
  174. }
  175. private void OnGetRouterNextDirection(EntityUid uid, DisposalRouterComponent component, ref GetDisposalsNextDirectionEvent args)
  176. {
  177. var ev = new GetDisposalsConnectableDirectionsEvent();
  178. RaiseLocalEvent(uid, ref ev);
  179. if (args.Holder.Tags.Overlaps(component.Tags))
  180. {
  181. args.Next = ev.Connectable[1];
  182. return;
  183. }
  184. args.Next = Transform(uid).LocalRotation.GetDir();
  185. }
  186. private void OnGetTransitConnectableDirections(EntityUid uid, DisposalTransitComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  187. {
  188. var rotation = Transform(uid).LocalRotation;
  189. var opposite = new Angle(rotation.Theta + Math.PI);
  190. args.Connectable = new[] { rotation.GetDir(), opposite.GetDir() };
  191. }
  192. private void OnGetTransitNextDirection(EntityUid uid, DisposalTransitComponent component, ref GetDisposalsNextDirectionEvent args)
  193. {
  194. var ev = new GetDisposalsConnectableDirectionsEvent();
  195. RaiseLocalEvent(uid, ref ev);
  196. var previousDF = args.Holder.PreviousDirectionFrom;
  197. var forward = ev.Connectable[0];
  198. if (previousDF == Direction.Invalid)
  199. {
  200. args.Next = forward;
  201. return;
  202. }
  203. var backward = ev.Connectable[1];
  204. args.Next = previousDF == forward ? backward : forward;
  205. }
  206. private void OnGetTaggerConnectableDirections(EntityUid uid, DisposalTaggerComponent component, ref GetDisposalsConnectableDirectionsEvent args)
  207. {
  208. OnGetTransitConnectableDirections(uid, component, ref args);
  209. }
  210. private void OnGetTaggerNextDirection(EntityUid uid, DisposalTaggerComponent component, ref GetDisposalsNextDirectionEvent args)
  211. {
  212. args.Holder.Tags.Add(component.Tag);
  213. OnGetTransitNextDirection(uid, component, ref args);
  214. }
  215. private void OnDeconstruct(EntityUid uid, DisposalTubeComponent component, ConstructionBeforeDeleteEvent args)
  216. {
  217. DisconnectTube(uid, component);
  218. }
  219. private void OnStartup(EntityUid uid, DisposalTubeComponent component, ComponentStartup args)
  220. {
  221. UpdateAnchored(uid, component, Transform(uid).Anchored);
  222. }
  223. private void OnBreak(EntityUid uid, DisposalTubeComponent component, BreakageEventArgs args)
  224. {
  225. DisconnectTube(uid, component);
  226. }
  227. private void OnOpenRouterUI(EntityUid uid, DisposalRouterComponent router, BoundUIOpenedEvent args)
  228. {
  229. UpdateRouterUserInterface(uid, router);
  230. }
  231. private void OnOpenTaggerUI(EntityUid uid, DisposalTaggerComponent tagger, BoundUIOpenedEvent args)
  232. {
  233. if (_uiSystem.HasUi(uid, DisposalTaggerUiKey.Key))
  234. {
  235. _uiSystem.SetUiState(uid, DisposalTaggerUiKey.Key,
  236. new DisposalTaggerUserInterfaceState(tagger.Tag));
  237. }
  238. }
  239. /// <summary>
  240. /// Gets component data to be used to update the user interface client-side.
  241. /// </summary>
  242. /// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterUserInterfaceState"/></returns>
  243. private void UpdateRouterUserInterface(EntityUid uid, DisposalRouterComponent router)
  244. {
  245. if (router.Tags.Count <= 0)
  246. {
  247. _uiSystem.SetUiState(uid, DisposalRouterUiKey.Key, new DisposalRouterUserInterfaceState(""));
  248. return;
  249. }
  250. var taglist = new StringBuilder();
  251. foreach (var tag in router.Tags)
  252. {
  253. taglist.Append(tag);
  254. taglist.Append(", ");
  255. }
  256. taglist.Remove(taglist.Length - 2, 2);
  257. _uiSystem.SetUiState(uid, DisposalRouterUiKey.Key, new DisposalRouterUserInterfaceState(taglist.ToString()));
  258. }
  259. private void OnAnchorChange(EntityUid uid, DisposalTubeComponent component, ref AnchorStateChangedEvent args)
  260. {
  261. UpdateAnchored(uid, component, args.Anchored);
  262. }
  263. private void UpdateAnchored(EntityUid uid, DisposalTubeComponent component, bool anchored)
  264. {
  265. if (anchored)
  266. {
  267. ConnectTube(uid, component);
  268. // TODO this visual data should just generalized into some anchored-visuals system/comp, this has nothing to do with disposal tubes.
  269. _appearanceSystem.SetData(uid, DisposalTubeVisuals.VisualState, DisposalTubeVisualState.Anchored);
  270. }
  271. else
  272. {
  273. DisconnectTube(uid, component);
  274. _appearanceSystem.SetData(uid, DisposalTubeVisuals.VisualState, DisposalTubeVisualState.Free);
  275. }
  276. }
  277. public EntityUid? NextTubeFor(EntityUid target, Direction nextDirection, DisposalTubeComponent? targetTube = null)
  278. {
  279. if (!Resolve(target, ref targetTube))
  280. return null;
  281. var oppositeDirection = nextDirection.GetOpposite();
  282. var xform = Transform(target);
  283. if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
  284. return null;
  285. var position = xform.Coordinates;
  286. foreach (var entity in _map.GetInDir(xform.GridUid.Value, grid, position, nextDirection))
  287. {
  288. if (!TryComp(entity, out DisposalTubeComponent? tube))
  289. {
  290. continue;
  291. }
  292. if (!CanConnect(entity, tube, oppositeDirection))
  293. {
  294. continue;
  295. }
  296. if (!CanConnect(target, targetTube, nextDirection))
  297. {
  298. continue;
  299. }
  300. return entity;
  301. }
  302. return null;
  303. }
  304. public static void ConnectTube(EntityUid _, DisposalTubeComponent tube)
  305. {
  306. if (tube.Connected)
  307. {
  308. return;
  309. }
  310. tube.Connected = true;
  311. }
  312. public void DisconnectTube(EntityUid _, DisposalTubeComponent tube)
  313. {
  314. if (!tube.Connected)
  315. {
  316. return;
  317. }
  318. tube.Connected = false;
  319. var query = GetEntityQuery<DisposalHolderComponent>();
  320. foreach (var entity in tube.Contents.ContainedEntities.ToArray())
  321. {
  322. if (query.TryGetComponent(entity, out var holder))
  323. _disposableSystem.ExitDisposals(entity, holder);
  324. }
  325. }
  326. public bool CanConnect(EntityUid tubeId, DisposalTubeComponent tube, Direction direction)
  327. {
  328. if (!tube.Connected)
  329. {
  330. return false;
  331. }
  332. var ev = new GetDisposalsConnectableDirectionsEvent();
  333. RaiseLocalEvent(tubeId, ref ev);
  334. return ev.Connectable.Contains(direction);
  335. }
  336. public void PopupDirections(EntityUid tubeId, DisposalTubeComponent _, EntityUid recipient)
  337. {
  338. var ev = new GetDisposalsConnectableDirectionsEvent();
  339. RaiseLocalEvent(tubeId, ref ev);
  340. var directions = string.Join(", ", ev.Connectable);
  341. _popups.PopupEntity(Loc.GetString("disposal-tube-component-popup-directions-text", ("directions", directions)), tubeId, recipient);
  342. }
  343. public bool TryInsert(EntityUid uid, DisposalUnitComponent from, IEnumerable<string>? tags = default, DisposalEntryComponent? entry = null)
  344. {
  345. if (!Resolve(uid, ref entry))
  346. return false;
  347. var xform = Transform(uid);
  348. var holder = Spawn(DisposalEntryComponent.HolderPrototypeId, _transform.GetMapCoordinates(uid, xform: xform));
  349. var holderComponent = Comp<DisposalHolderComponent>(holder);
  350. foreach (var entity in from.Container.ContainedEntities.ToArray())
  351. {
  352. _disposableSystem.TryInsert(holder, entity, holderComponent);
  353. }
  354. _atmosSystem.Merge(holderComponent.Air, from.Air);
  355. from.Air.Clear();
  356. if (tags != default)
  357. holderComponent.Tags.UnionWith(tags);
  358. return _disposableSystem.EnterTube(holder, uid, holderComponent);
  359. }
  360. }
  361. }