Firelock ECS + some other stuff. (#8366)

* firelock rejig

* dont error failed resolves

* fix

* less resolves

* switch fire and pressure messages

* update

Co-authored-by: wrexbe <wrexbe@protonmail.com>
This commit is contained in:
Leon Friedrich
2022-09-06 17:55:33 +12:00
committed by GitHub
parent 414f32a4ee
commit 35a142965d
11 changed files with 192 additions and 142 deletions

View File

@@ -160,9 +160,6 @@ namespace Content.Client.Doors
}
var door = _entMan.GetComponent<DoorComponent>(component.Owner);
var unlitVisible = true;
var boltedVisible = false;
var emergencyLightsVisible = false;
if (component.TryGetData(DoorVisuals.BaseRSI, out string baseRsi))
{
@@ -184,7 +181,6 @@ namespace Content.Client.Doors
{
case DoorState.Open:
sprite.LayerSetState(DoorVisualLayers.Base, "open");
unlitVisible = _openUnlitVisible;
if (_openUnlitVisible && !_simpleVisuals)
{
sprite.LayerSetState(DoorVisualLayers.BaseUnlit, "open_unlit");
@@ -219,33 +215,33 @@ namespace Content.Client.Doors
throw new ArgumentOutOfRangeException();
}
if (component.TryGetData(DoorVisuals.Powered, out bool powered) && !powered)
if (_simpleVisuals)
return;
var boltedVisible = false;
var emergencyLightsVisible = false;
var unlitVisible = false;
if (component.TryGetData(DoorVisuals.Powered, out bool powered) && powered)
{
unlitVisible = false;
}
if (component.TryGetData(DoorVisuals.BoltLights, out bool lights) && lights)
{
boltedVisible = true;
boltedVisible = component.TryGetData(DoorVisuals.BoltLights, out bool lights) && lights;
emergencyLightsVisible = component.TryGetData(DoorVisuals.EmergencyLights, out bool eaLights) && eaLights;
unlitVisible = state == DoorState.Closing
|| state == DoorState.Opening
|| state == DoorState.Denying
|| state == DoorState.Open && _openUnlitVisible
|| (component.TryGetData(DoorVisuals.ClosedLights, out bool closedLights) && closedLights);
}
if (component.TryGetData(DoorVisuals.EmergencyLights, out bool eaLights) && eaLights)
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, boltedVisible);
if (_emergencyAccessLayer)
{
emergencyLightsVisible = true;
}
if (!_simpleVisuals)
{
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible && state != DoorState.Closed && state != DoorState.Welded);
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
if (_emergencyAccessLayer)
{
sprite.LayerSetVisible(DoorVisualLayers.BaseEmergencyAccess,
emergencyLightsVisible
&& state != DoorState.Open
&& state != DoorState.Opening
&& state != DoorState.Closing
&& unlitVisible);
}
sprite.LayerSetVisible(DoorVisualLayers.BaseEmergencyAccess,
emergencyLightsVisible
&& state != DoorState.Open
&& state != DoorState.Opening
&& state != DoorState.Closing);
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Doors.Components;
using Content.Server.Doors.Systems;
using Content.Shared.Atmos;
using Content.Shared.Database;
using Robust.Shared.Map;
@@ -10,6 +11,8 @@ namespace Content.Server.Atmos.EntitySystems
{
public sealed partial class AtmosphereSystem
{
[Dependency] private readonly FirelockSystem _firelockSystem = default!;
private readonly TileAtmosphereComparer _monstermosComparer = new();
private readonly TileAtmosphere?[] _equalizeTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
@@ -502,7 +505,7 @@ namespace Content.Server.Atmos.EntitySystems
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
reconsiderAdjacent |= firelock.EmergencyPressureStop();
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
@@ -510,7 +513,7 @@ namespace Content.Server.Atmos.EntitySystems
if (!TryComp(entity, out FirelockComponent? firelock))
continue;
reconsiderAdjacent |= firelock.EmergencyPressureStop();
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
}
if (!reconsiderAdjacent)

View File

@@ -1,103 +1,35 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Doors.Systems;
using Content.Shared.Doors.Components;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Doors.Components
{
/// <summary>
/// Companion component to ServerDoorComponent that handles firelock-specific behavior -- primarily prying,
/// and not being openable on open-hand click.
/// Companion component to <see cref="DoorComponent"/> that handles firelock-specific behavior, including
/// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when
/// retaining pressure..
/// </summary>
[RegisterComponent]
public sealed class FirelockComponent : Component
{
[Dependency] private readonly IEntityManager _entMan = default!;
/// <summary>
/// Pry time modifier to be used when the firelock is currently closed due to fire or pressure.
/// </summary>
/// <returns></returns>
[DataField("lockedPryTimeModifier")]
[DataField("lockedPryTimeModifier"), ViewVariables(VVAccess.ReadWrite)]
public float LockedPryTimeModifier = 1.5f;
[DataField("autocloseDelay")] public TimeSpan AutocloseDelay = TimeSpan.FromSeconds(3f);
public bool EmergencyPressureStop()
{
var doorSys = EntitySystem.Get<DoorSystem>();
if (_entMan.TryGetComponent<DoorComponent>(Owner, out var door) &&
door.State == DoorState.Open &&
doorSys.CanClose(Owner, door))
{
doorSys.StartClosing(Owner, door);
/// <summary>
/// Maximum pressure difference before the firelock will refuse to open, in kPa.
/// </summary>
[DataField("pressureThreshold"), ViewVariables(VVAccess.ReadWrite)]
public float PressureThreshold = 20;
// Door system also sets airtight, but only after a delay. We want it to be immediate.
if (_entMan.TryGetComponent(Owner, out AirtightComponent? airtight))
{
EntitySystem.Get<AirtightSystem>().SetAirblocked(airtight, true);
}
return true;
}
return false;
}
public bool IsHoldingPressure(float threshold = 20)
{
var transform = _entMan.GetComponent<TransformComponent>(Owner);
if (transform.GridUid is not {} gridUid)
return false;
var atmosphereSystem = _entMan.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
var transformSystem = _entMan.EntitySysManager.GetEntitySystem<TransformSystem>();
var position = transformSystem.GetGridOrMapTilePosition(Owner, transform);
var minMoles = float.MaxValue;
var maxMoles = 0f;
foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(gridUid, position))
{
var moles = adjacent.TotalMoles;
if (moles < minMoles)
minMoles = moles;
if (moles > maxMoles)
maxMoles = moles;
}
return (maxMoles - minMoles) > threshold;
}
public bool IsHoldingFire()
{
var atmosphereSystem = _entMan.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
var transformSystem = _entMan.EntitySysManager.GetEntitySystem<TransformSystem>();
var transform = _entMan.GetComponent<TransformComponent>(Owner);
var position = transformSystem.GetGridOrMapTilePosition(Owner, transform);
// No grid, no fun.
if (transform.GridUid is not {} gridUid)
return false;
if (atmosphereSystem.GetTileMixture(gridUid, null, position) == null)
return false;
if (atmosphereSystem.IsHotspotActive(gridUid, position))
return true;
foreach (var adjacent in atmosphereSystem.GetAdjacentTiles(gridUid, position))
{
if (atmosphereSystem.IsHotspotActive(gridUid, adjacent))
return true;
}
return false;
}
/// <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.
/// </summary>
[DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)]
public bool AlarmAutoClose = true;
}
}

