From: Sir Warock <67167466+SirWarock@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:08:45 +0000 (+0100) Subject: Pry open critical Borgs (#42319) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=7540c8f152670f152da75b644bfb2faff979f88d;p=space-station-14.git 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 --- 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.