GasMixerSystem.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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.Trinary.Components;
  5. using Content.Server.NodeContainer;
  6. using Content.Server.NodeContainer.EntitySystems;
  7. using Content.Server.NodeContainer.Nodes;
  8. using Content.Shared.Atmos;
  9. using Content.Shared.Atmos.Piping;
  10. using Content.Shared.Atmos.Piping.Components;
  11. using Content.Shared.Atmos.Piping.Trinary.Components;
  12. using Content.Shared.Audio;
  13. using Content.Shared.Database;
  14. using Content.Shared.Interaction;
  15. using Content.Shared.Popups;
  16. using JetBrains.Annotations;
  17. using Robust.Server.GameObjects;
  18. using Robust.Shared.Player;
  19. namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
  20. {
  21. [UsedImplicitly]
  22. public sealed class GasMixerSystem : EntitySystem
  23. {
  24. [Dependency] private UserInterfaceSystem _userInterfaceSystem = default!;
  25. [Dependency] private IAdminLogManager _adminLogger = default!;
  26. [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
  27. [Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
  28. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  29. [Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
  30. [Dependency] private readonly SharedPopupSystem _popup = default!;
  31. public override void Initialize()
  32. {
  33. base.Initialize();
  34. SubscribeLocalEvent<GasMixerComponent, ComponentInit>(OnInit);
  35. SubscribeLocalEvent<GasMixerComponent, AtmosDeviceUpdateEvent>(OnMixerUpdated);
  36. SubscribeLocalEvent<GasMixerComponent, ActivateInWorldEvent>(OnMixerActivate);
  37. SubscribeLocalEvent<GasMixerComponent, GasAnalyzerScanEvent>(OnMixerAnalyzed);
  38. // Bound UI subscriptions
  39. SubscribeLocalEvent<GasMixerComponent, GasMixerChangeOutputPressureMessage>(OnOutputPressureChangeMessage);
  40. SubscribeLocalEvent<GasMixerComponent, GasMixerChangeNodePercentageMessage>(OnChangeNodePercentageMessage);
  41. SubscribeLocalEvent<GasMixerComponent, GasMixerToggleStatusMessage>(OnToggleStatusMessage);
  42. SubscribeLocalEvent<GasMixerComponent, AtmosDeviceDisabledEvent>(OnMixerLeaveAtmosphere);
  43. }
  44. private void OnInit(EntityUid uid, GasMixerComponent mixer, ComponentInit args)
  45. {
  46. UpdateAppearance(uid, mixer);
  47. }
  48. private void OnMixerUpdated(EntityUid uid, GasMixerComponent mixer, ref AtmosDeviceUpdateEvent args)
  49. {
  50. // TODO ATMOS: Cache total moles since it's expensive.
  51. if (!mixer.Enabled
  52. || !_nodeContainer.TryGetNodes(uid, mixer.InletOneName, mixer.InletTwoName, mixer.OutletName, out PipeNode? inletOne, out PipeNode? inletTwo, out PipeNode? outlet))
  53. {
  54. _ambientSoundSystem.SetAmbience(uid, false);
  55. return;
  56. }
  57. var outputStartingPressure = outlet.Air.Pressure;
  58. if (outputStartingPressure >= mixer.TargetPressure)
  59. return; // Target reached, no need to mix.
  60. var generalTransfer = (mixer.TargetPressure - outputStartingPressure) * outlet.Air.Volume / Atmospherics.R;
  61. var transferMolesOne = inletOne.Air.Temperature > 0 ? mixer.InletOneConcentration * generalTransfer / inletOne.Air.Temperature : 0f;
  62. var transferMolesTwo = inletTwo.Air.Temperature > 0 ? mixer.InletTwoConcentration * generalTransfer / inletTwo.Air.Temperature : 0f;
  63. if (mixer.InletTwoConcentration <= 0f)
  64. {
  65. if (inletOne.Air.Temperature <= 0f)
  66. return;
  67. transferMolesOne = MathF.Min(transferMolesOne, inletOne.Air.TotalMoles);
  68. transferMolesTwo = 0f;
  69. }
  70. else if (mixer.InletOneConcentration <= 0)
  71. {
  72. if (inletTwo.Air.Temperature <= 0f)
  73. return;
  74. transferMolesOne = 0f;
  75. transferMolesTwo = MathF.Min(transferMolesTwo, inletTwo.Air.TotalMoles);
  76. }
  77. else
  78. {
  79. if (inletOne.Air.Temperature <= 0f || inletTwo.Air.Temperature <= 0f)
  80. return;
  81. if (transferMolesOne <= 0 || transferMolesTwo <= 0)
  82. {
  83. _ambientSoundSystem.SetAmbience(uid, false);
  84. return;
  85. }
  86. if (inletOne.Air.TotalMoles < transferMolesOne || inletTwo.Air.TotalMoles < transferMolesTwo)
  87. {
  88. var ratio = MathF.Min(inletOne.Air.TotalMoles / transferMolesOne, inletTwo.Air.TotalMoles / transferMolesTwo);
  89. transferMolesOne *= ratio;
  90. transferMolesTwo *= ratio;
  91. }
  92. }
  93. // Actually transfer the gas now.
  94. var transferred = false;
  95. if (transferMolesOne > 0f)
  96. {
  97. transferred = true;
  98. var removed = inletOne.Air.Remove(transferMolesOne);
  99. _atmosphereSystem.Merge(outlet.Air, removed);
  100. }
  101. if (transferMolesTwo > 0f)
  102. {
  103. transferred = true;
  104. var removed = inletTwo.Air.Remove(transferMolesTwo);
  105. _atmosphereSystem.Merge(outlet.Air, removed);
  106. }
  107. if (transferred)
  108. _ambientSoundSystem.SetAmbience(uid, true);
  109. }
  110. private void OnMixerLeaveAtmosphere(EntityUid uid, GasMixerComponent mixer, ref AtmosDeviceDisabledEvent args)
  111. {
  112. mixer.Enabled = false;
  113. DirtyUI(uid, mixer);
  114. UpdateAppearance(uid, mixer);
  115. _userInterfaceSystem.CloseUi(uid, GasFilterUiKey.Key);
  116. }
  117. private void OnMixerActivate(EntityUid uid, GasMixerComponent mixer, ActivateInWorldEvent args)
  118. {
  119. if (args.Handled || !args.Complex)
  120. return;
  121. if (!EntityManager.TryGetComponent(args.User, out ActorComponent? actor))
  122. return;
  123. if (Transform(uid).Anchored)
  124. {
  125. _userInterfaceSystem.OpenUi(uid, GasMixerUiKey.Key, actor.PlayerSession);
  126. DirtyUI(uid, mixer);
  127. }
  128. else
  129. {
  130. _popup.PopupCursor(Loc.GetString("comp-gas-mixer-ui-needs-anchor"), args.User);
  131. }
  132. args.Handled = true;
  133. }
  134. private void DirtyUI(EntityUid uid, GasMixerComponent? mixer)
  135. {
  136. if (!Resolve(uid, ref mixer))
  137. return;
  138. _userInterfaceSystem.SetUiState(uid, GasMixerUiKey.Key,
  139. new GasMixerBoundUserInterfaceState(EntityManager.GetComponent<MetaDataComponent>(uid).EntityName, mixer.TargetPressure, mixer.Enabled, mixer.InletOneConcentration));
  140. }
  141. private void UpdateAppearance(EntityUid uid, GasMixerComponent? mixer = null, AppearanceComponent? appearance = null)
  142. {
  143. if (!Resolve(uid, ref mixer, ref appearance, false))
  144. return;
  145. _appearance.SetData(uid, FilterVisuals.Enabled, mixer.Enabled, appearance);
  146. }
  147. private void OnToggleStatusMessage(EntityUid uid, GasMixerComponent mixer, GasMixerToggleStatusMessage args)
  148. {
  149. mixer.Enabled = args.Enabled;
  150. _adminLogger.Add(LogType.AtmosPowerChanged, LogImpact.Medium,
  151. $"{ToPrettyString(args.Actor):player} set the power on {ToPrettyString(uid):device} to {args.Enabled}");
  152. DirtyUI(uid, mixer);
  153. UpdateAppearance(uid, mixer);
  154. }
  155. private void OnOutputPressureChangeMessage(EntityUid uid, GasMixerComponent mixer, GasMixerChangeOutputPressureMessage args)
  156. {
  157. mixer.TargetPressure = Math.Clamp(args.Pressure, 0f, mixer.MaxTargetPressure);
  158. _adminLogger.Add(LogType.AtmosPressureChanged, LogImpact.Medium,
  159. $"{ToPrettyString(args.Actor):player} set the pressure on {ToPrettyString(uid):device} to {args.Pressure}kPa");
  160. DirtyUI(uid, mixer);
  161. }
  162. private void OnChangeNodePercentageMessage(EntityUid uid, GasMixerComponent mixer,
  163. GasMixerChangeNodePercentageMessage args)
  164. {
  165. float nodeOne = Math.Clamp(args.NodeOne, 0f, 100.0f) / 100.0f;
  166. mixer.InletOneConcentration = nodeOne;
  167. mixer.InletTwoConcentration = 1.0f - mixer.InletOneConcentration;
  168. _adminLogger.Add(LogType.AtmosRatioChanged, LogImpact.Medium,
  169. $"{EntityManager.ToPrettyString(args.Actor):player} set the ratio on {EntityManager.ToPrettyString(uid):device} to {mixer.InletOneConcentration}:{mixer.InletTwoConcentration}");
  170. DirtyUI(uid, mixer);
  171. }
  172. /// <summary>
  173. /// Returns the gas mixture for the gas analyzer
  174. /// </summary>
  175. private void OnMixerAnalyzed(EntityUid uid, GasMixerComponent component, GasAnalyzerScanEvent args)
  176. {
  177. args.GasMixtures ??= new List<(string, GasMixture?)>();
  178. // multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
  179. if (_nodeContainer.TryGetNode(uid, component.InletOneName, out PipeNode? inletOne) && inletOne.Air.Volume != 0f)
  180. {
  181. var inletOneAirLocal = inletOne.Air.Clone();
  182. inletOneAirLocal.Multiply(inletOne.Volume / inletOne.Air.Volume);
  183. inletOneAirLocal.Volume = inletOne.Volume;
  184. args.GasMixtures.Add(($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOneAirLocal));
  185. }
  186. if (_nodeContainer.TryGetNode(uid, component.InletTwoName, out PipeNode? inletTwo) && inletTwo.Air.Volume != 0f)
  187. {
  188. var inletTwoAirLocal = inletTwo.Air.Clone();
  189. inletTwoAirLocal.Multiply(inletTwo.Volume / inletTwo.Air.Volume);
  190. inletTwoAirLocal.Volume = inletTwo.Volume;
  191. args.GasMixtures.Add(($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwoAirLocal));
  192. }
  193. if (_nodeContainer.TryGetNode(uid, component.OutletName, out PipeNode? outlet) && outlet.Air.Volume != 0f)
  194. {
  195. var outletAirLocal = outlet.Air.Clone();
  196. outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume);
  197. outletAirLocal.Volume = outlet.Volume;
  198. args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal));
  199. }
  200. args.DeviceFlipped = inletOne != null && inletTwo != null && inletOne.CurrentPipeDirection.ToDirection() == inletTwo.CurrentPipeDirection.ToDirection().GetClockwise90Degrees();
  201. }
  202. }
  203. }