View File

@@ -1,68 +1,133 @@
using Content.Server.Atmos.Monitor.Components;
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.Shared.Atmos.Monitor;
using Content.Shared.Doors;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.Player;
namespace Content.Server.Doors.Systems
{
public sealed class FirelockSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoorSystem _doorSystem = default!;
[Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!;
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
[Dependency] private readonly AirtightSystem _airtightSystem = default!;
private static float _visualUpdateInterval = 0.2f;
private float _accumulatedFrameTime;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
SubscribeLocalEvent<FirelockComponent, BeforeDoorDeniedEvent>(OnBeforeDoorDenied);
SubscribeLocalEvent<FirelockComponent, DoorGetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
SubscribeLocalEvent<FirelockComponent, BeforeDoorPryEvent>(OnBeforeDoorPry);
SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
SubscribeLocalEvent<FirelockComponent, AtmosAlarmEvent>(OnAtmosAlarm);
// Visuals
SubscribeLocalEvent<FirelockComponent, MapInitEvent>(UpdateVisuals);
}
#region Visuals
private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid);
public override void Update(float frameTime)
{
_accumulatedFrameTime += frameTime;
if (_accumulatedFrameTime < _visualUpdateInterval)
return;
_accumulatedFrameTime -= _visualUpdateInterval;
var powerQuery = GetEntityQuery<ApcPowerReceiverComponent>();
foreach (var (_, door, appearance, xform) in EntityQuery<FirelockComponent, DoorComponent, AppearanceComponent, TransformComponent>())
{
UpdateVisuals(door.Owner, door, appearance, powerQuery);
}
}
private void UpdateVisuals(EntityUid uid,
DoorComponent? door = null,
AppearanceComponent? appearance = null,
EntityQuery<ApcPowerReceiverComponent>? powerQuery = null)
{
if (!Resolve(uid, ref door, ref appearance, false))
return;
// 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)
{
appearance.SetData(DoorVisuals.ClosedLights, false);
return;
}
powerQuery ??= EntityManager.GetEntityQuery<ApcPowerReceiverComponent>();
if (powerQuery.Value.TryGetComponent(uid, out var receiver) && !receiver.Powered)
{
appearance.SetData(DoorVisuals.ClosedLights, false);
return;
}
appearance.SetData(DoorVisuals.ClosedLights,
IsHoldingPressureOrFire(uid));
}
#endregion
public bool EmergencyPressureStop(EntityUid uid, FirelockComponent? firelock = null, DoorComponent? door = null)
{
if (!Resolve(uid, ref firelock, ref door))
return false;
if (door.State == DoorState.Open)
{
if (_doorSystem.TryClose(door.Owner, door))
{
return _doorSystem.OnPartialClose(door.Owner, door);
}
}
return false;
}
private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
{
if (!this.IsPowered(uid, EntityManager) || component.IsHoldingFire() || component.IsHoldingPressure())
if (!this.IsPowered(uid, EntityManager) || IsHoldingPressureOrFire(uid))
args.Cancel();
}
private void OnBeforeDoorDenied(EntityUid uid, FirelockComponent component, BeforeDoorDeniedEvent args)
{
args.Cancel();
}
private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, DoorGetPryTimeModifierEvent args)
{
if (component.IsHoldingFire() || component.IsHoldingPressure())
var state = CheckPressureAndFire(uid);
if (state.Fire)
{
_popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"),
uid, Filter.Pvs(uid, entityManager: EntityManager));
}
else if (state.Pressure)
{
_popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"),
uid, Filter.Pvs(uid, entityManager: EntityManager));
}
if (state.Fire || state.Pressure)
args.PryTimeModifier *= component.LockedPryTimeModifier;
}
private void OnBeforeDoorPry(EntityUid uid, FirelockComponent component, BeforeDoorPryEvent args)
{
if (!TryComp<DoorComponent>(uid, out var door) || door.State != DoorState.Closed)
{
return;
}
if (component.IsHoldingPressure())
{
component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-pressure-message"));
}
else if (component.IsHoldingFire())
{
component.Owner.PopupMessage(args.User, Loc.GetString("firelock-component-is-holding-fire-message"));
}
}
private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args)
{
var ev = new BeforeDoorAutoCloseEvent();
@@ -73,6 +138,7 @@ namespace Content.Server.Doors.Systems
}
_doorSystem.SetNextStateChange(uid, component.AutocloseDelay);
UpdateVisuals(uid, component, args);
}
private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args)
@@ -83,7 +149,8 @@ namespace Content.Server.Doors.Systems
// Make firelocks autoclose, but only if the last alarm type it
// remembers was a danger. This is to prevent people from
// flooding hallways with endless bad air/fire.
if (_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null)
if (component.AlarmAutoClose &&
(_atmosAlarmable.TryGetHighestAlert(uid, out var alarm) && alarm != AtmosAlarmType.Danger || alarm == null))
args.Cancel();
}
@@ -98,7 +165,58 @@ namespace Content.Server.Doors.Systems
}
else if (args.AlarmType == AtmosAlarmType.Danger)
{
component.EmergencyPressureStop();
EmergencyPressureStop(uid, component, doorComponent);
}
}
public bool IsHoldingPressureOrFire(EntityUid uid)
{
var result = CheckPressureAndFire(uid);
return result.Pressure || result.Fire;
}
public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid owner)
{
float threshold = 20;
var atmosphereSystem = EntityManager.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
var transformSystem = EntityManager.EntitySysManager.GetEntitySystem<TransformSystem>();
var transform = EntityManager.GetComponent<TransformComponent>(owner);
var position = transformSystem.GetGridOrMapTilePosition(owner, transform);
if (transform.GridUid is not {} gridUid)
return (false, false);
var minMoles = float.MaxValue;
var maxMoles = 0f;
return (IsHoldingPressure(), IsHoldingFire());
bool IsHoldingPressure()
{
foreach (var adjacent in atmosphereSystem.GetAdjacentTileMixtures(gridUid, position))
{
var moles = adjacent.TotalMoles;
if (moles < minMoles)
minMoles = moles;
if (moles > maxMoles)
maxMoles = moles;
}
return (maxMoles - minMoles) > threshold;
}
bool IsHoldingFire()
{
if (atmosphereSystem.GetTileMixture(gridUid, null, position) == null)
return false;
if (atmosphereSystem.IsHotspotActive(gridUid, position))
return true;
foreach (var adjacent in atmosphereSystem.GetAdjacentTiles(gridUid, position))
{
if (atmosphereSystem.IsHotspotActive(gridUid, adjacent))
return true;
}
return false;
}
}
}

View File

@@ -231,6 +231,7 @@ public enum DoorVisuals
Powered,
BoltLights,
EmergencyLights,
ClosedLights,
BaseRSI,
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 B

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB