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 <comedian_vs_clown@hotmail.com>
This commit is contained in:
Nemanja
2024-03-31 02:34:17 -04:00
committed by GitHub
parent d512bc141a
commit 1b69762816
12 changed files with 129 additions and 15 deletions

View File

@@ -163,10 +163,13 @@ namespace Content.Client.Popups
PopupEntity(message, uid, type); 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) 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) public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)

View File

@@ -88,7 +88,7 @@ namespace Content.Server.Popups
RaiseNetworkEvent(new PopupEntityEvent(message, type, GetNetEntity(uid)), actor.PlayerSession); 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 // do nothing duh its for client only
} }

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access.Components; using Content.Shared.Access.Components;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Construction.Components;
using Content.Shared.DoAfter; using Content.Shared.DoAfter;
using Content.Shared.Emag.Systems; using Content.Shared.Emag.Systems;
using Content.Shared.Examine; using Content.Shared.Examine;
@@ -9,6 +10,7 @@ using Content.Shared.Interaction;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage.Components; using Content.Shared.Storage.Components;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Wires;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -40,8 +42,11 @@ public sealed class LockSystem : EntitySystem
SubscribeLocalEvent<LockComponent, GotEmaggedEvent>(OnEmagged); SubscribeLocalEvent<LockComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<LockComponent, LockDoAfter>(OnDoAfterLock); SubscribeLocalEvent<LockComponent, LockDoAfter>(OnDoAfterLock);
SubscribeLocalEvent<LockComponent, UnlockDoAfter>(OnDoAfterUnlock); SubscribeLocalEvent<LockComponent, UnlockDoAfter>(OnDoAfterUnlock);
}
SubscribeLocalEvent<LockedWiresPanelComponent, LockToggleAttemptEvent>(OnLockToggleAttempt);
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
}
private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args) private void OnStartup(EntityUid uid, LockComponent lockComp, ComponentStartup args)
{ {
_appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked); _appearanceSystem.SetData(uid, LockVisuals.Locked, lockComp.Locked);
@@ -226,18 +231,18 @@ public sealed class LockSystem : EntitySystem
private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent<AlternativeVerb> args) private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent<AlternativeVerb> args)
{ {
if (!args.CanAccess || !args.CanInteract || !CanToggleLock(uid, args.User)) if (!args.CanAccess || !args.CanInteract)
return; return;
AlternativeVerb verb = new() AlternativeVerb verb = new()
{ {
Act = component.Locked ? Act = component.Locked
() => TryUnlock(uid, args.User, component) : ? () => TryUnlock(uid, args.User, component)
() => TryLock(uid, args.User, component), : () => TryLock(uid, args.User, component),
Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"), Text = Loc.GetString(component.Locked ? "toggle-lock-verb-unlock" : "toggle-lock-verb-lock"),
Icon = component.Locked ? 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"))
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); args.Verbs.Add(verb);
} }
@@ -275,5 +280,53 @@ public sealed class LockSystem : EntitySystem
TryUnlock(uid, args.User, skipDoAfter: true); TryUnlock(uid, args.User, skipDoAfter: true);
} }
private void OnLockToggleAttempt(Entity<LockedWiresPanelComponent> ent, ref LockToggleAttemptEvent args)
{
if (args.Cancelled)
return;
if (!TryComp<WiresPanelComponent>(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<LockedWiresPanelComponent> ent, ref AttemptChangePanelEvent args)
{
if (args.Cancelled)
return;
if (!TryComp<LockComponent>(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<LockedAnchorableComponent> ent, ref UnanchorAttemptEvent args)
{
if (args.Cancelled)
return;
if (!TryComp<LockComponent>(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();
}
} }

View File

@@ -0,0 +1,13 @@
using Content.Shared.Construction.Components;
using Robust.Shared.GameStates;
namespace Content.Shared.Lock;
/// <summary>
/// This is used for a <see cref="AnchorableComponent"/> that cannot be unanchored while locked.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
public sealed partial class LockedAnchorableComponent : Component
{
}

View File

@@ -0,0 +1,13 @@
using Content.Shared.Wires;
using Robust.Shared.GameStates;
namespace Content.Shared.Lock;
/// <summary>
/// This is used for a <see cref="WiresPanelComponent"/> that cannot be opened while locked.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
public sealed partial class LockedWiresPanelComponent : Component
{
}

View File

@@ -86,7 +86,7 @@ namespace Content.Shared.Popups
/// Variant of <see cref="PopupEntity(string, EntityUid, EntityUid, PopupType)"/> that only runs on the client, outside of prediction. /// Variant of <see cref="PopupEntity(string, EntityUid, EntityUid, PopupType)"/> that only runs on the client, outside of prediction.
/// Useful for shared code that is always ran by both sides to avoid duplicate popups. /// Useful for shared code that is always ran by both sides to avoid duplicate popups.
/// </summary> /// </summary>
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);
/// <summary> /// <summary>
/// Variant of <see cref="PopupEntity(string, EntityUid, EntityUid, PopupType)"/> for use with prediction. The local client will show /// Variant of <see cref="PopupEntity(string, EntityUid, EntityUid, PopupType)"/> for use with prediction. The local client will show

View File

@@ -28,15 +28,24 @@ public abstract class SharedWiresSystem : EntitySystem
if (args.Cancelled) if (args.Cancelled)
return; 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")}"); 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; var sound = panel.Open ? panel.ScrewdriverOpenSound : panel.ScrewdriverCloseSound;
Audio.PlayPredicted(sound, uid, args.User); Audio.PlayPredicted(sound, uid, args.User);
args.Handled = true;
} }
private void OnInteractUsing(Entity<WiresPanelComponent> ent, ref InteractUsingEvent args) private void OnInteractUsing(Entity<WiresPanelComponent> ent, ref InteractUsingEvent args)
{ {
if (!Tool.HasQuality(args.Used, ent.Comp.OpeningTool))
return;
if (!CanTogglePanel(ent, args.User))
return;
if (!Tool.UseTool( if (!Tool.UseTool(
args.Used, args.Used,
args.User, args.User,
@@ -89,14 +98,25 @@ public abstract class SharedWiresSystem : EntitySystem
Appearance.SetData(uid, WiresVisuals.MaintenancePanelState, panel.Open && panel.Visible, appearance); 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; component.Open = open;
UpdateAppearance(uid, component); UpdateAppearance(uid, component);
Dirty(uid, component); Dirty(uid, component);
var ev = new PanelChangedEvent(component.Open); var ev = new PanelChangedEvent(component.Open);
RaiseLocalEvent(uid, ref ev); RaiseLocalEvent(uid, ref ev);
return true;
}
public bool CanTogglePanel(Entity<WiresPanelComponent> ent, EntityUid? user)
{
var attempt = new AttemptChangePanelEvent(ent.Comp.Open, user);
RaiseLocalEvent(ent, ref attempt);
return !attempt.Cancelled;
} }
public bool IsPanelOpen(Entity<WiresPanelComponent?> entity) public bool IsPanelOpen(Entity<WiresPanelComponent?> entity)

View File

@@ -57,6 +57,12 @@ public sealed partial class WiresPanelComponent : Component
public LocId? ExamineTextOpen = "wires-panel-component-on-examine-open"; public LocId? ExamineTextOpen = "wires-panel-component-on-examine-open";
} }
/// <summary>
/// Event raised on a <see cref="WiresPanelComponent"/> before its open state is about to be changed.
/// </summary>
[ByRefEvent]
public record struct AttemptChangePanelEvent(bool Open, EntityUid? User, bool Cancelled = false);
/// <summary> /// <summary>
/// Event raised when a panel is opened or closed. /// Event raised when a panel is opened or closed.
/// </summary> /// </summary>

View File

@@ -3,6 +3,7 @@ lock-comp-on-examined-is-unlocked = The {$entityName} seems to be unlocked.
lock-comp-do-lock-success = You lock the {$entityName}. lock-comp-do-lock-success = You lock the {$entityName}.
lock-comp-do-unlock-success = You unlock the {$entityName}. lock-comp-do-unlock-success = You unlock the {$entityName}.
lock-comp-has-user-access-fail = Access denied lock-comp-has-user-access-fail = Access denied
lock-comp-generic-fail = {CAPITALIZE(SUBJECT($target))} {CONJUGATE-BE($target)} locked.
## ToggleLockVerb ## ToggleLockVerb

View File

@@ -140,6 +140,7 @@
- type: Lock - type: Lock
locked: true locked: true
- type: ActivatableUIRequiresLock - type: ActivatableUIRequiresLock
- type: LockedWiresPanel
- type: Flashable - type: Flashable
- type: Damageable - type: Damageable
damageContainer: Silicon damageContainer: Silicon

View File

@@ -160,6 +160,7 @@
board: APECircuitboard board: APECircuitboard
- type: Lock - type: Lock
locked: false locked: false
- type: LockedWiresPanel
- type: AccessReader - type: AccessReader
access: [[ "Research" ]] access: [[ "Research" ]]
- type: Emitter - type: Emitter
@@ -204,6 +205,7 @@
True: { visible: true } True: { visible: true }
False: { visible: false } False: { visible: false }
- type: LockVisuals - type: LockVisuals
- type: LockedAnchorable
- type: DeviceNetwork - type: DeviceNetwork
deviceNetId: Wireless deviceNetId: Wireless
receiveFrequencyId: BasicDevice receiveFrequencyId: BasicDevice

View File

@@ -84,6 +84,8 @@
- type: Lock - type: Lock
locked: false locked: false
- type: LockVisuals - type: LockVisuals
- type: LockedAnchorable
- type: LockedWiresPanel
- type: AccessReader - type: AccessReader
access: [[ "Engineering" ]] access: [[ "Engineering" ]]
- type: Machine - type: Machine