CryoPodSystem.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. using Content.Server.Administration.Logs;
  2. using Content.Server.Atmos.EntitySystems;
  3. using Content.Server.Atmos.Piping.Components;
  4. using Content.Server.Atmos.Piping.Unary.EntitySystems;
  5. using Content.Server.Body.Components;
  6. using Content.Server.Body.Systems;
  7. using Content.Server.Medical.Components;
  8. using Content.Server.NodeContainer.EntitySystems;
  9. using Content.Server.NodeContainer.NodeGroups;
  10. using Content.Server.NodeContainer.Nodes;
  11. using Content.Server.Temperature.Components;
  12. using Content.Shared.Chemistry.EntitySystems;
  13. using Content.Shared.Atmos;
  14. using Content.Shared.UserInterface;
  15. using Content.Shared.Chemistry;
  16. using Content.Shared.Chemistry.Components;
  17. using Content.Shared.Chemistry.Components.SolutionManager;
  18. using Content.Shared.Climbing.Systems;
  19. using Content.Shared.Containers.ItemSlots;
  20. using Content.Shared.Database;
  21. using Content.Shared.DoAfter;
  22. using Content.Shared.DragDrop;
  23. using Content.Shared.Emag.Systems;
  24. using Content.Shared.Examine;
  25. using Content.Shared.Interaction;
  26. using Content.Shared.Medical.Cryogenics;
  27. using Content.Shared.MedicalScanner;
  28. using Content.Shared.Power;
  29. using Content.Shared.Verbs;
  30. using Robust.Server.GameObjects;
  31. using Robust.Shared.Containers;
  32. using Robust.Shared.Timing;
  33. using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
  34. namespace Content.Server.Medical;
  35. public sealed partial class CryoPodSystem : SharedCryoPodSystem
  36. {
  37. [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
  38. [Dependency] private readonly GasCanisterSystem _gasCanisterSystem = default!;
  39. [Dependency] private readonly ClimbSystem _climbSystem = default!;
  40. [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
  41. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
  42. [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!;
  43. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  44. [Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
  45. [Dependency] private readonly SharedToolSystem _toolSystem = default!;
  46. [Dependency] private readonly IGameTiming _gameTiming = default!;
  47. [Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
  48. [Dependency] private readonly ReactiveSystem _reactiveSystem = default!;
  49. [Dependency] private readonly IAdminLogManager _adminLogger = default!;
  50. [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
  51. public override void Initialize()
  52. {
  53. base.Initialize();
  54. SubscribeLocalEvent<CryoPodComponent, ComponentInit>(OnComponentInit);
  55. SubscribeLocalEvent<CryoPodComponent, GetVerbsEvent<AlternativeVerb>>(AddAlternativeVerbs);
  56. SubscribeLocalEvent<CryoPodComponent, GotEmaggedEvent>(OnEmagged);
  57. SubscribeLocalEvent<CryoPodComponent, CryoPodDragFinished>(OnDragFinished);
  58. SubscribeLocalEvent<CryoPodComponent, CryoPodPryFinished>(OnCryoPodPryFinished);
  59. SubscribeLocalEvent<CryoPodComponent, AtmosDeviceUpdateEvent>(OnCryoPodUpdateAtmosphere);
  60. SubscribeLocalEvent<CryoPodComponent, DragDropTargetEvent>(HandleDragDropOn);
  61. SubscribeLocalEvent<CryoPodComponent, InteractUsingEvent>(OnInteractUsing);
  62. SubscribeLocalEvent<CryoPodComponent, ExaminedEvent>(OnExamined);
  63. SubscribeLocalEvent<CryoPodComponent, PowerChangedEvent>(OnPowerChanged);
  64. SubscribeLocalEvent<CryoPodComponent, GasAnalyzerScanEvent>(OnGasAnalyzed);
  65. SubscribeLocalEvent<CryoPodComponent, ActivatableUIOpenAttemptEvent>(OnActivateUIAttempt);
  66. SubscribeLocalEvent<CryoPodComponent, AfterActivatableUIOpenEvent>(OnActivateUI);
  67. SubscribeLocalEvent<CryoPodComponent, EntRemovedFromContainerMessage>(OnEjected);
  68. }
  69. public override void Update(float frameTime)
  70. {
  71. base.Update(frameTime);
  72. var curTime = _gameTiming.CurTime;
  73. var bloodStreamQuery = GetEntityQuery<BloodstreamComponent>();
  74. var metaDataQuery = GetEntityQuery<MetaDataComponent>();
  75. var itemSlotsQuery = GetEntityQuery<ItemSlotsComponent>();
  76. var fitsInDispenserQuery = GetEntityQuery<FitsInDispenserComponent>();
  77. var solutionContainerManagerQuery = GetEntityQuery<SolutionContainerManagerComponent>();
  78. var query = EntityQueryEnumerator<ActiveCryoPodComponent, CryoPodComponent>();
  79. while (query.MoveNext(out var uid, out _, out var cryoPod))
  80. {
  81. metaDataQuery.TryGetComponent(uid, out var metaDataComponent);
  82. if (curTime < cryoPod.NextInjectionTime + _metaDataSystem.GetPauseTime(uid, metaDataComponent))
  83. continue;
  84. cryoPod.NextInjectionTime = curTime + TimeSpan.FromSeconds(cryoPod.BeakerTransferTime);
  85. if (!itemSlotsQuery.TryGetComponent(uid, out var itemSlotsComponent))
  86. {
  87. continue;
  88. }
  89. var container = _itemSlotsSystem.GetItemOrNull(uid, cryoPod.SolutionContainerName, itemSlotsComponent);
  90. var patient = cryoPod.BodyContainer.ContainedEntity;
  91. if (container != null
  92. && container.Value.Valid
  93. && patient != null
  94. && fitsInDispenserQuery.TryGetComponent(container, out var fitsInDispenserComponent)
  95. && solutionContainerManagerQuery.TryGetComponent(container,
  96. out var solutionContainerManagerComponent)
  97. && _solutionContainerSystem.TryGetFitsInDispenser((container.Value, fitsInDispenserComponent, solutionContainerManagerComponent),
  98. out var containerSolution, out _))
  99. {
  100. if (!bloodStreamQuery.TryGetComponent(patient, out var bloodstream))
  101. {
  102. continue;
  103. }
  104. var solutionToInject = _solutionContainerSystem.SplitSolution(containerSolution.Value, cryoPod.BeakerTransferAmount);
  105. _bloodstreamSystem.TryAddToChemicals(patient.Value, solutionToInject, bloodstream);
  106. _reactiveSystem.DoEntityReaction(patient.Value, solutionToInject, ReactionMethod.Injection);
  107. }
  108. }
  109. }
  110. public override EntityUid? EjectBody(EntityUid uid, CryoPodComponent? cryoPodComponent)
  111. {
  112. if (!Resolve(uid, ref cryoPodComponent))
  113. return null;
  114. if (cryoPodComponent.BodyContainer.ContainedEntity is not { Valid: true } contained)
  115. return null;
  116. base.EjectBody(uid, cryoPodComponent);
  117. _climbSystem.ForciblySetClimbing(contained, uid);
  118. return contained;
  119. }
  120. #region Interaction
  121. private void HandleDragDropOn(Entity<CryoPodComponent> entity, ref DragDropTargetEvent args)
  122. {
  123. if (entity.Comp.BodyContainer.ContainedEntity != null)
  124. return;
  125. var doAfterArgs = new DoAfterArgs(EntityManager, args.User, entity.Comp.EntryDelay, new CryoPodDragFinished(), entity, target: args.Dragged, used: entity)
  126. {
  127. BreakOnDamage = true,
  128. BreakOnMove = true,
  129. NeedHand = false,
  130. };
  131. _doAfterSystem.TryStartDoAfter(doAfterArgs);
  132. args.Handled = true;
  133. }
  134. private void OnDragFinished(Entity<CryoPodComponent> entity, ref CryoPodDragFinished args)
  135. {
  136. if (args.Cancelled || args.Handled || args.Args.Target == null)
  137. return;
  138. if (InsertBody(entity.Owner, args.Args.Target.Value, entity.Comp))
  139. {
  140. if (!TryComp(entity.Owner, out CryoPodAirComponent? cryoPodAir))
  141. _adminLogger.Add(LogType.Action, LogImpact.Medium,
  142. $"{ToPrettyString(args.User)} inserted {ToPrettyString(args.Args.Target.Value)} into {ToPrettyString(entity.Owner)}");
  143. _adminLogger.Add(LogType.Action, LogImpact.Medium,
  144. $"{ToPrettyString(args.User)} inserted {ToPrettyString(args.Args.Target.Value)} into {ToPrettyString(entity.Owner)} which contains gas: {cryoPodAir!.Air.ToPrettyString():gasMix}");
  145. }
  146. args.Handled = true;
  147. }
  148. private void OnActivateUIAttempt(Entity<CryoPodComponent> entity, ref ActivatableUIOpenAttemptEvent args)
  149. {
  150. if (args.Cancelled)
  151. {
  152. return;
  153. }
  154. var containedEntity = entity.Comp.BodyContainer.ContainedEntity;
  155. if (containedEntity == null || containedEntity == args.User || !HasComp<ActiveCryoPodComponent>(entity))
  156. {
  157. args.Cancel();
  158. }
  159. }
  160. private void OnActivateUI(Entity<CryoPodComponent> entity, ref AfterActivatableUIOpenEvent args)
  161. {
  162. if (!entity.Comp.BodyContainer.ContainedEntity.HasValue)
  163. return;
  164. TryComp<TemperatureComponent>(entity.Comp.BodyContainer.ContainedEntity, out var temp);
  165. TryComp<BloodstreamComponent>(entity.Comp.BodyContainer.ContainedEntity, out var bloodstream);
  166. if (TryComp<HealthAnalyzerComponent>(entity, out var healthAnalyzer))
  167. {
  168. healthAnalyzer.ScannedEntity = entity.Comp.BodyContainer.ContainedEntity;
  169. }
  170. // TODO: This should be a state my dude
  171. _uiSystem.ServerSendUiMessage(
  172. entity.Owner,
  173. HealthAnalyzerUiKey.Key,
  174. new HealthAnalyzerScannedUserMessage(GetNetEntity(entity.Comp.BodyContainer.ContainedEntity),
  175. temp?.CurrentTemperature ?? 0,
  176. (bloodstream != null && _solutionContainerSystem.ResolveSolution(entity.Comp.BodyContainer.ContainedEntity.Value,
  177. bloodstream.BloodSolutionName, ref bloodstream.BloodSolution, out var bloodSolution))
  178. ? bloodSolution.FillFraction
  179. : 0,
  180. null,
  181. null,
  182. null
  183. ));
  184. }
  185. private void OnInteractUsing(Entity<CryoPodComponent> entity, ref InteractUsingEvent args)
  186. {
  187. if (args.Handled || !entity.Comp.Locked || entity.Comp.BodyContainer.ContainedEntity == null)
  188. return;
  189. args.Handled = _toolSystem.UseTool(args.Used, args.User, entity.Owner, entity.Comp.PryDelay, "Prying", new CryoPodPryFinished());
  190. }
  191. private void OnExamined(Entity<CryoPodComponent> entity, ref ExaminedEvent args)
  192. {
  193. var container = _itemSlotsSystem.GetItemOrNull(entity.Owner, entity.Comp.SolutionContainerName);
  194. if (args.IsInDetailsRange && container != null && _solutionContainerSystem.TryGetFitsInDispenser(container.Value, out _, out var containerSolution))
  195. {
  196. using (args.PushGroup(nameof(CryoPodComponent)))
  197. {
  198. args.PushMarkup(Loc.GetString("cryo-pod-examine", ("beaker", Name(container.Value))));
  199. if (containerSolution.Volume == 0)
  200. {
  201. args.PushMarkup(Loc.GetString("cryo-pod-empty-beaker"));
  202. }
  203. }
  204. }
  205. }
  206. private void OnPowerChanged(Entity<CryoPodComponent> entity, ref PowerChangedEvent args)
  207. {
  208. // Needed to avoid adding/removing components on a deleted entity
  209. if (Terminating(entity))
  210. {
  211. return;
  212. }
  213. if (args.Powered)
  214. {
  215. EnsureComp<ActiveCryoPodComponent>(entity);
  216. }
  217. else
  218. {
  219. RemComp<ActiveCryoPodComponent>(entity);
  220. _uiSystem.CloseUi(entity.Owner, HealthAnalyzerUiKey.Key);
  221. }
  222. UpdateAppearance(entity.Owner, entity.Comp);
  223. }
  224. #endregion
  225. #region Atmos handler
  226. private void OnCryoPodUpdateAtmosphere(Entity<CryoPodComponent> entity, ref AtmosDeviceUpdateEvent args)
  227. {
  228. if (!_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PortablePipeNode? portNode))
  229. return;
  230. if (!TryComp(entity, out CryoPodAirComponent? cryoPodAir))
  231. return;
  232. _atmosphereSystem.React(cryoPodAir.Air, portNode);
  233. if (portNode.NodeGroup is PipeNet { NodeCount: > 1 } net)
  234. {
  235. _gasCanisterSystem.MixContainerWithPipeNet(cryoPodAir.Air, net.Air);
  236. }
  237. }
  238. private void OnGasAnalyzed(Entity<CryoPodComponent> entity, ref GasAnalyzerScanEvent args)
  239. {
  240. if (!TryComp(entity, out CryoPodAirComponent? cryoPodAir))
  241. return;
  242. args.GasMixtures ??= new List<(string, GasMixture?)>();
  243. args.GasMixtures.Add((Name(entity.Owner), cryoPodAir.Air));
  244. // If it's connected to a port, include the port side
  245. // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
  246. if (_nodeContainer.TryGetNode(entity.Owner, entity.Comp.PortName, out PipeNode? port) && port.Air.Volume != 0f)
  247. {
  248. var portAirLocal = port.Air.Clone();
  249. portAirLocal.Multiply(port.Volume / port.Air.Volume);
  250. portAirLocal.Volume = port.Volume;
  251. args.GasMixtures.Add((entity.Comp.PortName, portAirLocal));
  252. }
  253. }
  254. private void OnEjected(Entity<CryoPodComponent> cryoPod, ref EntRemovedFromContainerMessage args)
  255. {
  256. if (TryComp<HealthAnalyzerComponent>(cryoPod.Owner, out var healthAnalyzer))
  257. {
  258. healthAnalyzer.ScannedEntity = null;
  259. }
  260. // if body is ejected - no need to display health-analyzer
  261. _uiSystem.CloseUi(cryoPod.Owner, HealthAnalyzerUiKey.Key);
  262. }
  263. #endregion
  264. }