From 8ca7cb59a3b83e86c9b27a9549bf85a9f914b6fe Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 28 Jan 2024 01:28:02 +0100 Subject: [PATCH] Security barriers take 5 seconds to (un)lock (#24637) People are using these as unhackable and hard-to-tailgate airlocks into sec. They should not be trivial for security officers to move through. Made LockComponent have configurable lock times to implement this. --- Content.Shared/Lock/LockComponent.cs | 49 +++++++++++++++++ Content.Shared/Lock/LockSystem.cs | 55 ++++++++++++++++++- .../Objects/Specific/Security/barrier.yml | 2 + 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/Content.Shared/Lock/LockComponent.cs b/Content.Shared/Lock/LockComponent.cs index 174818c4e8..b3c4659749 100644 --- a/Content.Shared/Lock/LockComponent.cs +++ b/Content.Shared/Lock/LockComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.DoAfter; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -50,6 +51,26 @@ public sealed partial class LockComponent : Component [DataField("breakOnEmag")] [AutoNetworkedField] public bool BreakOnEmag = true; + + /// + /// Amount of do-after time needed to lock the entity. + /// + /// + /// If set to zero, no do-after will be used. + /// + [DataField] + [AutoNetworkedField] + public TimeSpan LockTime; + + /// + /// Amount of do-after time needed to unlock the entity. + /// + /// + /// If set to zero, no do-after will be used. + /// + [DataField] + [AutoNetworkedField] + public TimeSpan UnlockTime; } /// @@ -64,3 +85,31 @@ public record struct LockToggleAttemptEvent(EntityUid User, bool Silent = false, /// [ByRefEvent] public readonly record struct LockToggledEvent(bool Locked); + +/// +/// Used to lock a lockable entity that has a lock time configured. +/// +/// +/// +[Serializable, NetSerializable] +public sealed partial class LockDoAfter : DoAfterEvent +{ + public override DoAfterEvent Clone() + { + return this; + } +} + +/// +/// Used to unlock a lockable entity that has an unlock time configured. +/// +/// +/// +[Serializable, NetSerializable] +public sealed partial class UnlockDoAfter : DoAfterEvent +{ + public override DoAfterEvent Clone() + { + return this; + } +} diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 7babc6a9c0..e5f53b4080 100644 --- a/Content.Shared/Lock/LockSystem.cs +++ b/Content.Shared/Lock/LockSystem.cs @@ -1,5 +1,6 @@ using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.Examine; using Content.Shared.Hands.Components; @@ -26,6 +27,7 @@ public sealed class LockSystem : EntitySystem [Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; /// public override void Initialize() @@ -38,6 +40,8 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnExamined); SubscribeLocalEvent>(AddToggleLockVerb); SubscribeLocalEvent(OnEmagged); + SubscribeLocalEvent(OnDoAfterLock); + SubscribeLocalEvent(OnDoAfterUnlock); } private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) @@ -86,11 +90,15 @@ public sealed class LockSystem : EntitySystem /// /// Attmempts to lock a given entity /// + /// + /// If the lock is set to require a do-after, a true return value only indicates that the do-after started. + /// /// The entity with the lock /// The person trying to lock it /// + /// If true, skip the required do-after if one is configured. /// If locking was successful - public bool TryLock(EntityUid uid, EntityUid user, LockComponent? lockComp = null) + public bool TryLock(EntityUid uid, EntityUid user, LockComponent? lockComp = null, bool skipDoAfter = false) { if (!Resolve(uid, ref lockComp)) return false; @@ -101,6 +109,16 @@ public sealed class LockSystem : EntitySystem if (!HasUserAccess(uid, user, quiet: false)) return false; + if (!skipDoAfter && lockComp.LockTime != TimeSpan.Zero) + { + return _doAfter.TryStartDoAfter( + new DoAfterArgs(EntityManager, user, lockComp.LockTime, new LockDoAfter(), uid, uid) + { + BreakOnDamage = true, BreakOnTargetMove = true, BreakOnUserMove = true, RequireCanInteract = true, + NeedHand = true + }); + } + _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-do-lock-success", ("entityName", Identity.Name(uid, EntityManager))), uid, user); _audio.PlayPredicted(lockComp.LockSound, uid, user); @@ -117,6 +135,9 @@ public sealed class LockSystem : EntitySystem /// /// Forces a given entity to be unlocked /// + /// + /// This does not process do-after times. + /// /// The entity with the lock /// The person unlocking it. Can be null /// @@ -145,11 +166,15 @@ public sealed class LockSystem : EntitySystem /// /// Attmempts to unlock a given entity /// + /// + /// If the lock is set to require a do-after, a true return value only indicates that the do-after started. + /// /// The entity with the lock /// The person trying to unlock it /// + /// If true, skip the required do-after if one is configured. /// If locking was successful - public bool TryUnlock(EntityUid uid, EntityUid user, LockComponent? lockComp = null) + public bool TryUnlock(EntityUid uid, EntityUid user, LockComponent? lockComp = null, bool skipDoAfter = false) { if (!Resolve(uid, ref lockComp)) return false; @@ -160,6 +185,16 @@ public sealed class LockSystem : EntitySystem if (!HasUserAccess(uid, user, quiet: false)) return false; + if (!skipDoAfter && lockComp.UnlockTime != TimeSpan.Zero) + { + return _doAfter.TryStartDoAfter( + new DoAfterArgs(EntityManager, user, lockComp.LockTime, new UnlockDoAfter(), uid, uid) + { + BreakOnDamage = true, BreakOnTargetMove = true, BreakOnUserMove = true, RequireCanInteract = true, + NeedHand = true + }); + } + Unlock(uid, user, lockComp); return true; } @@ -219,5 +254,21 @@ public sealed class LockSystem : EntitySystem RemComp(uid); //Literally destroys the lock as a tell it was emagged args.Handled = true; } + + private void OnDoAfterLock(EntityUid uid, LockComponent component, LockDoAfter args) + { + if (args.Cancelled) + return; + + TryLock(uid, args.User, skipDoAfter: true); + } + + private void OnDoAfterUnlock(EntityUid uid, LockComponent component, UnlockDoAfter args) + { + if (args.Cancelled) + return; + + TryUnlock(uid, args.User, skipDoAfter: true); + } } diff --git a/Resources/Prototypes/Entities/Objects/Specific/Security/barrier.yml b/Resources/Prototypes/Entities/Objects/Specific/Security/barrier.yml index 035185487d..3a31edf7f1 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Security/barrier.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Security/barrier.yml @@ -45,6 +45,8 @@ - type: Lock locked: false lockOnClick: true # toggle lock just by clicking on barrier + lockTime: 5 + unlockTime: 5 - type: Damageable damageContainer: Inorganic damageModifierSet: Metallic