]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Firelock improvements part 1 (#26582)
authornikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com>
Wed, 22 May 2024 11:16:20 +0000 (11:16 +0000)
committerGitHub <noreply@github.com>
Wed, 22 May 2024 11:16:20 +0000 (04:16 -0700)
* Change prying system and pryunpoweredcomp to allow for custom time modifiers

This will be useful if I go the route of making firelocks pryable when
unpowered instead of just being able to open and close instantly when
unpowered.

* Make firelocks properly predicted

Shared system made. Since atmos checks can only be done on the server we
just have it set relevant bools on the component and then dirty it.
Ditched atmos checks on trying to open, they now only happen whenever
firelocks are updated.

* Make firelocks pryable without a crowbar

While this usually would only allow you to do this when a door is
unpowered, firelocks do not have the airlock component which actually
does that check. As such firelocks will always allow you to pry them
open/closed by hand.

* Clean up System. Change update interval to be based on ticks. Move as much as possible to shared

* Make firelocks unable to emergency close for 2 seconds after being pried open

* Clean up

* More cleanup

* Reorganize SharedFirelockSystem methods to match Initialize order

---------

Co-authored-by: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com>
Content.Client/Doors/FirelockSystem.cs
Content.Server/Doors/Systems/FirelockSystem.cs
Content.Shared/Doors/Components/FirelockComponent.cs
Content.Shared/Doors/Systems/SharedDoorSystem.cs
Content.Shared/Doors/Systems/SharedFirelockSystem.cs [new file with mode: 0644]
Content.Shared/Prying/Components/PryUnpoweredComponent.cs
Content.Shared/Prying/Systems/PryingSystem.cs
Resources/Prototypes/Entities/Structures/Doors/Firelocks/firelock.yml

index cfd84a471336cc4a13833c0a3bcdece0fa4a2010..f64b4c8e5222e5fe6ec794f0d5687e1a23e100d4 100644 (file)
@@ -1,9 +1,10 @@
 using Content.Shared.Doors.Components;
+using Content.Shared.Doors.Systems;
 using Robust.Client.GameObjects;
 
 namespace Content.Client.Doors;
 
-public sealed class FirelockSystem : EntitySystem
+public sealed class FirelockSystem : SharedFirelockSystem
 {
     [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
 
index 3d4c8a4ec59e04621dc62b9c80f7c9af69b1b300..5ad86fb20aab11a2263ce0b8141ac4bd0c33cec0 100644 (file)
@@ -1,67 +1,56 @@
 using Content.Server.Atmos.Components;
 using Content.Server.Atmos.EntitySystems;
 using Content.Server.Atmos.Monitor.Systems;
-using Content.Server.Popups;
 using Content.Server.Power.Components;
 using Content.Server.Power.EntitySystems;
 using Content.Server.Shuttles.Components;
-using Content.Shared.Access.Systems;
 using Content.Shared.Atmos;
 using Content.Shared.Atmos.Monitor;
 using Content.Shared.Doors;
 using Content.Shared.Doors.Components;
 using Content.Shared.Doors.Systems;
-using Content.Shared.Popups;
-using Content.Shared.Prying.Components;
 using Robust.Shared.Map.Components;
 
 namespace Content.Server.Doors.Systems
 {
-    public sealed class FirelockSystem : EntitySystem
+    public sealed class FirelockSystem : SharedFirelockSystem
     {
-        [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 SharedAppearanceSystem _appearance = default!;
-        [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
+        [Dependency] private readonly SharedMapSystem _mapping = default!;
 
-        private static float _visualUpdateInterval = 0.5f;
-        private float _accumulatedFrameTime;
+        private const int UpdateInterval = 30;
+        private int _accumulatedTicks;
 
         public override void Initialize()
         {
             base.Initialize();
 
-            SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
-            SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
-            SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
 
             SubscribeLocalEvent<FirelockComponent, BeforeDoorAutoCloseEvent>(OnBeforeDoorAutoclose);
             SubscribeLocalEvent<FirelockComponent, AtmosAlarmEvent>(OnAtmosAlarm);
 
-            // 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);
+            component.Powered = args.Powered;
+            Dirty(uid, component);
         }
 
-        #region Visuals
-        private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component);
-
         public override void Update(float frameTime)
         {
-            _accumulatedFrameTime += frameTime;
-            if (_accumulatedFrameTime < _visualUpdateInterval)
+            _accumulatedTicks += 1;
+            if (_accumulatedTicks < UpdateInterval)
                 return;
 
-            _accumulatedFrameTime -= _visualUpdateInterval;
+            _accumulatedTicks = 0;
 
             var airtightQuery = GetEntityQuery<AirtightComponent>();
             var appearanceQuery = GetEntityQuery<AppearanceComponent>();
@@ -84,94 +73,13 @@ namespace Content.Server.Doors.Systems
                 {
                     var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, airtightQuery);
                     _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
+                    firelock.Temperature = fire;
+                    firelock.Pressure = pressure;
+                    Dirty(uid, firelock);
                 }
             }
         }
 
-        private void UpdateVisuals(EntityUid uid,
-            FirelockComponent? firelock = null,
-            DoorComponent? door = null,
-            AirtightComponent? airtight = null,
-            AppearanceComponent? appearance = null,
-            TransformComponent? xform = 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(uid, DoorVisuals.ClosedLights, false, appearance);
-                return;
-            }
-
-            var query = GetEntityQuery<AirtightComponent>();
-            if (!Resolve(uid, ref firelock, ref airtight, ref appearance, ref xform, false) || !query.Resolve(uid, ref airtight, false))
-                return;
-
-            var (fire, pressure) = CheckPressureAndFire(uid, firelock, xform, airtight, query);
-            _appearance.SetData(uid, DoorVisuals.ClosedLights, fire || pressure, appearance);
-        }
-        #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(uid, door))
-                {
-                    return _doorSystem.OnPartialClose(uid, door);
-                }
-            }
-            return false;
-        }
-
-        private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
-        {
-            // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas
-            var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid);
-
-            if (!this.IsPowered(uid, EntityManager) || (!overrideAccess && IsHoldingPressureOrFire(uid, component)))
-                args.Cancel();
-        }
-
-        private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
-        {
-            var state = CheckPressureAndFire(uid, component);
-
-            if (state.Fire)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-fire-message"),
-                    uid, args.User, PopupType.MediumCaution);
-            }
-            else if (state.Pressure)
-            {
-                _popupSystem.PopupEntity(Loc.GetString("firelock-component-is-holding-pressure-message"),
-                    uid, args.User, PopupType.MediumCaution);
-            }
-
-            if (state.Fire || state.Pressure)
-                args.PryTimeModifier *= component.LockedPryTimeModifier;
-        }
-
-        private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args)
-        {
-            var ev = new BeforeDoorAutoCloseEvent();
-            RaiseLocalEvent(uid, ev);
-            UpdateVisuals(uid, component, args);
-            if (ev.Cancelled)
-            {
-                return;
-            }
-
-            _doorSystem.SetNextStateChange(uid, component.AutocloseDelay);
-        }
-
         private void OnBeforeDoorAutoclose(EntityUid uid, FirelockComponent component, BeforeDoorAutoCloseEvent args)
         {
             if (!this.IsPowered(uid, EntityManager))
@@ -204,12 +112,6 @@ namespace Content.Server.Doors.Systems
             }
         }
 
