FirelockSystem.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. using Content.Server.Atmos.Components;
  2. using Content.Server.Atmos.EntitySystems;
  3. using Content.Server.Atmos.Monitor.Systems;
  4. using Content.Server.Power.Components;
  5. using Content.Server.Power.EntitySystems;
  6. using Content.Server.Shuttles.Components;
  7. using Content.Shared.Atmos;
  8. using Content.Shared.Atmos.Monitor;
  9. using Content.Shared.Doors.Components;
  10. using Content.Shared.Doors.Systems;
  11. using Content.Shared.Power;
  12. using Robust.Server.GameObjects;
  13. using Robust.Shared.Map.Components;
  14. namespace Content.Server.Doors.Systems
  15. {
  16. public sealed class FirelockSystem : SharedFirelockSystem
  17. {
  18. [Dependency] private readonly SharedDoorSystem _doorSystem = default!;
  19. [Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
  20. [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
  21. [Dependency] private readonly SharedMapSystem _mapping = default!;
  22. [Dependency] private readonly PointLightSystem _pointLight = default!;
  23. private const int UpdateInterval = 30;
  24. private int _accumulatedTicks;
  25. public override void Initialize()
  26. {
  27. base.Initialize();
  28. SubscribeLocalEvent<FirelockComponent, AtmosAlarmEvent>(OnAtmosAlarm);
  29. SubscribeLocalEvent<FirelockComponent, PowerChangedEvent>(PowerChanged);
  30. }
  31. private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args)
  32. {
  33. component.Powered = args.Powered;
  34. Dirty(uid, component);
  35. }
  36. public override void Update(float frameTime)
  37. {
  38. _accumulatedTicks += 1;
  39. if (_accumulatedTicks < UpdateInterval)
  40. return;
  41. _accumulatedTicks = 0;
  42. var airtightQuery = GetEntityQuery<AirtightComponent>();
  43. var appearanceQuery = GetEntityQuery<AppearanceComponent>();
  44. var xformQuery = GetEntityQuery<TransformComponent>();
  45. var pointLightQuery = GetEntityQuery<PointLightComponent>();
  46. var query = EntityQueryEnumerator<FirelockComponent, DoorComponent>();
  47. while (query.MoveNext(out var uid, out var firelock, out var door))
  48. {
  49. // only bother to check pressure on doors that are some variation of closed.
  50. if (door.State != DoorState.Closed
  51. && door.State != DoorState.Welded
  52. && door.State != DoorState.Denying)
  53. {
  54. continue;
  55. }
  56. if (airtightQuery.TryGetComponent(uid, out var airtight)
  57. && xformQuery.TryGetComponent(uid, out var xform)
  58. && appearanceQuery.TryGetComponent(uid, out var appearance))
  59. {
  60. var (pressure, fire) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery);
  61. _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
  62. firelock.Temperature = fire;
  63. firelock.Pressure = pressure;
  64. Dirty(uid, firelock);
  65. if (pointLightQuery.TryComp(uid, out var pointLight))
  66. {
  67. _pointLight.SetEnabled(uid, fire | pressure, pointLight);
  68. }
  69. }
  70. }
  71. }
  72. private void OnAtmosAlarm(EntityUid uid, FirelockComponent component, AtmosAlarmEvent args)
  73. {
  74. if (!this.IsPowered(uid, EntityManager))
  75. return;
  76. if (!TryComp<DoorComponent>(uid, out var doorComponent))
  77. return;
  78. if (args.AlarmType == AtmosAlarmType.Normal)
  79. {
  80. if (doorComponent.State == DoorState.Closed)
  81. _doorSystem.TryOpen(uid);
  82. }
  83. else if (args.AlarmType == AtmosAlarmType.Danger)
  84. {
  85. EmergencyPressureStop(uid, component, doorComponent);
  86. }
  87. }
  88. public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock)
  89. {
  90. var query = GetEntityQuery<AirtightComponent>();
  91. if (query.TryGetComponent(uid, out AirtightComponent? airtight))
  92. return CheckPressureAndFire(uid, firelock, Transform(uid), airtight, query);
  93. return (false, false);
  94. }
  95. public (bool Pressure, bool Fire) CheckPressureAndFire(
  96. EntityUid uid,
  97. FirelockComponent firelock,
  98. TransformComponent xform,
  99. AirtightComponent airtight,
  100. EntityQuery<AirtightComponent> airtightQuery)
  101. {
  102. if (!airtight.AirBlocked)
  103. return (false, false);
  104. if (TryComp(uid, out DockingComponent? dock) && dock.Docked)
  105. {
  106. // Currently docking automatically opens the doors. But maybe in future, check the pressure difference before opening doors?
  107. return (false, false);
  108. }
  109. if (!HasComp<GridAtmosphereComponent>(xform.ParentUid))
  110. return (false, false);
  111. var grid = Comp<MapGridComponent>(xform.ParentUid);
  112. var pos = _mapping.CoordinatesToTile(xform.ParentUid, grid, xform.Coordinates);
  113. var minPressure = float.MaxValue;
  114. var maxPressure = float.MinValue;
  115. var minTemperature = float.MaxValue;
  116. var maxTemperature = float.MinValue;
  117. var holdingFire = false;
  118. var holdingPressure = false;
  119. // We cannot simply use `_atmosSystem.GetAdjacentTileMixtures` because of how the `includeBlocked` option
  120. // works, we want to ignore the firelock's blocking, while including blockers on other tiles.
  121. // GetAdjacentTileMixtures also ignores empty/non-existent tiles, which we don't want. Additionally, for
  122. // edge-fire locks, we only want to enumerate over a single directions. So AFAIK there is no nice way of
  123. // achieving all this using existing atmos functions, and the functionality is too specialized to bother
  124. // adding new public atmos system functions.
  125. List<Vector2i> tiles = new(4);
  126. List<AtmosDirection> directions = new(4);
  127. for (var i = 0; i < Atmospherics.Directions; i++)
  128. {
  129. var dir = (AtmosDirection)(1 << i);
  130. if (airtight.AirBlockedDirection.HasFlag(dir))
  131. {
  132. directions.Add(dir);
  133. tiles.Add(pos.Offset(dir));
  134. }
  135. }
  136. // May also have to consider pressure on the same tile as the firelock.
  137. var count = tiles.Count;
  138. if (airtight.AirBlockedDirection != AtmosDirection.All)
  139. tiles.Add(pos);
  140. var gasses = _atmosSystem.GetTileMixtures(xform.ParentUid, xform.MapUid, tiles);
  141. if (gasses == null)
  142. return (false, false);
  143. for (var i = 0; i < count; i++)
  144. {
  145. var gas = gasses[i];
  146. var dir = directions[i];
  147. var adjacentPos = tiles[i];
  148. if (gas != null)
  149. {
  150. // Is there some airtight entity blocking this direction? If yes, don't include this direction in the
  151. // pressure differential
  152. if (HasAirtightBlocker(_mapping.GetAnchoredEntities(xform.ParentUid, grid, adjacentPos), dir.GetOpposite(), airtightQuery))
  153. continue;
  154. var p = gas.Pressure;
  155. minPressure = Math.Min(minPressure, p);
  156. maxPressure = Math.Max(maxPressure, p);
  157. minTemperature = Math.Min(minTemperature, gas.Temperature);
  158. maxTemperature = Math.Max(maxTemperature, gas.Temperature);
  159. }
  160. holdingPressure |= maxPressure - minPressure > firelock.PressureThreshold;
  161. holdingFire |= maxTemperature - minTemperature > firelock.TemperatureThreshold;
  162. if (holdingPressure && holdingFire)
  163. return (holdingPressure, holdingFire);
  164. }
  165. if (airtight.AirBlockedDirection == AtmosDirection.All)
  166. return (holdingPressure, holdingFire);
  167. var local = gasses[count];
  168. if (local != null)
  169. {
  170. var p = local.Pressure;
  171. minPressure = Math.Min(minPressure, p);
  172. maxPressure = Math.Max(maxPressure, p);
  173. minTemperature = Math.Min(minTemperature, local.Temperature);
  174. maxTemperature = Math.Max(maxTemperature, local.Temperature);
  175. }
  176. else
  177. {
  178. minPressure = Math.Min(minPressure, 0);
  179. maxPressure = Math.Max(maxPressure, 0);
  180. minTemperature = Math.Min(minTemperature, 0);
  181. maxTemperature = Math.Max(maxTemperature, 0);
  182. }
  183. holdingPressure |= maxPressure - minPressure > firelock.PressureThreshold;
  184. holdingFire |= maxTemperature - minTemperature > firelock.TemperatureThreshold;
  185. return (holdingPressure, holdingFire);
  186. }
  187. private bool HasAirtightBlocker(IEnumerable<EntityUid> enumerable, AtmosDirection dir, EntityQuery<AirtightComponent> airtightQuery)
  188. {
  189. foreach (var ent in enumerable)
  190. {
  191. if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked)
  192. continue;
  193. if ((airtight.AirBlockedDirection & dir) == dir)
  194. return true;
  195. }
  196. return false;
  197. }
  198. }
  199. }