Pry open critical Borgs (#42319)

* One commit ops

* Please the maintainer gods

* More requested changes

* review

* actually this is probably a good idea

---------

Co-authored-by: ScarKy0 <scarky0@onet.eu>
This commit is contained in:
Sir Warock
2026-01-13 23:08:45 +01:00
committed by GitHub
parent 3cec0aa476
commit 7540c8f152
6 changed files with 231 additions and 1 deletions

View File

@@ -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;
/// <summary>
/// This component lets the lock on this entity be pried open when the entity is in critical or dead state.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(BypassLockSystem))]
public sealed partial class BypassLockComponent : Component
{
/// <summary>
/// The tool quality needed to bypass the lock.
/// </summary>
[DataField]
public ProtoId<ToolQualityPrototype> BypassingTool = "Prying";
/// <summary>
/// Amount of time in seconds it takes to bypass
/// </summary>
[DataField]
public TimeSpan BypassDelay = TimeSpan.FromSeconds(5f);
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Lock.BypassLock.Systems;
using Content.Shared.Mobs;
using Robust.Shared.GameStates;
namespace Content.Shared.Lock.BypassLock.Components;
/// <summary>
/// This component lets the lock on this entity be pried open when the entity is in critical or dead state.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(BypassLockSystem))]
public sealed partial class BypassLockRequiresMobStateComponent : Component
{
/// <summary>
/// The mobstate where the lock can be bypassed.
/// </summary>
[DataField]
public HashSet<MobState> RequiredMobState = [];
}

View File

@@ -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<BypassLockRequiresMobStateComponent, ForceOpenLockAttemptEvent>(OnForceOpenLockAttempt);
SubscribeLocalEvent<BypassLockRequiresMobStateComponent, CheckBypassLockVerbRequirements>(OnGetVerb);
}
private void OnForceOpenLockAttempt(Entity<BypassLockRequiresMobStateComponent> target, ref ForceOpenLockAttemptEvent args)
{
if (!TryComp<MobStateComponent>(target, out var mobState))
return;
args.CanForceOpen &= target.Comp.RequiredMobState.Contains(mobState.CurrentState);
}
private void OnGetVerb(Entity<BypassLockRequiresMobStateComponent> target, ref CheckBypassLockVerbRequirements args)
{
if (!TryComp<MobStateComponent>(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()));
}
}
}

View File

@@ -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<BypassLockComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<LockComponent, ForceOpenLockDoAfterEvent>(OnBypassAccessDoAfterEvent);
SubscribeLocalEvent<BypassLockComponent, GetVerbsEvent<InteractionVerb>>(OnGetVerb);
InitializeMobStateLockSystem();
}
private void OnInteractUsing(Entity<BypassLockComponent> 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<BypassLockComponent> 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<LockComponent> target, ref ForceOpenLockDoAfterEvent args)
{
if (args.Cancelled)
return;
_lock.Unlock(target, args.User, target.Comp);
}
private void OnGetVerb(Entity<BypassLockComponent> target, ref GetVerbsEvent<InteractionVerb> 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);
}
}
/// <summary>
/// This event gets raised on the entity with the <see cref="BypassLockRequiresMobStateComponent"/> after someone finished
/// a doafter forcing the lock open.
/// </summary>
[Serializable, NetSerializable]
public sealed partial class ForceOpenLockDoAfterEvent : SimpleDoAfterEvent;
/// <summary>
/// This gets raised on the target whose lock is attempted to be forced open.
/// </summary>
/// <param name="User">Entity attempting to open this.</param>
/// <param name="CanForceOpen">Whether the lock can be forced open.</param>
[ByRefEvent]
public record struct ForceOpenLockAttemptEvent(EntityUid User, bool CanForceOpen = true);
/// <summary>
/// This gets raised on the target that is being right-clicked to check for verb requirements.
/// </summary>
/// <param name="Verb">The interaction verb that will be shown.</param>
/// <param name="RightTool">Whether the tool has the right properties to force the lock open.</param>
/// <param name="ShowVerb">Whether the verb should be shown.</param>
/// <param name="ToolQuality">The required tool quality to force the lock open.</param>
[ByRefEvent]
public record struct CheckBypassLockVerbRequirements(InteractionVerb Verb, bool RightTool, bool ShowVerb, ProtoId<ToolQualityPrototype> ToolQuality);

View File

@@ -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.

View File

@@ -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.