1
0

BluespaceLockerSystem.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. using System.Linq;
  2. using Content.Server.Explosion.EntitySystems;
  3. using Content.Server.Resist;
  4. using Content.Server.Storage.Components;
  5. using Content.Shared.Access;
  6. using Content.Shared.Access.Components;
  7. using Content.Shared.Coordinates;
  8. using Content.Shared.DoAfter;
  9. using Content.Shared.Lock;
  10. using Content.Shared.Mind.Components;
  11. using Content.Shared.Station.Components;
  12. using Content.Shared.Storage.Components;
  13. using Content.Shared.Storage.EntitySystems;
  14. using Content.Shared.Tools.Systems;
  15. using Robust.Shared.Containers;
  16. using Robust.Shared.Random;
  17. using Robust.Shared.Timing;
  18. using Robust.Shared.Prototypes;
  19. using Content.Server.Shuttles.Components;
  20. namespace Content.Server.Storage.EntitySystems;
  21. public sealed class BluespaceLockerSystem : EntitySystem
  22. {
  23. [Dependency] private readonly IRobustRandom _robustRandom = default!;
  24. [Dependency] private readonly IGameTiming _timing = default!;
  25. [Dependency] private readonly SharedContainerSystem _containerSystem = default!;
  26. [Dependency] private readonly EntityStorageSystem _entityStorage = default!;
  27. [Dependency] private readonly WeldableSystem _weldableSystem = default!;
  28. [Dependency] private readonly LockSystem _lockSystem = default!;
  29. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  30. [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
  31. [Dependency] private readonly ExplosionSystem _explosionSystem = default!;
  32. public override void Initialize()
  33. {
  34. base.Initialize();
  35. SubscribeLocalEvent<BluespaceLockerComponent, ComponentStartup>(OnStartup);
  36. SubscribeLocalEvent<BluespaceLockerComponent, StorageBeforeOpenEvent>(PreOpen);
  37. SubscribeLocalEvent<BluespaceLockerComponent, StorageAfterCloseEvent>(PostClose);
  38. SubscribeLocalEvent<BluespaceLockerComponent, BluespaceLockerDoAfterEvent>(OnDoAfter);
  39. }
  40. private void OnStartup(EntityUid uid, BluespaceLockerComponent component, ComponentStartup args)
  41. {
  42. GetTarget(uid, component, true);
  43. if (component.BehaviorProperties.BluespaceEffectOnInit)
  44. BluespaceEffect(uid, component, component, true);
  45. EnsureComp<ArrivalsBlacklistComponent>(uid); // To stop people getting to arrivals terminal
  46. }
  47. public void BluespaceEffect(EntityUid effectTargetUid, BluespaceLockerComponent effectSourceComponent, BluespaceLockerComponent? effectTargetComponent, bool bypassLimit = false)
  48. {
  49. if (!bypassLimit && Resolve(effectTargetUid, ref effectTargetComponent, false))
  50. if (effectTargetComponent.BehaviorProperties.BluespaceEffectMinInterval > 0)
  51. {
  52. var curTimeTicks = _timing.CurTick.Value;
  53. if (curTimeTicks < effectTargetComponent.BluespaceEffectNextTime)
  54. return;
  55. effectTargetComponent.BluespaceEffectNextTime = curTimeTicks + (uint) (_timing.TickRate * effectTargetComponent.BehaviorProperties.BluespaceEffectMinInterval);
  56. }
  57. Spawn(effectSourceComponent.BehaviorProperties.BluespaceEffectPrototype, effectTargetUid.ToCoordinates());
  58. }
  59. private void PreOpen(EntityUid uid, BluespaceLockerComponent component, ref StorageBeforeOpenEvent args)
  60. {
  61. EntityStorageComponent? entityStorageComponent = null;
  62. int transportedEntities = 0;
  63. if (!Resolve(uid, ref entityStorageComponent))
  64. return;
  65. if (!component.BehaviorProperties.ActOnOpen)
  66. return;
  67. // Select target
  68. var target = GetTarget(uid, component);
  69. if (target == null)
  70. return;
  71. // Close target if it is open
  72. if (target.Value.storageComponent.Open)
  73. _entityStorage.CloseStorage(target.Value.uid, target.Value.storageComponent);
  74. // Apply bluespace effects if target is not a bluespace locker, otherwise let it handle it
  75. if (target.Value.bluespaceLockerComponent == null)
  76. {
  77. // Move contained items
  78. if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient)
  79. foreach (var entity in target.Value.storageComponent.Contents.ContainedEntities.ToArray())
  80. {
  81. if (EntityManager.HasComponent<MindContainerComponent>(entity))
  82. {
  83. if (!component.BehaviorProperties.TransportSentient)
  84. continue;
  85. _containerSystem.Insert(entity, entityStorageComponent.Contents);
  86. transportedEntities++;
  87. }
  88. else if (component.BehaviorProperties.TransportEntities)
  89. {
  90. _containerSystem.Insert(entity, entityStorageComponent.Contents);
  91. transportedEntities++;
  92. }
  93. }
  94. // Move contained air
  95. if (component.BehaviorProperties.TransportGas)
  96. {
  97. entityStorageComponent.Air.CopyFrom(target.Value.storageComponent.Air);
  98. target.Value.storageComponent.Air.Clear();
  99. }
  100. // Bluespace effects
  101. if (component.BehaviorProperties.BluespaceEffectOnTeleportSource)
  102. BluespaceEffect(target.Value.uid, component, target.Value.bluespaceLockerComponent);
  103. if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget)
  104. BluespaceEffect(uid, component, component);
  105. }
  106. DestroyAfterLimit(uid, component, transportedEntities);
  107. }
  108. private bool ValidLink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent, bool intendToLink = false)
  109. {
  110. if (!link.Valid ||
  111. !TryComp<EntityStorageComponent>(link, out var linkStorage) ||
  112. linkStorage.LifeStage == ComponentLifeStage.Deleted ||
  113. link == locker)
  114. return false;
  115. if (lockerComponent.BehaviorProperties.InvalidateOneWayLinks &&
  116. !(intendToLink && lockerComponent.AutoLinksBidirectional) &&
  117. !(HasComp<BluespaceLockerComponent>(link) && Comp<BluespaceLockerComponent>(link).BluespaceLinks.Contains(locker)))
  118. return false;
  119. return true;
  120. }
  121. /// <returns>True if any HashSet in <paramref name="a"/> would grant access to <paramref name="b"/></returns>
  122. private bool AccessMatch(IReadOnlyCollection<HashSet<ProtoId<AccessLevelPrototype>>>? a, IReadOnlyCollection<HashSet<ProtoId<AccessLevelPrototype>>>? b)
  123. {
  124. if ((a == null || a.Count == 0) && (b == null || b.Count == 0))
  125. return true;
  126. if (a != null && a.Any(aSet => aSet.Count == 0))
  127. return true;
  128. if (b != null && b.Any(bSet => bSet.Count == 0))
  129. return true;
  130. if (a != null && b != null)
  131. return a.Any(aSet => b.Any(aSet.SetEquals));
  132. return false;
  133. }
  134. private bool ValidAutolink(EntityUid locker, EntityUid link, BluespaceLockerComponent lockerComponent)
  135. {
  136. if (!ValidLink(locker, link, lockerComponent, true))
  137. return false;
  138. if (lockerComponent.PickLinksFromSameMap &&
  139. link.ToCoordinates().GetMapId(EntityManager) != locker.ToCoordinates().GetMapId(EntityManager))
  140. return false;
  141. if (lockerComponent.PickLinksFromStationGrids &&
  142. !HasComp<StationMemberComponent>(link.ToCoordinates().GetGridUid(EntityManager)))
  143. return false;
  144. if (lockerComponent.PickLinksFromResistLockers &&
  145. !HasComp<ResistLockerComponent>(link))
  146. return false;
  147. if (lockerComponent.PickLinksFromSameAccess)
  148. {
  149. TryComp<AccessReaderComponent>(locker, out var sourceAccess);
  150. TryComp<AccessReaderComponent>(link, out var targetAccess);
  151. if (!AccessMatch(sourceAccess?.AccessLists, targetAccess?.AccessLists))
  152. return false;
  153. }
  154. if (HasComp<BluespaceLockerComponent>(link))
  155. {
  156. if (lockerComponent.PickLinksFromNonBluespaceLockers)
  157. return false;
  158. }
  159. else
  160. {
  161. if (lockerComponent.PickLinksFromBluespaceLockers)
  162. return false;
  163. }
  164. return true;
  165. }
  166. public (EntityUid uid, EntityStorageComponent storageComponent, BluespaceLockerComponent? bluespaceLockerComponent)? GetTarget(EntityUid lockerUid, BluespaceLockerComponent component, bool init = false)
  167. {
  168. while (true)
  169. {
  170. // Ensure MinBluespaceLinks
  171. if (component.BluespaceLinks.Count < component.MinBluespaceLinks)
  172. {
  173. // Get an shuffle the list of all EntityStorages
  174. var storages = new List<Entity<EntityStorageComponent>>();
  175. var query = EntityQueryEnumerator<EntityStorageComponent>();
  176. while (query.MoveNext(out var uid, out var storage))
  177. {
  178. storages.Add((uid, storage));
  179. }
  180. _robustRandom.Shuffle(storages);
  181. // Add valid candidates till MinBluespaceLinks is met
  182. foreach (var storage in storages)
  183. {
  184. var potentialLink = storage.Owner;
  185. if (!ValidAutolink(lockerUid, potentialLink, component))
  186. continue;
  187. component.BluespaceLinks.Add(potentialLink);
  188. if (component.AutoLinksBidirectional || component.AutoLinksUseProperties)
  189. {
  190. var targetBluespaceComponent = CompOrNull<BluespaceLockerComponent>(potentialLink);
  191. if (targetBluespaceComponent == null)
  192. {
  193. targetBluespaceComponent = AddComp<BluespaceLockerComponent>(potentialLink);
  194. if (component.AutoLinksBidirectional)
  195. targetBluespaceComponent.BluespaceLinks.Add(lockerUid);
  196. if (component.AutoLinksUseProperties)
  197. targetBluespaceComponent.BehaviorProperties = component.AutoLinkProperties with {};
  198. GetTarget(potentialLink, targetBluespaceComponent, true);
  199. BluespaceEffect(potentialLink, targetBluespaceComponent, targetBluespaceComponent, true);
  200. }
  201. else if (component.AutoLinksBidirectional)
  202. {
  203. targetBluespaceComponent.BluespaceLinks.Add(lockerUid);
  204. }
  205. }
  206. if (component.BluespaceLinks.Count >= component.MinBluespaceLinks)
  207. break;
  208. }
  209. }
  210. // If there are no possible link targets and no links, return null
  211. if (component.BluespaceLinks.Count == 0)
  212. {
  213. if (component.MinBluespaceLinks == 0 && !init)
  214. RemComp<BluespaceLockerComponent>(lockerUid);
  215. return null;
  216. }
  217. // Attempt to select, validate, and return a link
  218. var links = component.BluespaceLinks.ToArray();
  219. var link = links[_robustRandom.Next(0, component.BluespaceLinks.Count)];
  220. if (ValidLink(lockerUid, link, component))
  221. return (link, Comp<EntityStorageComponent>(link), CompOrNull<BluespaceLockerComponent>(link));
  222. component.BluespaceLinks.Remove(link);
  223. }
  224. }
  225. private void PostClose(EntityUid uid, BluespaceLockerComponent component, ref StorageAfterCloseEvent args)
  226. {
  227. PostClose(uid, component);
  228. }
  229. private void OnDoAfter(EntityUid uid, BluespaceLockerComponent component, DoAfterEvent args)
  230. {
  231. if (args.Handled || args.Cancelled)
  232. return;
  233. PostClose(uid, component, false);
  234. args.Handled = true;
  235. }
  236. private void PostClose(EntityUid uid, BluespaceLockerComponent component, bool doDelay = true)
  237. {
  238. EntityStorageComponent? entityStorageComponent = null;
  239. int transportedEntities = 0;
  240. if (!Resolve(uid, ref entityStorageComponent))
  241. return;
  242. if (!component.BehaviorProperties.ActOnClose)
  243. return;
  244. // Do delay
  245. if (doDelay && component.BehaviorProperties.Delay > 0)
  246. {
  247. EnsureComp<DoAfterComponent>(uid);
  248. _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, uid, component.BehaviorProperties.Delay, new BluespaceLockerDoAfterEvent(), uid));
  249. return;
  250. }
  251. // Select target
  252. var target = GetTarget(uid, component);
  253. if (target == null)
  254. return;
  255. // Move contained items
  256. if (component.BehaviorProperties.TransportEntities || component.BehaviorProperties.TransportSentient)
  257. foreach (var entity in entityStorageComponent.Contents.ContainedEntities.ToArray())
  258. {
  259. if (EntityManager.HasComponent<MindContainerComponent>(entity))
  260. {
  261. if (!component.BehaviorProperties.TransportSentient)
  262. continue;
  263. _containerSystem.Insert(entity, target.Value.storageComponent.Contents);
  264. transportedEntities++;
  265. }
  266. else if (component.BehaviorProperties.TransportEntities)
  267. {
  268. _containerSystem.Insert(entity, target.Value.storageComponent.Contents);
  269. transportedEntities++;
  270. }
  271. }
  272. // Move contained air
  273. if (component.BehaviorProperties.TransportGas)
  274. {
  275. target.Value.storageComponent.Air.CopyFrom(entityStorageComponent.Air);
  276. entityStorageComponent.Air.Clear();
  277. }
  278. // Open and empty target
  279. if (target.Value.storageComponent.Open)
  280. {
  281. _entityStorage.EmptyContents(target.Value.uid, target.Value.storageComponent);
  282. _entityStorage.ReleaseGas(target.Value.uid, target.Value.storageComponent);
  283. }
  284. else
  285. {
  286. if (_weldableSystem.IsWelded(target.Value.uid))
  287. {
  288. // It gets bluespaced open...
  289. _weldableSystem.SetWeldedState(target.Value.uid, false);
  290. }
  291. LockComponent? lockComponent = null;
  292. if (Resolve(target.Value.uid, ref lockComponent, false) && lockComponent.Locked)
  293. _lockSystem.Unlock(target.Value.uid, target.Value.uid, lockComponent);
  294. _entityStorage.OpenStorage(target.Value.uid, target.Value.storageComponent);
  295. }
  296. // Bluespace effects
  297. if (component.BehaviorProperties.BluespaceEffectOnTeleportSource)
  298. BluespaceEffect(uid, component, component);
  299. if (component.BehaviorProperties.BluespaceEffectOnTeleportTarget)
  300. BluespaceEffect(target.Value.uid, component, target.Value.bluespaceLockerComponent);
  301. DestroyAfterLimit(uid, component, transportedEntities);
  302. }
  303. private void DestroyAfterLimit(EntityUid uid, BluespaceLockerComponent component, int transportedEntities)
  304. {
  305. if (component.BehaviorProperties.DestroyAfterUsesMinItemsToCountUse > transportedEntities)
  306. return;
  307. if (component.BehaviorProperties.ClearLinksEvery != -1)
  308. {
  309. component.UsesSinceLinkClear++;
  310. if (component.BehaviorProperties.ClearLinksEvery <= component.UsesSinceLinkClear)
  311. {
  312. if (component.BehaviorProperties.ClearLinksDebluespaces)
  313. foreach (var link in component.BluespaceLinks)
  314. RemComp<BluespaceLockerComponent>(link);
  315. component.BluespaceLinks.Clear();
  316. component.UsesSinceLinkClear = 0;
  317. }
  318. }
  319. if (component.BehaviorProperties.DestroyAfterUses == -1)
  320. return;
  321. component.BehaviorProperties.DestroyAfterUses--;
  322. if (component.BehaviorProperties.DestroyAfterUses > 0)
  323. return;
  324. switch (component.BehaviorProperties.DestroyType)
  325. {
  326. case BluespaceLockerDestroyType.Explode:
  327. _explosionSystem.QueueExplosion(uid.ToCoordinates().ToMap(EntityManager, _transformSystem),
  328. ExplosionSystem.DefaultExplosionPrototypeId, 4, 1, 2, uid, maxTileBreak: 0);
  329. goto case BluespaceLockerDestroyType.Delete;
  330. case BluespaceLockerDestroyType.Delete:
  331. QueueDel(uid);
  332. break;
  333. default:
  334. case BluespaceLockerDestroyType.DeleteComponent:
  335. RemComp<BluespaceLockerComponent>(uid);
  336. break;
  337. }
  338. }
  339. }