-        public bool IsHoldingPressureOrFire(EntityUid uid, FirelockComponent firelock)
-        {
-            var result = CheckPressureAndFire(uid, firelock);
-            return result.Pressure || result.Fire;
-        }
-
         public (bool Pressure, bool Fire) CheckPressureAndFire(EntityUid uid, FirelockComponent firelock)
         {
             var query = GetEntityQuery<AirtightComponent>();
@@ -234,17 +136,17 @@ namespace Content.Server.Doors.Systems
                 return (false, false);
             }
 
-            if (!TryComp(xform.ParentUid, out GridAtmosphereComponent? gridAtmosphere))
+            if (!HasComp<GridAtmosphereComponent>(xform.ParentUid))
                 return (false, false);
 
             var grid = Comp<MapGridComponent>(xform.ParentUid);
-            var pos = grid.CoordinatesToTile(xform.Coordinates);
+            var pos = _mapping.CoordinatesToTile(xform.ParentUid, grid, xform.Coordinates);
             var minPressure = float.MaxValue;
             var maxPressure = float.MinValue;
             var minTemperature = float.MaxValue;
             var maxTemperature = float.MinValue;
-            bool holdingFire = false;
-            bool holdingPressure = false;
+            var holdingFire = false;
+            var 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.
@@ -284,7 +186,7 @@ namespace Content.Server.Doors.Systems
                 {
                     // 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))
+                    if (HasAirtightBlocker(_mapping.GetAnchoredEntities(xform.ParentUid, grid, adjacentPos), dir.GetOpposite(), airtightQuery))
                         continue;
 
                     var p = gas.Pressure;
index 97e57185cac9ad4d4206aba8ca28f1dbfd59af8e..ca62daaa0fd100f3bc90abf35e8a0d906e97492c 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Doors.Components;
+using Robust.Shared.GameStates;
 
 namespace Content.Shared.Doors.Components
 {
@@ -7,9 +7,11 @@ namespace Content.Shared.Doors.Components
     /// auto-closing on depressurization, air/fire alarm interactions, and preventing normal door functions when
     /// retaining pressure..
     /// </summary>
-    [RegisterComponent]
+    [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
     public sealed partial class FirelockComponent : Component
     {
+        #region Settings
+
         /// <summary>
         /// Pry time modifier to be used when the firelock is currently closed due to fire or pressure.
         /// </summary>
@@ -39,5 +41,47 @@ namespace Content.Shared.Doors.Components
         /// </summary>
         [DataField("alarmAutoClose"), ViewVariables(VVAccess.ReadWrite)]
         public bool AlarmAutoClose = true;
+
+        /// <summary>
+        /// The cooldown duration before a firelock can automatically close due to a hazardous environment after it has
+        /// been pried open. Measured in seconds.
+        /// </summary>
+        [DataField]
+        public TimeSpan EmergencyCloseCooldownDuration = TimeSpan.FromSeconds(2);
+
+        #endregion
+
+        #region Set by system
+
+        /// <summary>
+        /// When the firelock will be allowed to automatically close again due to a hazardous environment.
+        /// </summary>
+        [DataField]
+        public TimeSpan? EmergencyCloseCooldown;
+
+        /// <summary>
+        /// Whether the firelock can open, or is locked due to its environment.
+        /// </summary>
+        public bool IsLocked => Pressure || Temperature;
+
+        /// <summary>
+        /// Whether the firelock is holding back a hazardous pressure.
+        /// </summary>
+        [DataField, AutoNetworkedField]
+        public bool Pressure;
+
+        /// <summary>
+        /// Whether the firelock is holding back extreme temperatures.
+        /// </summary>
+        [DataField, AutoNetworkedField]
+        public bool Temperature;
+
+        /// <summary>
+        /// Whether the airlock is powered.
+        /// </summary>
+        [DataField, AutoNetworkedField]
+        public bool Powered;
+
+        #endregion
     }
 }
index b58b7b265e9aba9112161de5b8ccb4f43f6a6f3c..20456c14777be0befd206d36c4e8c76069da4d32 100644 (file)
@@ -6,7 +6,6 @@ using Content.Shared.Damage;
 using Content.Shared.Database;
 using Content.Shared.Doors.Components;
 using Content.Shared.Emag.Systems;
-using Content.Shared.Hands.Components;
 using Content.Shared.Interaction;
 using Content.Shared.Physics;
 using Content.Shared.Popups;
@@ -466,7 +465,7 @@ public abstract partial class SharedDoorSystem : EntitySystem
 
         door.Partial = true;
 
-        // Make sure no entity waled into the airlock when it started closing.
+        // Make sure no entity walked into the airlock when it started closing.
         if (!CanClose(uid, door))
         {
             door.NextStateChange = GameTiming.CurTime + door.OpenTimeTwo;
diff --git a/Content.Shared/Doors/Systems/SharedFirelockSystem.cs b/Content.Shared/Doors/Systems/SharedFirelockSystem.cs
new file mode 100644 (file)
index 0000000..7d033ef
--- /dev/null
@@ -0,0 +1,125 @@
+using Content.Shared.Access.Systems;
+using Content.Shared.Doors.Components;
+using Content.Shared.Popups;
+using Content.Shared.Prying.Components;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Doors.Systems;
+
+public abstract class SharedFirelockSystem : EntitySystem
+{
+    [Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
+    [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedDoorSystem _doorSystem = default!;
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<FirelockComponent, DoorStateChangedEvent>(OnUpdateState);
+
+        // Access/Prying
+        SubscribeLocalEvent<FirelockComponent, BeforeDoorOpenedEvent>(OnBeforeDoorOpened);
+        SubscribeLocalEvent<FirelockComponent, GetPryTimeModifierEvent>(OnDoorGetPryTimeModifier);
+        SubscribeLocalEvent<FirelockComponent, PriedEvent>(OnAfterPried);
+
+        // Visuals
+        SubscribeLocalEvent<FirelockComponent, MapInitEvent>(UpdateVisuals);
+        SubscribeLocalEvent<FirelockComponent, ComponentStartup>(UpdateVisuals);
+    }
+
+    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
+            || firelock.EmergencyCloseCooldown != null
+            && _gameTiming.CurTime < firelock.EmergencyCloseCooldown)
+            return false;
+
+        if (!_doorSystem.TryClose(uid, door))
+            return false;
+
+        return _doorSystem.OnPartialClose(uid, door);
+    }
+
+    private void OnUpdateState(EntityUid uid, FirelockComponent component, DoorStateChangedEvent args)
+    {
+        var ev = new BeforeDoorAutoCloseEvent();
+        RaiseLocalEvent(uid, ev);
+        UpdateVisuals(uid, component, args);
+        if (ev.Cancelled)
+        {
+            return;
+        }
+
+        _doorSystem.SetNextStateChange(uid, component.AutocloseDelay);
+    }
+
+    #region Access/Prying
+
+    private void OnBeforeDoorOpened(EntityUid uid, FirelockComponent component, BeforeDoorOpenedEvent args)
+    {
+        // Give the Door remote the ability to force a firelock open even if it is holding back dangerous gas
+        var overrideAccess = (args.User != null) && _accessReaderSystem.IsAllowed(args.User.Value, uid);
+
+        if (!component.Powered || (!overrideAccess && component.IsLocked))
+            args.Cancel();
+    }
+
+    private void OnDoorGetPryTimeModifier(EntityUid uid, FirelockComponent component, ref GetPryTimeModifierEvent args)
+    {
+        if (component.Temperature)
+        {
+            _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-fire-message"),
+                uid, args.User, PopupType.MediumCaution);
+        }
+        else if (component.Pressure)
+        {
+            _popupSystem.PopupClient(Loc.GetString("firelock-component-is-holding-pressure-message"),
+                uid, args.User, PopupType.MediumCaution);
+        }
+
+        if (component.IsLocked)
+            args.PryTimeModifier *= component.LockedPryTimeModifier;
+    }
+
+    private void OnAfterPried(EntityUid uid, FirelockComponent component, ref PriedEvent args)
+    {
+        component.EmergencyCloseCooldown = _gameTiming.CurTime + component.EmergencyCloseCooldownDuration;
+    }
+
+    #endregion
+
+    #region Visuals
+
+    private void UpdateVisuals(EntityUid uid, FirelockComponent component, EntityEventArgs args) => UpdateVisuals(uid, component);
+
+    private void UpdateVisuals(EntityUid uid,
+        FirelockComponent? firelock = null,
+        DoorComponent? door = null,
+        AppearanceComponent? appearance = 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(uid, DoorVisuals.ClosedLights, false, appearance);
+            return;
+        }
+
+        if (!Resolve(uid, ref firelock, ref appearance, false))
+            return;
+
+        _appearance.SetData(uid, DoorVisuals.ClosedLights, firelock.IsLocked, appearance);
+    }
+
+    #endregion
+}
index f0e61dc9685d611603368cb1bc194d5fc5f71482..b6b69a2577d2df74a7d47df6917744c87540be26 100644 (file)
@@ -8,4 +8,6 @@ namespace Content.Shared.Prying.Components;
 [RegisterComponent, NetworkedComponent]
 public sealed partial class PryUnpoweredComponent : Component
 {
+    [DataField]
+    public float PryModifier = 0.1f;
 }
