From 8620899a4a788d4d63f4054fdf508479a6383c5b Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Wed, 9 Nov 2022 08:55:45 +1300 Subject: [PATCH] Fix firelock danger indicators (#12327) * Fix firelock danger indicators * remove unused arg --- .../EntitySystems/AtmosphereSystem.API.cs | 25 ++ .../AtmosphereSystem.GridAtmosphere.cs | 23 ++ .../EntitySystems/AtmosphereSystem.Map.cs | 17 ++ .../Doors/Components/FirelockComponent.cs | 8 + .../Doors/Systems/FirelockSystem.cs | 223 +++++++++++++----- 5 files changed, 243 insertions(+), 53 deletions(-) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs index 2c0b158893..b58ec491a7 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.API.cs @@ -89,6 +89,28 @@ public partial class AtmosphereSystem RaiseLocalEvent(gridUid, ref ev); } + public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List tiles, bool excite = false) + { + var ev = new GetTileMixturesMethodEvent(gridUid, mapUid, tiles, excite); + + // If we've been passed a grid, try to let it handle it. + if (gridUid.HasValue) + RaiseLocalEvent(gridUid.Value, ref ev, false); + + if (ev.Handled) + return ev.Mixtures; + + // We either don't have a grid, or the event wasn't handled. + // Let the map handle it instead, and also broadcast the event. + if (mapUid.HasValue) + RaiseLocalEvent(mapUid.Value, ref ev, true); + else + RaiseLocalEvent(ref ev); + + // Default to a space mixture... This is a space game, after all! + return ev.Mixtures ?? new GasMixture?[tiles.Count]; + } + public GasMixture? GetTileMixture(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, bool excite = false) { var ev = new GetTileMixtureMethodEvent(gridUid, mapUid, tile, excite); @@ -256,6 +278,9 @@ public partial class AtmosphereSystem [ByRefEvent] private record struct InvalidateTileMethodEvent (EntityUid Grid, Vector2i Tile, bool Handled = false); + [ByRefEvent] private record struct GetTileMixturesMethodEvent + (EntityUid? GridUid, EntityUid? MapUid, List Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false); + [ByRefEvent] private record struct GetTileMixtureMethodEvent (EntityUid? GridUid, EntityUid? MapUid, Vector2i Tile, bool Excite = false, GasMixture? Mixture = null, bool Handled = false); diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs index 6f8ec55389..a4748f18f5 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.GridAtmosphere.cs @@ -21,6 +21,7 @@ public sealed partial class AtmosphereSystem SubscribeLocalEvent(GridGetAllMixtures); SubscribeLocalEvent(GridInvalidateTile); SubscribeLocalEvent(GridGetTileMixture); + SubscribeLocalEvent(GridGetTileMixtures); SubscribeLocalEvent(GridReactTile); SubscribeLocalEvent(GridIsTileAirBlocked); SubscribeLocalEvent(GridIsTileSpace); @@ -178,6 +179,28 @@ public sealed partial class AtmosphereSystem args.Handled = true; } + private void GridGetTileMixtures(EntityUid uid, GridAtmosphereComponent component, + ref GetTileMixturesMethodEvent args) + { + if (args.Handled) + return; + + args.Handled = true; + args.Mixtures = new GasMixture?[args.Tiles.Count]; + + for (var i = 0; i < args.Tiles.Count; i++) + { + var tile = args.Tiles[i]; + if (!component.Tiles.TryGetValue(tile, out var atmosTile)) + continue; + + if (args.Excite) + component.InvalidatedCoords.Add(tile); + + args.Mixtures[i] = atmosTile.Air; + } + } + private void GridReactTile(EntityUid uid, GridAtmosphereComponent component, ref ReactTileMethodEvent args) { if (args.Handled) diff --git a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs index e4dd4b5154..ad5c39e965 100644 --- a/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs +++ b/Content.Server/Atmos/EntitySystems/AtmosphereSystem.Map.cs @@ -8,6 +8,7 @@ public partial class AtmosphereSystem { SubscribeLocalEvent(MapIsTileSpace); SubscribeLocalEvent(MapGetTileMixture); + SubscribeLocalEvent(MapGetTileMixtures); } private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args) @@ -28,4 +29,20 @@ public partial class AtmosphereSystem args.Mixture = component.Mixture?.Clone(); args.Handled = true; } + + private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args) + { + if (args.Handled) + return; + args.Handled = true; + args.Mixtures = new GasMixture?[args.Tiles.Count]; + + if (component.Mixture == null) + return; + + for (var i = 0; i < args.Tiles.Count; i++) + { + args.Mixtures[i] = component.Mixture.Clone(); + } + } } diff --git a/Content.Server/Doors/Components/FirelockComponent.cs b/Content.Server/Doors/Components/FirelockComponent.cs index 7bb35ad2c9..cde4aa7692 100644 --- a/Content.Server/Doors/Components/FirelockComponent.cs +++ b/Content.Server/Doors/Components/FirelockComponent.cs @@ -25,6 +25,14 @@ namespace Content.Server.Doors.Components [DataField("pressureThreshold"), ViewVariables(VVAccess.ReadWrite)] public float PressureThreshold = 20; + /// + /// Maximum temperature difference before the firelock will refuse to open, in k. + /// + [DataField("temperatureThreshold"), ViewVariables(VVAccess.ReadWrite)] + public float TemperatureThreshold = 330; + // this used to check for hot-spots, but because accessing that data is a a mess this now just checks + // temperature. This does mean a cold room will trigger hot-air pop-ups + /// /// If true, and if this door has an , then it will only auto-close if the /// alarm is set to danger. diff --git a/Content.Server/Doors/Systems/FirelockSystem.cs b/Content.Server/Doors/Systems/FirelockSystem.cs index 2ca9267cae..46fec0b4c3 100644 --- a/Content.Server/Doors/Systems/FirelockSystem.cs +++ b/Content.Server/Doors/Systems/FirelockSystem.cs @@ -1,13 +1,17 @@ +using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Monitor.Systems; using Content.Server.Doors.Components; using Content.Server.Popups; -using Content.Server.Power.EntitySystems; using Content.Server.Power.Components; +using Content.Server.Power.EntitySystems; +using Content.Server.Shuttles.Components; +using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Doors; using Content.Shared.Doors.Components; using Content.Shared.Doors.Systems; +using Microsoft.Extensions.Options; using Robust.Server.GameObjects; using Robust.Shared.Player; @@ -19,7 +23,6 @@ namespace Content.Server.Doors.Systems [Dependency] private readonly SharedDoorSystem _doorSystem = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosphereSystem _atmosSystem = default!; - [Dependency] private readonly TransformSystem _xformSys = default!; [Dependency] private readonly AppearanceSystem _appearance = default!; private static float _visualUpdateInterval = 0.5f; @@ -38,10 +41,18 @@ namespace Content.Server.Doors.Systems // Visuals SubscribeLocalEvent(UpdateVisuals); + SubscribeLocalEvent(UpdateVisuals); + SubscribeLocalEvent(PowerChanged); + } + + private void PowerChanged(EntityUid uid, FirelockComponent component, ref PowerChangedEvent args) + { + // TODO this should REALLLLY not be door specific appearance thing. + _appearance.SetData(uid, DoorVisuals.Powered, args.Powered); } #region Visuals - private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid); + private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component); public override void Update(float frameTime) { @@ -51,21 +62,39 @@ namespace Content.Server.Doors.Systems _accumulatedFrameTime -= _visualUpdateInterval; - var powerQuery = GetEntityQuery(); + var airtightQuery = GetEntityQuery(); + var appearanceQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); - foreach (var (_, door, appearance, xform) in EntityQuery()) + foreach (var (firelock, door) in EntityQuery()) { - UpdateVisuals(door.Owner, door, appearance, xform, powerQuery); + // only bother to check pressure on doors that are some variation of closed. + if (door.State != DoorState.Closed + && door.State != DoorState.Welded + && door.State != DoorState.Denying) + { + continue; + } + + var uid = door.Owner; + if (airtightQuery.TryGetComponent(uid, out var airtight) + && xformQuery.TryGetComponent(uid, out var xform) + && appearanceQuery.TryGetComponent(uid, out var appearance)) + { + var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery); + _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); + } } } private void UpdateVisuals(EntityUid uid, + FirelockComponent? firelock = null, DoorComponent? door = null, + AirtightComponent? airtight = null, AppearanceComponent? appearance = null, - TransformComponent? xform = null, - EntityQuery? powerQuery = null) + TransformComponent? xform = null) { - if (!Resolve(uid, ref door, ref appearance, ref xform, false)) + if (!Resolve(uid, ref door, ref appearance, false)) return; // only bother to check pressure on doors that are some variation of closed. @@ -77,14 +106,12 @@ namespace Content.Server.Doors.Systems return; } - powerQuery ??= EntityManager.GetEntityQuery(); - if (powerQuery.Value.TryGetComponent(uid, out var receiver) && !receiver.Powered) - { - _appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance); + var query = GetEntityQuery(); + if (!Resolve(uid, ref firelock, ref airtight, ref appearance, ref xform, false) || !query.Resolve(uid, ref airtight, false)) return; - } - _appearance.SetData(uid, DoorVisuals.ClosedLights, IsHoldingPressureOrFire(uid, xform), appearance); + var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, query); + _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance); } #endregion @@ -105,13 +132,13 @@ namespace Content.Server.Doors.Systems private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args) { - if (!this.IsPowered(uid, EntityManager) || IsHoldingPressureOrFire(uid)) + if (!this.IsPowered(uid, EntityManager) || IsHoldingPressureOrFire(uid, component)) args.Cancel(); } private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args) { - var state = CheckPressureAndFire(uid); + var state = CheckPressureAndFire(uid, component); if (state.Fire) { @@ -173,55 +200,145 @@ namespace Content.Server.Doors.Systems } } - public bool IsHoldingPressureOrFire(EntityUid uid, TransformComponent? xform = null) + public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock) { - var result = CheckPressureAndFire(uid, xform); + var result = CheckPressureAndFire(uid, firelock); return result.Pressure || result.Fire; } - public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid owner, TransformComponent? xform = null) + public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock) { - if (!Resolve(owner, ref xform)) + var query = GetEntityQuery(); + if (query.TryGetComponent(uid, out AirtightComponent? airtight)) + return CheckPressureAndFire(uid, firelock, Transform(uid), airtight, query); + return (false, false); + } + + public (bool Pressure, bool Fire) CheckPressureAndFire( + EntityUid uid, + FirelockComponent firelock, + TransformComponent xform, + AirtightComponent airtight, + EntityQuery airtightQuery) + { + if (!airtight.AirBlocked) return (false, false); - float threshold = 20; - var position = _xformSys.GetGridOrMapTilePosition(owner, xform); - if (xform.GridUid is not {} gridUid) - return (false, false); - var minMoles = float.MaxValue; - var maxMoles = 0f; - - return (IsHoldingPressure(), IsHoldingFire()); - - bool IsHoldingPressure() + if (TryComp(uid, out DockingComponent? dock) && dock.Docked) { - foreach (var adjacent in _atmosSystem.GetAdjacentTileMixtures(gridUid, position)) - { - var moles = adjacent.TotalMoles; - if (moles < minMoles) - minMoles = moles; - if (moles > maxMoles) - maxMoles = moles; - } - - return (maxMoles - minMoles) > threshold; + // Currently docking automatically opens the doors. But maybe in future, check the pressure difference before opening doors? + return (false, false); } - bool IsHoldingFire() - { - if (_atmosSystem.GetTileMixture(gridUid, null, position) == null) - return false; + if (!TryComp(xform.ParentUid, out GridAtmosphereComponent? gridAtmosphere)) + return (false, false); - if (_atmosSystem.IsHotspotActive(gridUid, position)) + var grid = Comp(xform.ParentUid).Grid; + var pos = grid.CoordinatesToTile(xform.Coordinates); + var minPressure = float.MaxValue; + var maxPressure = float.MinValue; + var minTemperature = float.MaxValue; + var maxTemperature = float.MinValue; + bool holdingFire = false; + bool holdingPressure = false; + + // We cannot simply use `_atmosSystem.GetAdjacentTileMixtures` because of how the `includeBlocked` option + // works, we want to ignore the firelock's blocking, while including blockers on other tiles. + // GetAdjacentTileMixtures also ignores empty/non-existent tiles, which we don't want. Additionally, for + // edge-fire locks, we only want to enumerate over a single directions. So AFAIK there is no nice way of + // achieving all this using existing atmos functions, and the functionality is too specialized to bother + // adding new public atmos system functions. + + + // TODO redo this with planet/map atmospheres + // there is probably a faster way of doing this. tbh I kinda hate the atmos method events for making + // accessing tile data directly such a pain. Dealting with maps will make it even more painful. + List tiles = new(4); + List directions = new(4); + for (var i = 0; i < Atmospherics.Directions; i++) + { + var dir = (AtmosDirection) (1 << i); + if (airtight.AirBlockedDirection.HasFlag(dir)) + { + directions.Add(dir); + tiles.Add(pos.Offset(dir)); + } + } + + // May also have to consider pressure on the same tile as the firelock. + var count = tiles.Count; + if (airtight.AirBlockedDirection != AtmosDirection.All) + tiles.Add(pos); + + var gasses = _atmosSystem.GetTileMixtures(gridAtmosphere.Owner, null, tiles); + if (gasses == null) + return (false, false); + + for (var i = 0; i < count; i++) + { + var gas = gasses[i]; + var dir = directions[i]; + var adjacentPos = tiles[i]; + + if (gas != null) + { + // Is there some airtight entity blocking this direction? If yes, don't include this direction in the + // pressure differential + if (HasAirtightBlocker(grid.GetAnchoredEntities(adjacentPos), dir.GetOpposite(), airtightQuery)) + continue; + + var p = gas.Pressure; + minPressure = Math.Min(minPressure, p); + maxPressure = Math.Max(maxPressure, p); + minTemperature = Math.Min(minTemperature, gas.Temperature); + maxTemperature = Math.Max(maxTemperature, gas.Temperature); + } + + holdingPressure |= maxPressure - minPressure > firelock.PressureThreshold; + holdingFire |= maxTemperature - minTemperature > firelock.TemperatureThreshold; + + if (holdingPressure && holdingFire) + return (holdingPressure, holdingFire); + } + + if (airtight.AirBlockedDirection == AtmosDirection.All) + return (holdingPressure, holdingFire); + + var local = gasses[count]; + if (local != null) + { + var p = local.Pressure; + minPressure = Math.Min(minPressure, p); + maxPressure = Math.Max(maxPressure, p); + minTemperature = Math.Min(minTemperature, local.Temperature); + maxTemperature = Math.Max(maxTemperature, local.Temperature); + } + else + { + minPressure = Math.Min(minPressure, 0); + maxPressure = Math.Max(maxPressure, 0); + minTemperature = Math.Min(minTemperature, 0); + maxTemperature = Math.Max(maxTemperature, 0); + } + + holdingPressure |= maxPressure - minPressure > firelock.PressureThreshold; + holdingFire |= maxTemperature - minTemperature > firelock.TemperatureThreshold; + + return (holdingPressure, holdingFire); + } + + private bool HasAirtightBlocker(IEnumerable enumerable, AtmosDirection dir, EntityQuery airtightQuery) + { + foreach (var ent in enumerable) + { + if (!airtightQuery.TryGetComponent(ent, out var airtight) || !airtight.AirBlocked) + continue; + + if ((airtight.AirBlockedDirection & dir) == dir) return true; - - foreach (var adjacent in _atmosSystem.GetAdjacentTiles(gridUid, position)) - { - if (_atmosSystem.IsHotspotActive(gridUid, adjacent)) - return true; - } - return false; } + + return false; } } }