using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Localizations;
+using Content.Shared.Lock;
using Content.Shared.NameIdentifier;
using Content.Shared.PDA;
using Content.Shared.StationRecords;
SubscribeLocalEvent<AccessReaderComponent, GotEmaggedEvent>(OnEmagged);
SubscribeLocalEvent<AccessReaderComponent, LinkAttemptEvent>(OnLinkAttempt);
SubscribeLocalEvent<AccessReaderComponent, AccessReaderConfigurationAttemptEvent>(OnConfigurationAttempt);
+ SubscribeLocalEvent<AccessReaderComponent, FindAvailableLocksEvent>(OnFindAvailableLocks);
+ SubscribeLocalEvent<AccessReaderComponent, CheckUserHasLockAccessEvent>(OnCheckLockAccess);
SubscribeLocalEvent<AccessReaderComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<AccessReaderComponent, ComponentHandleState>(OnHandleState);
ent.Comp.AccessListsOriginal ??= new(ent.Comp.AccessLists);
}
+ private void OnFindAvailableLocks(Entity<AccessReaderComponent> ent, ref FindAvailableLocksEvent args)
+ {
+ args.FoundReaders |= LockTypes.Access;
+ }
+
+ private void OnCheckLockAccess(Entity<AccessReaderComponent> ent, ref CheckUserHasLockAccessEvent args)
+ {
+ // Are we looking for an access lock?
+ if (!args.FoundReaders.HasFlag(LockTypes.Access))
+ return;
+
+ // If the user has access to this lock, we pass it into the event.
+ if (IsAllowed(args.User, ent))
+ args.HasAccess |= LockTypes.Access;
+ }
+
/// <summary>
/// Searches the source for access tags
/// then compares it with the all targets accesses to see if it is allowed.
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Forensics.Components;
using Content.Shared.Inventory;
+using Content.Shared.Lock;
using Content.Shared.Popups;
using JetBrains.Annotations;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<FingerprintReaderComponent, FindAvailableLocksEvent>(OnFindAvailableLocks);
+ SubscribeLocalEvent<FingerprintReaderComponent, CheckUserHasLockAccessEvent>(OnCheckLockAccess);
+ }
+
+ private void OnFindAvailableLocks(Entity<FingerprintReaderComponent> ent, ref FindAvailableLocksEvent args)
+ {
+ args.FoundReaders |= LockTypes.Fingerprint;
+ }
+
+ private void OnCheckLockAccess(Entity<FingerprintReaderComponent> ent, ref CheckUserHasLockAccessEvent args)
+ {
+ // Are we looking for a fingerprint lock?
+ if (!args.FoundReaders.HasFlag(LockTypes.Fingerprint))
+ return;
+
+ // If the user has access to this lock, we pass it into the event.
+ if (IsAllowed(ent.Owner, args.User, out var denyReason))
+ args.HasAccess |= LockTypes.Fingerprint;
+ else
+ args.DenyReason = denyReason;
+ }
+
/// <summary>
/// Checks if the given user has fingerprint access to the target entity.
/// </summary>
/// <param name="target">The target entity.</param>
/// <param name="user">User trying to gain access.</param>
+ /// <param name="showPopup">Whether to display a popup with the reason you are not allowed to access this.</param>
+ /// <param name="denyReason">The reason why access was denied.</param>
/// <returns>True if access was granted, otherwise false.</returns>
+ // TODO: Remove showPopup, just keeping it here for backwards compatibility while I refactor mail
[PublicAPI]
- public bool IsAllowed(Entity<FingerprintReaderComponent?> target, EntityUid user, bool showPopup = true)
+ public bool IsAllowed(Entity<FingerprintReaderComponent?> target, EntityUid user, [NotNullWhen(false)] out string? denyReason, bool showPopup = true)
{
+ denyReason = null;
if (!Resolve(target, ref target.Comp, false))
return true;
// Check for gloves first
if (!target.Comp.IgnoreGloves && TryGetBlockingGloves(user, out var gloves))
{
- if (target.Comp.FailGlovesPopup != null && showPopup)
- _popup.PopupClient(Loc.GetString(target.Comp.FailGlovesPopup, ("blocker", gloves)), target, user);
+ denyReason = Loc.GetString("fingerprint-reader-fail-gloves", ("blocker", gloves));
+
+ if (showPopup)
+ _popup.PopupClient(denyReason, target, user);
+
return false;
}
if (!TryComp<FingerprintComponent>(user, out var fingerprint) || fingerprint.Fingerprint == null ||
!target.Comp.AllowedFingerprints.Contains(fingerprint.Fingerprint))
{
- if (target.Comp.FailPopup != null && showPopup)
- _popup.PopupClient(Loc.GetString(target.Comp.FailPopup), target, user);
+ denyReason = Loc.GetString("fingerprint-reader-fail");
+
+ if (showPopup)
+ _popup.PopupClient(denyReason, target, user);
return false;
}
-using Content.Shared.Access.Components;
using Content.Shared.DoAfter;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
public bool UnlockOnClick = true;
/// <summary>
- /// Whether the lock requires access validation through <see cref="AccessReaderComponent"/>
+ /// Whether or not the lock is locked when used it hand.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool LockInHand;
+
+ /// <summary>
+ /// Whether or not the lock is unlocked when used in hand.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool UnlockInHand;
+
+ /// <summary>
+ /// Whether access requirements should be checked for this lock.
/// </summary>
[DataField, AutoNetworkedField]
public bool UseAccess = true;
+ /// <summary>
+ /// What readers should be checked to determine if an entity has access.
+ /// If null, all possible readers are checked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LockTypes? CheckedLocks;
+
+ /// <summary>
+ /// Whether any reader needs to be accessed to operate this lock.
+ /// By default, all readers need to be able to be accessed.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CheckForAnyReaders;
+
/// <summary>
/// The sound played when unlocked.
/// </summary>
[DataField]
[AutoNetworkedField]
public TimeSpan UnlockTime;
+
+ /// <summary>
+ /// Whether this lock can be locked again after being unlocked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool AllowRepeatedLocking = true;
}
/// <summary>
-using Content.Shared.Access.Components;
-using Content.Shared.Access.Systems;
using Content.Shared.ActionBlocker;
using Content.Shared.Construction.Components;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Content.Shared.Storage;
using Content.Shared.Storage.Components;
using Content.Shared.Item.ItemToggle.Components;
using JetBrains.Annotations;
using Robust.Shared.Audio.Systems;
+using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Lock;
[UsedImplicitly]
public sealed class LockSystem : EntitySystem
{
- [Dependency] private readonly AccessReaderSystem _accessReader = default!;
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly EmagSystem _emag = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+ private readonly LocId _defaultDenyReason = "lock-comp-has-user-access-fail";
+
/// <inheritdoc />
public override void Initialize()
{
SubscribeLocalEvent<LockComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<LockComponent, ActivateInWorldEvent>(OnActivated, before: [typeof(ActivatableUISystem)]);
+ SubscribeLocalEvent<LockComponent, UseInHandEvent>(OnUseInHand, before: [typeof(ActivatableUISystem)]);
SubscribeLocalEvent<LockComponent, StorageOpenAttemptEvent>(OnStorageOpenAttempt);
SubscribeLocalEvent<LockComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<LockComponent, GetVerbsEvent<AlternativeVerb>>(AddToggleLockVerb);
}
}
+ private void OnUseInHand(EntityUid uid, LockComponent lockComp, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (lockComp.Locked && lockComp.UnlockInHand)
+ {
+ args.Handled = true;
+ TryUnlock(uid, args.User, lockComp);
+ }
+ else if (!lockComp.Locked && lockComp.LockInHand)
+ {
+ args.Handled = true;
+ TryLock(uid, args.User, lockComp);
+ }
+ }
+
private void OnStorageOpenAttempt(EntityUid uid, LockComponent component, ref StorageOpenAttemptEvent args)
{
if (!component.Locked)
if (!CanToggleLock(uid, user, quiet: false))
return false;
- if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
+ if (lockComp.UseAccess && !HasUserAccess(uid, user, false))
return false;
if (!skipDoAfter && lockComp.LockTime != TimeSpan.Zero)
if (!CanToggleLock(uid, user, quiet: false))
return false;
- if (lockComp.UseAccess && !HasUserAccess(uid, user, quiet: false))
+ if (lockComp.UseAccess && !HasUserAccess(uid, user, false))
return false;
if (!skipDoAfter && lockComp.UnlockTime != TimeSpan.Zero)
/// Raises an event for other components to check whether or not
/// the entity can be locked in its current state.
/// </summary>
- public bool CanToggleLock(EntityUid uid, EntityUid user, bool quiet = true)
+ public bool CanToggleLock(Entity<LockComponent?> ent, EntityUid user, bool quiet = true)
{
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
if (!_actionBlocker.CanComplexInteract(user))
return false;
+ if (!ent.Comp.Locked && !ent.Comp.AllowRepeatedLocking)
+ return false;
+
var ev = new LockToggleAttemptEvent(user, quiet);
- RaiseLocalEvent(uid, ref ev, true);
+ RaiseLocalEvent(ent, ref ev, true);
if (ev.Cancelled)
return false;
- var userEv = new UserLockToggleAttemptEvent(uid, quiet);
+ var userEv = new UserLockToggleAttemptEvent(ent, quiet);
RaiseLocalEvent(user, ref userEv, true);
return !userEv.Cancelled;
}
- // TODO: this should be a helper on AccessReaderSystem since so many systems copy paste it
- private bool HasUserAccess(EntityUid uid, EntityUid user, AccessReaderComponent? reader = null, bool quiet = true)
+ /// <summary>
+ /// Checks whether the user has access to locks on an entity.
+ /// </summary>
+ /// <param name="ent">The entity we check for locks.</param>
+ /// <param name="user">The user we check for access.</param>
+ /// <param name="quiet">Whether to display a popup if user has no access.</param>
+ /// <returns>True if the user has access, otherwise False.</returns>
+ [PublicAPI]
+ public bool HasUserAccess(Entity<LockComponent?> ent, EntityUid user, bool quiet = true)
{
- // Not having an AccessComponent means you get free access. woo!
- if (!Resolve(uid, ref reader, false))
+ // Entity literally has no lock. Congratulations.
+ if (!Resolve(ent, ref ent.Comp, false))
return true;
- if (_accessReader.IsAllowed(user, uid, reader))
+ var checkedReaders = LockTypes.None;
+ if (ent.Comp.CheckedLocks is null)
+ {
+ var lockEv = new FindAvailableLocksEvent(user);
+ RaiseLocalEvent(ent, ref lockEv);
+ checkedReaders = lockEv.FoundReaders;
+ }
+
+ // If no locks are found, you have access. Woo!
+ if (checkedReaders == LockTypes.None)
+ return true;
+
+ var accessEv = new CheckUserHasLockAccessEvent(user, checkedReaders);
+ RaiseLocalEvent(ent, ref accessEv);
+
+ // If we check for any, as long as user has access to any of the locks we grant access.
+ if (accessEv.HasAccess != LockTypes.None && ent.Comp.CheckForAnyReaders)
+ return true;
+
+ if (accessEv.HasAccess == checkedReaders)
return true;
if (!quiet)
- _sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-has-user-access-fail"), uid, user);
+ {
+ var denyReason = accessEv.DenyReason ?? _defaultDenyReason;
+ _sharedPopupSystem.PopupClient(denyReason, ent, user);
+ }
+
return false;
}
}
}
}
+
+/// <summary>
+/// Raised on an entity to check whether it has any readers that can prevent it from being opened.
+/// </summary>
+/// <param name="User">The person attempting to open the entity.</param>
+/// <param name="FoundReaders">What readers were found. This should not be set when raising the event.</param>
+[ByRefEvent]
+public record struct FindAvailableLocksEvent(EntityUid User, LockTypes FoundReaders = LockTypes.None);
+
+/// <summary>
+/// Raised on an entity to check if the user has access (ID, Fingerprint, etc) to said entity.
+/// </summary>
+/// <param name="User">The user we are checking.</param>
+/// <param name="FoundReaders">What readers we are attempting to verify access for.</param>
+/// <param name="HasAccess">Which readers the user has access to. This should not be set when raising the event.</param>
+[ByRefEvent]
+public record struct CheckUserHasLockAccessEvent(EntityUid User, LockTypes FoundReaders = LockTypes.None, LockTypes HasAccess = LockTypes.None, string? DenyReason = null);
+
+/// <summary>
+/// Enum of all readers a lock can be "locked" by.
+/// Used to determine what you need in order to access the lock.
+/// For example, an entity with <see cref="AccessReaderComponent"/> will have the Access type, which is gathered by an event and handled by the respective system.
+/// </summary>
+[Flags]
+[Serializable, NetSerializable]
+public enum LockTypes : byte
+{
+ None, // Default state, means the lock is not restricted.
+ Access, // Means there is an AccessReader currently present.
+ Fingerprint, // Means there is a FingerprintReader currently present.
+ All = Access | Fingerprint,
+}