diff --git a/Content.Shared/Lock/BypassLock/Components/BypassLockComponent.cs b/Content.Shared/Lock/BypassLock/Components/BypassLockComponent.cs
new file mode 100644
index 0000000000..38c6275d86
--- /dev/null
+++ b/Content.Shared/Lock/BypassLock/Components/BypassLockComponent.cs
@@ -0,0 +1,25 @@
+using Content.Shared.Lock.BypassLock.Systems;
+using Content.Shared.Tools;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Lock.BypassLock.Components;
+
+///
+/// This component lets the lock on this entity be pried open when the entity is in critical or dead state.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(BypassLockSystem))]
+public sealed partial class BypassLockComponent : Component
+{
+ ///
+ /// The tool quality needed to bypass the lock.
+ ///
+ [DataField]
+ public ProtoId BypassingTool = "Prying";
+
+ ///
+ /// Amount of time in seconds it takes to bypass
+ ///
+ [DataField]
+ public TimeSpan BypassDelay = TimeSpan.FromSeconds(5f);
+}
diff --git a/Content.Shared/Lock/BypassLock/Components/BypassLockRequiresMobStateComponent.cs b/Content.Shared/Lock/BypassLock/Components/BypassLockRequiresMobStateComponent.cs
new file mode 100644
index 0000000000..7e40a8fcd5
--- /dev/null
+++ b/Content.Shared/Lock/BypassLock/Components/BypassLockRequiresMobStateComponent.cs
@@ -0,0 +1,18 @@
+using Content.Shared.Lock.BypassLock.Systems;
+using Content.Shared.Mobs;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Lock.BypassLock.Components;
+
+///
+/// This component lets the lock on this entity be pried open when the entity is in critical or dead state.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(BypassLockSystem))]
+public sealed partial class BypassLockRequiresMobStateComponent : Component
+{
+ ///
+ /// The mobstate where the lock can be bypassed.
+ ///
+ [DataField]
+ public HashSet RequiredMobState = [];
+}
diff --git a/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.MobState.cs b/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.MobState.cs
new file mode 100644
index 0000000000..02ec502004
--- /dev/null
+++ b/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.MobState.cs
@@ -0,0 +1,41 @@
+using Content.Shared.Lock.BypassLock.Components;
+using Content.Shared.Mobs.Components;
+
+namespace Content.Shared.Lock.BypassLock.Systems;
+
+public sealed partial class BypassLockSystem
+{
+ private void InitializeMobStateLockSystem()
+ {
+ SubscribeLocalEvent(OnForceOpenLockAttempt);
+ SubscribeLocalEvent(OnGetVerb);
+ }
+
+ private void OnForceOpenLockAttempt(Entity target, ref ForceOpenLockAttemptEvent args)
+ {
+ if (!TryComp(target, out var mobState))
+ return;
+
+ args.CanForceOpen &= target.Comp.RequiredMobState.Contains(mobState.CurrentState);
+ }
+
+ private void OnGetVerb(Entity target, ref CheckBypassLockVerbRequirements args)
+ {
+ if (!TryComp(target, out var mobState))
+ return;
+
+ // Only show disabled verb on a too healthy target when they have the right tool.
+ if (!target.Comp.RequiredMobState.Contains(mobState.CurrentState) && args.RightTool)
+ {
+ args.Verb.Disabled = true;
+ args.Verb.Message = Loc.GetString("bypass-lock-disabled-healthy");
+ }
+ // Show verb of using the wrong tool when the target is critical.
+ else if (target.Comp.RequiredMobState.Contains(mobState.CurrentState) && !args.RightTool)
+ {
+ args.ShowVerb = true;
+ args.Verb.Disabled = true;
+ args.Verb.Message = Loc.GetString("bypass-lock-disabled-wrong-tool", ("quality", args.ToolQuality.ToString().ToLower()));
+ }
+ }
+}
diff --git a/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.cs b/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.cs
new file mode 100644
index 0000000000..1dcc4ee74a
--- /dev/null
+++ b/Content.Shared/Lock/BypassLock/Systems/BypassLockSystem.cs
@@ -0,0 +1,132 @@
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.DoAfter;
+using Content.Shared.Interaction;
+using Content.Shared.Lock.BypassLock.Components;
+using Content.Shared.Tools;
+using Content.Shared.Tools.Systems;
+using Content.Shared.Verbs;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Lock.BypassLock.Systems;
+
+public sealed partial class BypassLockSystem : EntitySystem
+{
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly LockSystem _lock = default!;
+ [Dependency] private readonly SharedToolSystem _tool = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInteractUsing);
+ SubscribeLocalEvent(OnBypassAccessDoAfterEvent);
+ SubscribeLocalEvent>(OnGetVerb);
+
+ InitializeMobStateLockSystem();
+ }
+
+ private void OnInteractUsing(Entity target, ref InteractUsingEvent args)
+ {
+ if (target.Owner == args.User)
+ return;
+
+ if (!_tool.HasQuality(args.Used, target.Comp.BypassingTool)
+ || !_lock.IsLocked(target.Owner))
+ return;
+
+ var ev = new ForceOpenLockAttemptEvent(args.User);
+ RaiseLocalEvent(target.Owner, ref ev);
+
+ if (!ev.CanForceOpen)
+ return;
+
+ args.Handled = TryStartDoAfter(target, args.User, args.Used);
+ }
+
+ private bool TryStartDoAfter(Entity target, EntityUid user, EntityUid used)
+ {
+ if (!_tool.UseTool(
+ used,
+ user,
+ target,
+ (float) target.Comp.BypassDelay.TotalSeconds,
+ target.Comp.BypassingTool,
+ new ForceOpenLockDoAfterEvent()))
+ {
+ return false;
+ }
+
+ _adminLogger.Add(LogType.Action, LogImpact.Low,
+ $"{ToPrettyString(user):user} is prying {ToPrettyString(target):target}'s lock open at {Transform(target).Coordinates:targetlocation}");
+ return true;
+ }
+
+ private void OnBypassAccessDoAfterEvent(Entity target, ref ForceOpenLockDoAfterEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ _lock.Unlock(target, args.User, target.Comp);
+ }
+
+ private void OnGetVerb(Entity target, ref GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess || args.Using == null)
+ return;
+
+ var rightTool = _tool.HasQuality(args.Using.Value, target.Comp.BypassingTool);
+ var item = args.Using.Value;
+ var bypassVerb = new InteractionVerb
+ {
+ IconEntity = GetNetEntity(item),
+ };
+
+ bypassVerb.Text = bypassVerb.Message = Loc.GetString("bypass-lock-verb");
+
+ var ev = new CheckBypassLockVerbRequirements(bypassVerb, rightTool, rightTool, target.Comp.BypassingTool);
+ RaiseLocalEvent(target, ref ev);
+
+ if (!ev.ShowVerb)
+ return;
+
+ var user = args.User;
+
+ bypassVerb.Act = () => TryStartDoAfter(target, user, item);
+
+ if (!_lock.IsLocked(target.Owner))
+ {
+ bypassVerb.Disabled = true;
+ bypassVerb.Message = Loc.GetString("bypass-lock-disabled-already-open");
+ }
+
+ args.Verbs.Add(bypassVerb);
+ }
+}
+
+///
+/// This event gets raised on the entity with the after someone finished
+/// a doafter forcing the lock open.
+///
+[Serializable, NetSerializable]
+public sealed partial class ForceOpenLockDoAfterEvent : SimpleDoAfterEvent;
+
+///
+/// This gets raised on the target whose lock is attempted to be forced open.
+///
+/// Entity attempting to open this.
+/// Whether the lock can be forced open.
+[ByRefEvent]
+public record struct ForceOpenLockAttemptEvent(EntityUid User, bool CanForceOpen = true);
+
+///
+/// This gets raised on the target that is being right-clicked to check for verb requirements.
+///
+/// The interaction verb that will be shown.
+/// Whether the tool has the right properties to force the lock open.
+/// Whether the verb should be shown.
+/// The required tool quality to force the lock open.
+[ByRefEvent]
+public record struct CheckBypassLockVerbRequirements(InteractionVerb Verb, bool RightTool, bool ShowVerb, ProtoId ToolQuality);
diff --git a/Resources/Locale/en-US/lock/bypass-lock-component.ftl b/Resources/Locale/en-US/lock/bypass-lock-component.ftl
new file mode 100644
index 0000000000..d37996041d
--- /dev/null
+++ b/Resources/Locale/en-US/lock/bypass-lock-component.ftl
@@ -0,0 +1,4 @@
+bypass-lock-verb = Force open the access lock
+bypass-lock-disabled-healthy = The lock needs to be damaged further before it can be forced open.
+bypass-lock-disabled-wrong-tool = This lock requires {$quality} to be forced open.
+bypass-lock-disabled-already-open = The lock is already open.
diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
index 681cb86796..7b2adf1a22 100644
--- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
+++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml
@@ -114,7 +114,6 @@
- BorgChassis
- RoboticsConsole
- type: WiresPanel
- openingTool: Prying
- type: ActivatableUIRequiresPanel
- type: NameIdentifier
group: Silicon
@@ -247,6 +246,11 @@
damageProtection:
flatReductions:
Heat: 10 # capable of touching light bulbs and stoves without feeling pain!
+ - type: BypassLock
+ - type: BypassLockRequiresMobState
+ requiredMobState:
+ - Critical
+ - Dead
- type: entity
abstract: true
@@ -365,6 +369,8 @@
Unsexed: UnisexSiliconSyndicate
- type: PointLight
color: "#dd200b"
+ - type: BypassLock
+ bypassDelay: 15 # We don't want people to easily be able to remove syndieborg brains.
- type: entity
id: BaseBorgChassisDerelict
@@ -422,6 +428,8 @@
Unsexed: UnisexSiliconSyndicate
- type: PointLight
color: "#dd200b"
+ - type: BypassLock
+ bypassDelay: 15 # We don't want people to easily be able to remove syndieborg brains.
- type: entity
parent: BaseBorgChassisNotIonStormable
@@ -539,3 +547,5 @@
interactSuccessSound:
path: /Audio/Ambience/Objects/periodic_beep.ogg
- type: Xenoborg
+ - type: BypassLock
+ bypassDelay: 15 # We don't want people to easily be able to remove xenoborg brains.