From 1b69762816c5ae5184baa024a4bcd23b75236a6c Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Sun, 31 Mar 2024 02:34:17 -0400 Subject: [PATCH] disallow unanchoring or opening panels on locked emitters/APEs (#26600) * disallow unanchoring or opening panels on locked emitters/APEs * no locking open panels * oops * needback feedback * Update Content.Shared/Lock/LockSystem.cs * Update Content.Shared/Lock/LockSystem.cs * Update Content.Shared/Lock/LockSystem.cs * sanity --------- Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth --- Content.Client/Popups/PopupSystem.cs | 7 +- Content.Server/Popups/PopupSystem.cs | 2 +- Content.Shared/Lock/LockSystem.cs | 69 ++++++++++++++++--- .../Lock/LockedAnchorableComponent.cs | 13 ++++ .../Lock/LockedWiresPanelComponent.cs | 13 ++++ Content.Shared/Popups/SharedPopupSystem.cs | 2 +- Content.Shared/Wires/SharedWiresSystem.cs | 24 ++++++- Content.Shared/Wires/WiresPanelComponent.cs | 6 ++ .../Locale/en-US/lock/lock-component.ftl | 3 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 1 + .../Structures/Machines/anomaly_equipment.yml | 2 + .../Power/Generation/Singularity/emitter.yml | 2 + 12 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 Content.Shared/Lock/LockedAnchorableComponent.cs create mode 100644 Content.Shared/Lock/LockedWiresPanelComponent.cs diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs index 479fb02906..fcc8bfc420 100644 --- a/Content.Client/Popups/PopupSystem.cs +++ b/Content.Client/Popups/PopupSystem.cs @@ -163,10 +163,13 @@ namespace Content.Client.Popups PopupEntity(message, uid, type); } - public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small) + public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small) { + if (recipient == null) + return; + if (_timing.IsFirstTimePredicted) - PopupEntity(message, uid, recipient, type); + PopupEntity(message, uid, recipient.Value, type); } public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small) diff --git a/Content.Server/Popups/PopupSystem.cs b/Content.Server/Popups/PopupSystem.cs index c5eb3819b5..237ca33a4d 100644 --- a/Content.Server/Popups/PopupSystem.cs +++ b/Content.Server/Popups/PopupSystem.cs @@ -88,7 +88,7 @@ namespace Content.Server.Popups RaiseNetworkEvent(new PopupEntityEvent(message, type, GetNetEntity(uid)), actor.PlayerSession); } - public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small) + public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small) { // do nothing duh its for client only } diff --git a/Content.Shared/Lock/LockSystem.cs b/Content.Shared/Lock/LockSystem.cs index 74cf5496d9..5644a6b02f 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.Construction.Components; using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.Examine; @@ -9,6 +10,7 @@ using Content.Shared.Interaction; using Content.Shared.Popups; using Content.Shared.Storage.Components; using Content.Shared.Verbs; +using Content.Shared.Wires; using JetBrains.Annotations; using Robust.Shared.Audio.Systems; using Robust.Shared.Utility; @@ -40,8 +42,11 @@ public sealed class LockSystem : EntitySystem SubscribeLocalEvent(OnEmagged); SubscribeLocalEvent(OnDoAfterLock); SubscribeLocalEvent(OnDoAfterUnlock); - } + SubscribeLocalEvent(OnLockToggleAttempt); + SubscribeLocalEvent(OnAttemptChangePanel); + SubscribeLocalEvent(OnUnanchorAttempt); + } private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) { _appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked); @@ -226,18 +231,18 @@ public sealed class LockSystem : EntitySystem private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent args) { - if (!args.CanAccess || !args.CanInteract || !CanToggleLock(uid, args.User)) + if (!args.CanAccess || !args.CanInteract) return; AlternativeVerb verb = new() { - Act = component.Locked ? - () => TryUnlock(uid, args.User, component) : - () => TryLock(uid, args.User, component), + Act = component.Locked + ? () => TryUnlock(uid, args.User, component) + : () => TryLock(uid, args.User, component), Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"), - Icon = component.Locked ? - new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unlock.svg.192dpi.png")) : - new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")), + Icon = !component.Locked + ? new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/lock.svg.192dpi.png")) + : new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/unlock.svg.192dpi.png")), }; args.Verbs.Add(verb); } @@ -275,5 +280,53 @@ public sealed class LockSystem : EntitySystem TryUnlock(uid, args.User, skipDoAfter: true); } + + private void OnLockToggleAttempt(Entity ent, ref LockToggleAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!TryComp(ent, out var panel) || !panel.Open) + return; + + if (!args.Silent) + { + _sharedPopupSystem.PopupClient(Loc.GetString("construction-step-condition-wire-panel-close"), + ent, + args.User); + } + args.Cancelled = true; + } + + + private void OnAttemptChangePanel(Entity ent, ref AttemptChangePanelEvent args) + { + if (args.Cancelled) + return; + + if (!TryComp(ent, out var lockComp) || !lockComp.Locked) + return; + + _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail", + ("target", Identity.Entity(ent, EntityManager))), + ent, + args.User); + args.Cancelled = true; + } + + private void OnUnanchorAttempt(Entity ent, ref UnanchorAttemptEvent args) + { + if (args.Cancelled) + return; + + if (!TryComp(ent, out var lockComp) || !lockComp.Locked) + return; + + _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail", + ("target", Identity.Entity(ent, EntityManager))), + ent, + args.User); + args.Cancel(); + } } diff --git a/Content.Shared/Lock/LockedAnchorableComponent.cs b/Content.Shared/Lock/LockedAnchorableComponent.cs new file mode 100644 index 0000000000..781b7f6532 --- /dev/null +++ b/Content.Shared/Lock/LockedAnchorableComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Construction.Components; +using Robust.Shared.GameStates; + +namespace Content.Shared.Lock; + +/// +/// This is used for a that cannot be unanchored while locked. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] +public sealed partial class LockedAnchorableComponent : Component +{ + +} diff --git a/Content.Shared/Lock/LockedWiresPanelComponent.cs b/Content.Shared/Lock/LockedWiresPanelComponent.cs new file mode 100644 index 0000000000..1dbe6a4932 --- /dev/null +++ b/Content.Shared/Lock/LockedWiresPanelComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.Wires; +using Robust.Shared.GameStates; + +namespace Content.Shared.Lock; + +/// +/// This is used for a that cannot be opened while locked. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))] +public sealed partial class LockedWiresPanelComponent : Component +{ + +} diff --git a/Content.Shared/Popups/SharedPopupSystem.cs b/Content.Shared/Popups/SharedPopupSystem.cs index aeb85de2f5..b199884afb 100644 --- a/Content.Shared/Popups/SharedPopupSystem.cs +++ b/Content.Shared/Popups/SharedPopupSystem.cs @@ -86,7 +86,7 @@ namespace Content.Shared.Popups /// Variant of that only runs on the client, outside of prediction. /// Useful for shared code that is always ran by both sides to avoid duplicate popups. /// - public abstract void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small); + public abstract void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small); /// /// Variant of for use with prediction. The local client will show diff --git a/Content.Shared/Wires/SharedWiresSystem.cs b/Content.Shared/Wires/SharedWiresSystem.cs index f069687ffb..b4b0768e0f 100644 --- a/Content.Shared/Wires/SharedWiresSystem.cs +++ b/Content.Shared/Wires/SharedWiresSystem.cs @@ -28,15 +28,24 @@ public abstract class SharedWiresSystem : EntitySystem if (args.Cancelled) return; - TogglePanel(uid, panel, !panel.Open); + if (!TogglePanel(uid, panel, !panel.Open, args.User)) + return; + AdminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(args.User):user} screwed {ToPrettyString(uid):target}'s maintenance panel {(panel.Open ? "open" : "closed")}"); var sound = panel.Open ? panel.ScrewdriverOpenSound : panel.ScrewdriverCloseSound; Audio.PlayPredicted(sound, uid, args.User); + args.Handled = true; } private void OnInteractUsing(Entity ent, ref InteractUsingEvent args) { + if (!Tool.HasQuality(args.Used, ent.Comp.OpeningTool)) + return; + + if (!CanTogglePanel(ent, args.User)) + return; + if (!Tool.UseTool( args.Used, args.User, @@ -89,14 +98,25 @@ public abstract class SharedWiresSystem : EntitySystem Appearance.SetData(uid, WiresVisuals.MaintenancePanelState, panel.Open && panel.Visible, appearance); } - public void TogglePanel(EntityUid uid, WiresPanelComponent component, bool open) + public bool TogglePanel(EntityUid uid, WiresPanelComponent component, bool open, EntityUid? user = null) { + if (!CanTogglePanel((uid, component), user)) + return false; + component.Open = open; UpdateAppearance(uid, component); Dirty(uid, component); var ev = new PanelChangedEvent(component.Open); RaiseLocalEvent(uid, ref ev); + return true; + } + + public bool CanTogglePanel(Entity ent, EntityUid? user) + { + var attempt = new AttemptChangePanelEvent(ent.Comp.Open, user); + RaiseLocalEvent(ent, ref attempt); + return !attempt.Cancelled; } public bool IsPanelOpen(Entity entity) diff --git a/Content.Shared/Wires/WiresPanelComponent.cs b/Content.Shared/Wires/WiresPanelComponent.cs index 9c7444778e..a18e590e21 100644 --- a/Content.Shared/Wires/WiresPanelComponent.cs +++ b/Content.Shared/Wires/WiresPanelComponent.cs @@ -57,6 +57,12 @@ public sealed partial class WiresPanelComponent : Component public LocId? ExamineTextOpen = "wires-panel-component-on-examine-open"; } +/// +/// Event raised on a before its open state is about to be changed. +/// +[ByRefEvent] +public record struct AttemptChangePanelEvent(bool Open, EntityUid? User, bool Cancelled = false); + /// /// Event raised when a panel is opened or closed. /// diff --git a/Resources/Locale/en-US/lock/lock-component.ftl b/Resources/Locale/en-US/lock/lock-component.ftl index f9f975c96e..380605697b 100644 --- a/Resources/Locale/en-US/lock/lock-component.ftl +++ b/Resources/Locale/en-US/lock/lock-component.ftl @@ -3,8 +3,9 @@ lock-comp-on-examined-is-unlocked = The {$entityName} seems to be unlocked. lock-comp-do-lock-success = You lock the {$entityName}. lock-comp-do-unlock-success = You unlock the {$entityName}. lock-comp-has-user-access-fail = Access denied +lock-comp-generic-fail = {CAPITALIZE(SUBJECT($target))} {CONJUGATE-BE($target)} locked. ## ToggleLockVerb toggle-lock-verb-unlock = Unlock -toggle-lock-verb-lock = Lock \ No newline at end of file +toggle-lock-verb-lock = Lock diff --git a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml index dc6e718290..5c7a4e139f 100644 --- a/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml +++ b/Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml @@ -140,6 +140,7 @@ - type: Lock locked: true - type: ActivatableUIRequiresLock + - type: LockedWiresPanel - type: Flashable - type: Damageable damageContainer: Silicon diff --git a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml index cc9f8035fe..36d77a236d 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/anomaly_equipment.yml @@ -160,6 +160,7 @@ board: APECircuitboard - type: Lock locked: false + - type: LockedWiresPanel - type: AccessReader access: [[ "Research" ]] - type: Emitter @@ -204,6 +205,7 @@ True: { visible: true } False: { visible: false } - type: LockVisuals + - type: LockedAnchorable - type: DeviceNetwork deviceNetId: Wireless receiveFrequencyId: BasicDevice diff --git a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml index 52698f62cc..b999b2bded 100644 --- a/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml +++ b/Resources/Prototypes/Entities/Structures/Power/Generation/Singularity/emitter.yml @@ -84,6 +84,8 @@ - type: Lock locked: false - type: LockVisuals + - type: LockedAnchorable + - type: LockedWiresPanel - type: AccessReader access: [[ "Engineering" ]] - type: Machine