index ab87585c706f9ac250217627d48b30c1ec20b3cf..372c89c9ae090e6e0b24bd5ca5caf18fcd260e84 100644 (file)
@@ -93,17 +93,17 @@ public sealed class PryingSystem : EntitySystem
         id = null;
 
         // We don't care about displaying a message if no tool was used.
-        if (!CanPry(target, user, out _))
+        if (!TryComp<PryUnpoweredComponent>(target, out var unpoweredComp) || !CanPry(target, user, out _, unpoweredComp: unpoweredComp))
             // If we have reached this point we want the event that caused this
             // to be marked as handled.
             return true;
 
         // hand-prying is much slower
-        var modifier = CompOrNull<PryingComponent>(user)?.SpeedModifier ?? 0.1f;
+        var modifier = CompOrNull<PryingComponent>(user)?.SpeedModifier ?? unpoweredComp.PryModifier;
         return StartPry(target, user, null, modifier, out id);
     }
 
-    private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null)
+    private bool CanPry(EntityUid target, EntityUid user, out string? message, PryingComponent? comp = null, PryUnpoweredComponent? unpoweredComp = null)
     {
         BeforePryEvent canev;
 
@@ -113,7 +113,7 @@ public sealed class PryingSystem : EntitySystem
         }
         else
         {
-            if (!TryComp<PryUnpoweredComponent>(target, out _))
+            if (!Resolve(target, ref unpoweredComp))
             {
                 message = null;
                 return false;
index fcdb432dce60f823776a362ded230e58d71545bb..860db862aeefc0f114c5c04e87bb58474c7af159 100644 (file)
     - type: DoorBolt
     - type: AccessReader
       access: [ [ "Engineering" ] ]
+    - type: PryUnpowered
+      pryModifier: 0.5
 
 - type: entity
   id: Firelock