GasVentPumpSystem.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. using Content.Server.Atmos.EntitySystems;
  2. using Content.Server.Atmos.Monitor.Systems;
  3. using Content.Server.Atmos.Piping.Components;
  4. using Content.Server.Atmos.Piping.Unary.Components;
  5. using Content.Server.DeviceLinking.Events;
  6. using Content.Server.DeviceLinking.Systems;
  7. using Content.Server.DeviceNetwork;
  8. using Content.Server.DeviceNetwork.Components;
  9. using Content.Server.DeviceNetwork.Systems;
  10. using Content.Server.NodeContainer.EntitySystems;
  11. using Content.Server.NodeContainer.Nodes;
  12. using Content.Server.Power.Components;
  13. using Content.Server.Power.EntitySystems;
  14. using Content.Shared.Administration.Logs;
  15. using Content.Shared.Atmos;
  16. using Content.Shared.Atmos.Monitor;
  17. using Content.Shared.Atmos.Piping.Components;
  18. using Content.Shared.Atmos.Piping.Unary;
  19. using Content.Shared.Atmos.Piping.Unary.Components;
  20. using Content.Shared.Atmos.Visuals;
  21. using Content.Shared.Audio;
  22. using Content.Shared.Database;
  23. using Content.Shared.DeviceNetwork;
  24. using Content.Shared.DoAfter;
  25. using Content.Shared.Examine;
  26. using Content.Shared.Interaction;
  27. using Content.Shared.Power;
  28. using Content.Shared.Tools.Systems;
  29. using JetBrains.Annotations;
  30. using Robust.Shared.Timing;
  31. namespace Content.Server.Atmos.Piping.Unary.EntitySystems
  32. {
  33. [UsedImplicitly]
  34. public sealed class GasVentPumpSystem : EntitySystem
  35. {
  36. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  37. [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
  38. [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!;
  39. [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
  40. [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
  41. [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
  42. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  43. [Dependency] private readonly WeldableSystem _weldable = default!;
  44. [Dependency] private readonly SharedToolSystem _toolSystem = default!;
  45. [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
  46. [Dependency] private readonly IGameTiming _timing = default!;
  47. [Dependency] private readonly PowerReceiverSystem _powerReceiverSystem = default!;
  48. public override void Initialize()
  49. {
  50. base.Initialize();
  51. SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceUpdateEvent>(OnGasVentPumpUpdated);
  52. SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceDisabledEvent>(OnGasVentPumpLeaveAtmosphere);
  53. SubscribeLocalEvent<GasVentPumpComponent, AtmosDeviceEnabledEvent>(OnGasVentPumpEnterAtmosphere);
  54. SubscribeLocalEvent<GasVentPumpComponent, AtmosAlarmEvent>(OnAtmosAlarm);
  55. SubscribeLocalEvent<GasVentPumpComponent, PowerChangedEvent>(OnPowerChanged);
  56. SubscribeLocalEvent<GasVentPumpComponent, DeviceNetworkPacketEvent>(OnPacketRecv);
  57. SubscribeLocalEvent<GasVentPumpComponent, ComponentInit>(OnInit);
  58. SubscribeLocalEvent<GasVentPumpComponent, ExaminedEvent>(OnExamine);
  59. SubscribeLocalEvent<GasVentPumpComponent, SignalReceivedEvent>(OnSignalReceived);
  60. SubscribeLocalEvent<GasVentPumpComponent, GasAnalyzerScanEvent>(OnAnalyzed);
  61. SubscribeLocalEvent<GasVentPumpComponent, WeldableChangedEvent>(OnWeldChanged);
  62. SubscribeLocalEvent<GasVentPumpComponent, InteractUsingEvent>(OnInteractUsing);
  63. SubscribeLocalEvent<GasVentPumpComponent, VentScrewedDoAfterEvent>(OnVentScrewed);
  64. }
  65. private void OnGasVentPumpUpdated(EntityUid uid, GasVentPumpComponent vent, ref AtmosDeviceUpdateEvent args)
  66. {
  67. //Bingo waz here
  68. if (_weldable.IsWelded(uid))
  69. return;
  70. if (!_powerReceiverSystem.IsPowered(uid))
  71. return;
  72. var nodeName = vent.PumpDirection switch
  73. {
  74. VentPumpDirection.Releasing => vent.Inlet,
  75. VentPumpDirection.Siphoning => vent.Outlet,
  76. _ => throw new ArgumentOutOfRangeException()
  77. };
  78. if (!vent.Enabled || !_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe))
  79. {
  80. return;
  81. }
  82. var environment = _atmosphereSystem.GetContainingMixture(uid, args.Grid, args.Map, true, true);
  83. // We're in an air-blocked tile... Do nothing.
  84. if (environment == null)
  85. {
  86. return;
  87. }
  88. // If the lockout has expired, disable it.
  89. if (vent.IsPressureLockoutManuallyDisabled && _timing.CurTime >= vent.ManualLockoutReenabledAt)
  90. {
  91. vent.IsPressureLockoutManuallyDisabled = false;
  92. }
  93. var timeDelta = args.dt;
  94. var pressureDelta = timeDelta * vent.TargetPressureChange;
  95. var lockout = (environment.Pressure < vent.UnderPressureLockoutThreshold) && !vent.IsPressureLockoutManuallyDisabled;
  96. if (vent.UnderPressureLockout != lockout) // update visuals only if this changes
  97. {
  98. vent.UnderPressureLockout = lockout;
  99. UpdateState(uid, vent);
  100. }
  101. if (vent.PumpDirection == VentPumpDirection.Releasing && pipe.Air.Pressure > 0)
  102. {
  103. if (environment.Pressure > vent.MaxPressure)
  104. return;
  105. if ((vent.PressureChecks & VentPressureBound.ExternalBound) != 0)
  106. {
  107. // Vents cannot supply high pressures from an almost empty pipe, instead it's proportional to the pipe
  108. // pressure, up to a limit.
  109. // This also means supply pipe pressure indicates minimum pressure on the station, with lower pressure
  110. // sections getting air first.
  111. var supplyPressure = MathF.Min(pipe.Air.Pressure * vent.PumpPower, vent.ExternalPressureBound);
  112. // Calculate the ratio of supply pressure to current pressure.
  113. pressureDelta = MathF.Min(pressureDelta, supplyPressure - environment.Pressure);
  114. }
  115. if (pressureDelta <= 0)
  116. return;
  117. // how many moles to transfer to change external pressure by pressureDelta
  118. // (ignoring temperature differences because I am lazy)
  119. var transferMoles = pressureDelta * environment.Volume / (pipe.Air.Temperature * Atmospherics.R);
  120. // Only run if the device is under lockout and not being overriden
  121. if (vent.UnderPressureLockout & !vent.PressureLockoutOverride & !vent.IsPressureLockoutManuallyDisabled)
  122. {
  123. // Leak only a small amount of gas as a proportion of supply pipe pressure.
  124. var pipeDelta = pipe.Air.Pressure - environment.Pressure;
  125. transferMoles = (float)timeDelta * pipeDelta * vent.UnderPressureLockoutLeaking;
  126. if (transferMoles < 0.0)
  127. return;
  128. }
  129. // limit transferMoles so the source doesn't go below its bound.
  130. if ((vent.PressureChecks & VentPressureBound.InternalBound) != 0)
  131. {
  132. var internalDelta = pipe.Air.Pressure - vent.InternalPressureBound;
  133. if (internalDelta <= 0)
  134. return;
  135. var maxTransfer = internalDelta * pipe.Air.Volume / (pipe.Air.Temperature * Atmospherics.R);
  136. transferMoles = MathF.Min(transferMoles, maxTransfer);
  137. }
  138. _atmosphereSystem.Merge(environment, pipe.Air.Remove(transferMoles));
  139. }
  140. else if (vent.PumpDirection == VentPumpDirection.Siphoning && environment.Pressure > 0)
  141. {
  142. if (pipe.Air.Pressure > vent.MaxPressure)
  143. return;
  144. if ((vent.PressureChecks & VentPressureBound.InternalBound) != 0)
  145. pressureDelta = MathF.Min(pressureDelta, vent.InternalPressureBound - pipe.Air.Pressure);
  146. if (pressureDelta <= 0)
  147. return;
  148. // how many moles to transfer to change internal pressure by pressureDelta
  149. // (ignoring temperature differences because I am lazy)
  150. var transferMoles = pressureDelta * pipe.Air.Volume / (environment.Temperature * Atmospherics.R);
  151. // limit transferMoles so the source doesn't go below its bound.
  152. if ((vent.PressureChecks & VentPressureBound.ExternalBound) != 0)
  153. {
  154. var externalDelta = environment.Pressure - vent.ExternalPressureBound;
  155. if (externalDelta <= 0)
  156. return;
  157. var maxTransfer = externalDelta * environment.Volume / (environment.Temperature * Atmospherics.R);
  158. transferMoles = MathF.Min(transferMoles, maxTransfer);
  159. }
  160. _atmosphereSystem.Merge(pipe.Air, environment.Remove(transferMoles));
  161. }
  162. }
  163. private void OnGasVentPumpLeaveAtmosphere(EntityUid uid, GasVentPumpComponent component, ref AtmosDeviceDisabledEvent args)
  164. {
  165. UpdateState(uid, component);
  166. }
  167. private void OnGasVentPumpEnterAtmosphere(EntityUid uid, GasVentPumpComponent component, ref AtmosDeviceEnabledEvent args)
  168. {
  169. UpdateState(uid, component);
  170. }
  171. private void OnAtmosAlarm(EntityUid uid, GasVentPumpComponent component, AtmosAlarmEvent args)
  172. {
  173. if (args.AlarmType == AtmosAlarmType.Danger)
  174. {
  175. component.Enabled = false;
  176. }
  177. else if (args.AlarmType == AtmosAlarmType.Normal)
  178. {
  179. component.Enabled = true;
  180. }
  181. UpdateState(uid, component);
  182. }
  183. private void OnPowerChanged(EntityUid uid, GasVentPumpComponent component, ref PowerChangedEvent args)
  184. {
  185. UpdateState(uid, component);
  186. }
  187. private void OnPacketRecv(EntityUid uid, GasVentPumpComponent component, DeviceNetworkPacketEvent args)
  188. {
  189. if (!EntityManager.TryGetComponent(uid, out DeviceNetworkComponent? netConn)
  190. || !args.Data.TryGetValue(DeviceNetworkConstants.Command, out var cmd))
  191. return;
  192. var payload = new NetworkPayload();
  193. switch (cmd)
  194. {
  195. case AtmosDeviceNetworkSystem.SyncData:
  196. payload.Add(DeviceNetworkConstants.Command, AtmosDeviceNetworkSystem.SyncData);
  197. payload.Add(AtmosDeviceNetworkSystem.SyncData, component.ToAirAlarmData());
  198. _deviceNetSystem.QueuePacket(uid, args.SenderAddress, payload, device: netConn);
  199. return;
  200. case DeviceNetworkConstants.CmdSetState:
  201. if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData))
  202. break;
  203. var previous = component.ToAirAlarmData();
  204. if (previous.Enabled != setData.Enabled)
  205. {
  206. string enabled = setData.Enabled ? "enabled" : "disabled" ;
  207. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}");
  208. }
  209. if (previous.PumpDirection != setData.PumpDirection)
  210. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}");
  211. if (previous.PressureChecks != setData.PressureChecks)
  212. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure check changed to {setData.PressureChecks}");
  213. if (previous.ExternalPressureBound != setData.ExternalPressureBound)
  214. {
  215. _adminLogger.Add(
  216. LogType.AtmosDeviceSetting,
  217. LogImpact.Medium,
  218. $"{ToPrettyString(uid)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa"
  219. );
  220. }
  221. if (previous.InternalPressureBound != setData.InternalPressureBound)
  222. {
  223. _adminLogger.Add(
  224. LogType.AtmosDeviceSetting,
  225. LogImpact.Medium,
  226. $"{ToPrettyString(uid)} internal pressure bound changed from {previous.InternalPressureBound} kPa to {setData.InternalPressureBound} kPa"
  227. );
  228. }
  229. if (previous.PressureLockoutOverride != setData.PressureLockoutOverride)
  230. {
  231. string enabled = setData.PressureLockoutOverride ? "enabled" : "disabled" ;
  232. _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure lockout override {enabled}");
  233. }
  234. component.FromAirAlarmData(setData);
  235. UpdateState(uid, component);
  236. return;
  237. }
  238. }
  239. private void OnInit(EntityUid uid, GasVentPumpComponent component, ComponentInit args)
  240. {
  241. if (component.CanLink)
  242. _signalSystem.EnsureSinkPorts(uid, component.PressurizePort, component.DepressurizePort);
  243. }
  244. private void OnSignalReceived(EntityUid uid, GasVentPumpComponent component, ref SignalReceivedEvent args)
  245. {
  246. if (!component.CanLink)
  247. return;
  248. if (args.Port == component.PressurizePort)
  249. {
  250. component.PumpDirection = VentPumpDirection.Releasing;
  251. component.ExternalPressureBound = component.PressurizePressure;
  252. component.PressureChecks = VentPressureBound.ExternalBound;
  253. UpdateState(uid, component);
  254. }
  255. else if (args.Port == component.DepressurizePort)
  256. {
  257. component.PumpDirection = VentPumpDirection.Siphoning;
  258. component.ExternalPressureBound = component.DepressurizePressure;
  259. component.PressureChecks = VentPressureBound.ExternalBound;
  260. UpdateState(uid, component);
  261. }
  262. }
  263. private void UpdateState(EntityUid uid, GasVentPumpComponent vent, AppearanceComponent? appearance = null)
  264. {
  265. if (!Resolve(uid, ref appearance, false))
  266. return;
  267. _ambientSoundSystem.SetAmbience(uid, true);
  268. if (_weldable.IsWelded(uid))
  269. {
  270. _ambientSoundSystem.SetAmbience(uid, false);
  271. _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Welded, appearance);
  272. }
  273. else if (!_powerReceiverSystem.IsPowered(uid) || !vent.Enabled)
  274. {
  275. _ambientSoundSystem.SetAmbience(uid, false);
  276. _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Off, appearance);
  277. }
  278. else if (vent.PumpDirection == VentPumpDirection.Releasing)
  279. {
  280. if (vent.UnderPressureLockout & !vent.PressureLockoutOverride & !vent.IsPressureLockoutManuallyDisabled)
  281. _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Lockout, appearance);
  282. else
  283. _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.Out, appearance);
  284. }
  285. else if (vent.PumpDirection == VentPumpDirection.Siphoning)
  286. {
  287. _appearance.SetData(uid, VentPumpVisuals.State, VentPumpState.In, appearance);
  288. }
  289. }
  290. private void OnExamine(EntityUid uid, GasVentPumpComponent component, ExaminedEvent args)
  291. {
  292. if (!TryComp<GasVentPumpComponent>(uid, out var pumpComponent))
  293. return;
  294. if (args.IsInDetailsRange)
  295. {
  296. if (pumpComponent.PumpDirection == VentPumpDirection.Releasing & pumpComponent.UnderPressureLockout & !pumpComponent.PressureLockoutOverride & !pumpComponent.IsPressureLockoutManuallyDisabled)
  297. {
  298. args.PushMarkup(Loc.GetString("gas-vent-pump-uvlo"));
  299. }
  300. }
  301. }
  302. /// <summary>
  303. /// Returns the gas mixture for the gas analyzer
  304. /// </summary>
  305. private void OnAnalyzed(EntityUid uid, GasVentPumpComponent component, GasAnalyzerScanEvent args)
  306. {
  307. args.GasMixtures ??= new List<(string, GasMixture?)>();
  308. // these are both called pipe, above it switches using this so I duplicated that...?
  309. var nodeName = component.PumpDirection switch
  310. {
  311. VentPumpDirection.Releasing => component.Inlet,
  312. VentPumpDirection.Siphoning => component.Outlet,
  313. _ => throw new ArgumentOutOfRangeException()
  314. };
  315. // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
  316. if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe) && pipe.Air.Volume != 0f)
  317. {
  318. var pipeAirLocal = pipe.Air.Clone();
  319. pipeAirLocal.Multiply(pipe.Volume / pipe.Air.Volume);
  320. pipeAirLocal.Volume = pipe.Volume;
  321. args.GasMixtures.Add((nodeName, pipeAirLocal));
  322. }
  323. }
  324. private void OnWeldChanged(EntityUid uid, GasVentPumpComponent component, ref WeldableChangedEvent args)
  325. {
  326. UpdateState(uid, component);
  327. }
  328. private void OnInteractUsing(EntityUid uid, GasVentPumpComponent component, InteractUsingEvent args)
  329. {
  330. if (args.Handled
  331. || component.UnderPressureLockout == false
  332. || !_toolSystem.HasQuality(args.Used, "Screwing")
  333. || !Transform(uid).Anchored
  334. )
  335. {
  336. return;
  337. }
  338. args.Handled = true;
  339. _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ManualLockoutDisableDoAfter, new VentScrewedDoAfterEvent(), uid, uid, args.Used));
  340. }
  341. private void OnVentScrewed(EntityUid uid, GasVentPumpComponent component, VentScrewedDoAfterEvent args)
  342. {
  343. component.ManualLockoutReenabledAt = _timing.CurTime + component.ManualLockoutDisabledDuration;
  344. component.IsPressureLockoutManuallyDisabled = true;
  345. }
  346. }
  347. }