Fix firelock danger indicators (#12327)

* Fix firelock danger indicators

* remove unused arg
This commit is contained in:
Leon Friedrich
2022-11-09 08:55:45 +13:00
committed by GitHub
parent 5f71fc1ea1
commit 8620899a4a
5 changed files with 243 additions and 53 deletions

View File

@@ -89,6 +89,28 @@ public partial class AtmosphereSystem
RaiseLocalEvent(gridUid, ref ev);
}
public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> 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<Vector2i> 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);

View File

@@ -21,6 +21,7 @@ public sealed partial class AtmosphereSystem
SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, InvalidateTileMethodEvent>(GridInvalidateTile);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileAirBlockedMethodEvent>(GridIsTileAirBlocked);
SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(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)

View File

@@ -8,6 +8,7 @@ public partial class AtmosphereSystem
{
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(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();
}
}
}

View File

@@ -25,6 +25,14 @@ namespace Content.Server.Doors.Components
[DataField("pressureThreshold"), ViewVariables(VVAccess.ReadWrite)]
public float PressureThreshold = 20;
/// <summary>
/// Maximum temperature difference before the firelock will refuse to open, in k.
/// </summary>
[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
/// <summary>
/// If true, and if this door has an <see cref="AtmosAlarmableComponent"/>, then it will only auto-close if the
/// alarm is set to danger.

View File

@@ -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<FirelockComponent, MapInitEvent>(UpdateVisuals);
SubscribeLocalEvent<FirelockComponent, ComponentStartup>(UpdateVisuals);
SubscribeLocalEvent<FirelockComponent, PowerChangedEvent>(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<ApcPowerReceiverComponent>();
var airtightQuery = GetEntityQuery<AirtightComponent>();
var appearanceQuery = GetEntityQuery<AppearanceComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var (_, door, appearance, xform) in EntityQuery<FirelockComponent, DoorComponent, AppearanceComponent, TransformComponent>())
foreach (var (firelock, door) in EntityQuery<FirelockComponent, DoorComponent>())
{
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<ApcPowerReceiverComponent>? 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<ApcPowerReceiverComponent>();
if (powerQuery.Value.TryGetComponent(uid, out var receiver) && !receiver.Powered)
{
_appearance.SetData(uid, DoorVisuals.ClosedLights, false, appearance);
var query = GetEntityQuery<AirtightComponent>();
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<AirtightComponent>();
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<AirtightComponent> 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<IMapGridComponent>(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<Vector2i> tiles = new(4);
List<AtmosDirection> 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<EntityUid> enumerable, AtmosDirection dir, EntityQuery<AirtightComponent> 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;
}
}
}