From db4d419f7bcef1dfe8809f4f7ae53bfae5faafea Mon Sep 17 00:00:00 2001 From: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:28:55 -0500 Subject: [PATCH] Access Reader Refactor (#37772) * Initial commit * Integration test fix * Removed redundant dirtying of accessreader --- .../Tests/Access/AccessReaderTest.cs | 43 ++- Content.Server/Access/AccessWireAction.cs | 14 +- Content.Server/Access/AddAccessLogCommand.cs | 4 +- Content.Server/Access/LogWireAction.cs | 10 +- .../Access/Systems/AccessOverriderSystem.cs | 19 +- .../Systems/DoorElectronicsSystem.cs | 2 +- .../Components/AccessReaderComponent.cs | 27 +- .../Access/Systems/AccessReaderSystem.cs | 352 ++++++++++++++++-- 8 files changed, 369 insertions(+), 102 deletions(-) diff --git a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs index 3f703ce774..b98f030b06 100644 --- a/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs +++ b/Content.IntegrationTests/Tests/Access/AccessReaderTest.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -using System.Linq; using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Robust.Shared.GameObjects; +using Robust.Shared.Map; using Robust.Shared.Prototypes; namespace Content.IntegrationTests.Tests.Access @@ -12,6 +12,15 @@ namespace Content.IntegrationTests.Tests.Access [TestOf(typeof(AccessReaderComponent))] public sealed class AccessReaderTest { + [TestPrototypes] + private const string Prototypes = @" +- type: entity + id: TestAccessReader + name: access reader + components: + - type: AccessReader +"; + [Test] public async Task TestTags() { @@ -19,13 +28,13 @@ namespace Content.IntegrationTests.Tests.Access var server = pair.Server; var entityManager = server.ResolveDependency(); - await server.WaitAssertion(() => { var system = entityManager.System(); + var ent = entityManager.SpawnEntity("TestAccessReader", MapCoordinates.Nullspace); + var reader = new Entity(ent, entityManager.GetComponent(ent)); // test empty - var reader = new AccessReaderComponent(); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); @@ -34,8 +43,7 @@ namespace Content.IntegrationTests.Tests.Access }); // test deny - reader = new AccessReaderComponent(); - reader.DenyTags.Add("A"); + system.AddDenyTag(reader, "A"); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "Foo" }, reader), Is.True); @@ -43,10 +51,10 @@ namespace Content.IntegrationTests.Tests.Access Assert.That(system.AreAccessTagsAllowed(new List> { "A", "Foo" }, reader), Is.False); Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.True); }); + system.ClearDenyTags(reader); // test one list - reader = new AccessReaderComponent(); - reader.AccessLists.Add(new HashSet> { "A" }); + system.AddAccess(reader, "A"); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); @@ -54,10 +62,10 @@ namespace Content.IntegrationTests.Tests.Access Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); }); + system.ClearAccesses(reader); // test one list - two items - reader = new AccessReaderComponent(); - reader.AccessLists.Add(new HashSet> { "A", "B" }); + system.AddAccess(reader, new HashSet> { "A", "B" }); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.False); @@ -65,11 +73,14 @@ namespace Content.IntegrationTests.Tests.Access Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.True); Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); }); + system.ClearAccesses(reader); // test two list - reader = new AccessReaderComponent(); - reader.AccessLists.Add(new HashSet> { "A" }); - reader.AccessLists.Add(new HashSet> { "B", "C" }); + var accesses = new List>>() { + new HashSet> () { "A" }, + new HashSet> () { "B", "C" } + }; + system.AddAccesses(reader, accesses); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); @@ -79,11 +90,11 @@ namespace Content.IntegrationTests.Tests.Access Assert.That(system.AreAccessTagsAllowed(new List> { "C", "B", "A" }, reader), Is.True); Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); }); + system.ClearAccesses(reader); // test deny list - reader = new AccessReaderComponent(); - reader.AccessLists.Add(new HashSet> { "A" }); - reader.DenyTags.Add("B"); + system.AddAccess(reader, new HashSet> { "A" }); + system.AddDenyTag(reader, "B"); Assert.Multiple(() => { Assert.That(system.AreAccessTagsAllowed(new List> { "A" }, reader), Is.True); @@ -91,6 +102,8 @@ namespace Content.IntegrationTests.Tests.Access Assert.That(system.AreAccessTagsAllowed(new List> { "A", "B" }, reader), Is.False); Assert.That(system.AreAccessTagsAllowed(Array.Empty>(), reader), Is.False); }); + system.ClearAccesses(reader); + system.ClearDenyTags(reader); }); await pair.CleanReturnAsync(); } diff --git a/Content.Server/Access/AccessWireAction.cs b/Content.Server/Access/AccessWireAction.cs index b3beb3967b..2682fff286 100644 --- a/Content.Server/Access/AccessWireAction.cs +++ b/Content.Server/Access/AccessWireAction.cs @@ -1,6 +1,7 @@ using Content.Server.Wires; using Content.Shared.Access; using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; using Content.Shared.Wires; namespace Content.Server.Access; @@ -23,23 +24,21 @@ public sealed partial class AccessWireAction : ComponentWireAction().SetActive((wire.Owner, comp), false); + return true; } public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp) { - comp.Enabled = true; - EntityManager.Dirty(wire.Owner, comp); + EntityManager.System().SetActive((wire.Owner, comp), true); return true; } public override void Pulse(EntityUid user, Wire wire, AccessReaderComponent comp) { - comp.Enabled = false; - EntityManager.Dirty(wire.Owner, comp); + EntityManager.System().SetActive((wire.Owner, comp), false); WiresSystem.StartWireAction(wire.Owner, _pulseTimeout, PulseTimeoutKey.Key, new TimedWireEvent(AwaitPulseCancel, wire)); } @@ -57,8 +56,7 @@ public sealed partial class AccessWireAction : ComponentWireAction(wire.Owner, out var access)) { - access.Enabled = true; - EntityManager.Dirty(wire.Owner, access); + EntityManager.System().SetActive((wire.Owner, access), true); } } } diff --git a/Content.Server/Access/AddAccessLogCommand.cs b/Content.Server/Access/AddAccessLogCommand.cs index f55a9b8f1e..e68a58d165 100644 --- a/Content.Server/Access/AddAccessLogCommand.cs +++ b/Content.Server/Access/AddAccessLogCommand.cs @@ -1,8 +1,8 @@ using Content.Server.Administration; using Content.Shared.Access.Components; +using Content.Shared.Access.Systems; using Content.Shared.Administration; using Robust.Shared.Toolshed; -using Robust.Shared.Toolshed.Syntax; namespace Content.Server.Access; @@ -19,7 +19,7 @@ public sealed class AddAccessLogCommand : ToolshedCommand ctx.WriteLine($"WARNING: Surpassing the limit of the log by {accessLogCount - accessReader.AccessLogLimit+1} entries!"); var accessTime = TimeSpan.FromSeconds(seconds); - accessReader.AccessLog.Enqueue(new AccessRecord(accessTime, accessor)); + EntityManager.System().LogAccess((input, accessReader), accessor, accessTime, true); ctx.WriteLine($"Successfully added access log to {input} with this information inside:\n " + $"Time of access: {accessTime}\n " + $"Accessed by: {accessor}"); diff --git a/Content.Server/Access/LogWireAction.cs b/Content.Server/Access/LogWireAction.cs index 837cf420d5..d6ba3dbfcd 100644 --- a/Content.Server/Access/LogWireAction.cs +++ b/Content.Server/Access/LogWireAction.cs @@ -37,21 +37,21 @@ public sealed partial class LogWireAction : ComponentWireAction().SetLoggingActive((wire.Owner, comp), false); + return true; } public override bool Mend(EntityUid user, Wire wire, AccessReaderComponent comp) { - comp.LoggingDisabled = false; + EntityManager.System().SetLoggingActive((wire.Owner, comp), true); return true; } public override void Pulse(EntityUid user, Wire wire, AccessReaderComponent comp) { _access.LogAccess((wire.Owner, comp), Loc.GetString(PulseLog)); - comp.LoggingDisabled = true; + EntityManager.System().SetLoggingActive((wire.Owner, comp), false); WiresSystem.StartWireAction(wire.Owner, PulseTimeout, PulseTimeoutKey.Key, new TimedWireEvent(AwaitPulseCancel, wire)); } @@ -64,7 +64,7 @@ public sealed partial class LogWireAction : ComponentWireAction(wire.Owner, out var comp)) - comp.LoggingDisabled = false; + EntityManager.System().SetLoggingActive((wire.Owner, comp), true); } private enum PulseTimeoutKey : byte diff --git a/Content.Server/Access/Systems/AccessOverriderSystem.cs b/Content.Server/Access/Systems/AccessOverriderSystem.cs index 4062909d75..51d35c50a4 100644 --- a/Content.Server/Access/Systems/AccessOverriderSystem.cs +++ b/Content.Server/Access/Systems/AccessOverriderSystem.cs @@ -168,21 +168,6 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem return accessList; } - private List>> ConvertAccessListToHashSet(List> accessList) - { - List>> accessHashsets = new List>>(); - - if (accessList != null && accessList.Any()) - { - foreach (ProtoId access in accessList) - { - accessHashsets.Add(new HashSet>() { access }); - } - } - - return accessHashsets; - } - /// /// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader. /// @@ -244,12 +229,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem _adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(player):player} has modified {ToPrettyString(accessReaderEnt.Value):entity} with the following allowed access level holders: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]"); - accessReaderEnt.Value.Comp.AccessLists = ConvertAccessListToHashSet(newAccessList); + _accessReader.SetAccesses(accessReaderEnt.Value, newAccessList); var ev = new OnAccessOverriderAccessUpdatedEvent(player); RaiseLocalEvent(component.TargetAccessReaderId, ref ev); - - Dirty(accessReaderEnt.Value); } /// diff --git a/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs b/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs index af9ccadd91..af2738d105 100644 --- a/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs +++ b/Content.Server/Doors/Electronics/Systems/DoorElectronicsSystem.cs @@ -48,7 +48,7 @@ public sealed class DoorElectronicsSystem : EntitySystem DoorElectronicsUpdateConfigurationMessage args) { var accessReader = EnsureComp(uid); - _accessReader.SetAccesses(uid, accessReader, args.AccessList); + _accessReader.SetAccesses((uid, accessReader), args.AccessList); } private void OnAccessReaderChanged( diff --git a/Content.Shared/Access/Components/AccessReaderComponent.cs b/Content.Shared/Access/Components/AccessReaderComponent.cs index 0219fd2b1a..6c2416fdf4 100644 --- a/Content.Shared/Access/Components/AccessReaderComponent.cs +++ b/Content.Shared/Access/Components/AccessReaderComponent.cs @@ -1,8 +1,8 @@ +using Content.Shared.Access.Systems; using Content.Shared.StationRecords; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; namespace Content.Shared.Access.Components; @@ -11,10 +11,11 @@ namespace Content.Shared.Access.Components; /// and allows checking if something or somebody is authorized with these access levels. /// [RegisterComponent, NetworkedComponent] +[Access(typeof(AccessReaderSystem))] public sealed partial class AccessReaderComponent : Component { /// - /// Whether or not the accessreader is enabled. + /// Whether or not the access reader is enabled. /// If not, it will always let people through. /// [DataField] @@ -23,7 +24,6 @@ public sealed partial class AccessReaderComponent : Component /// /// The set of tags that will automatically deny an allowed check, if any of them are present. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField] public HashSet> DenyTags = new(); @@ -31,12 +31,11 @@ public sealed partial class AccessReaderComponent : Component /// List of access groups that grant access to this reader. Only a single matching group is required to gain access. /// A group matches if it is a subset of the set being checked against. /// - [DataField("access")] [ViewVariables(VVAccess.ReadWrite)] + [DataField("access")] public List>> AccessLists = new(); /// - /// A list of s that grant access. Only a single matching key is required to gain - /// access. + /// A list of s that grant access. Only a single matching key is required to gain access. /// [DataField] public HashSet AccessKeys = new(); @@ -54,7 +53,7 @@ public sealed partial class AccessReaderComponent : Component public string? ContainerAccessProvider; /// - /// A list of past authentications + /// A list of past authentications. /// [DataField] public Queue AccessLog = new(); @@ -62,7 +61,7 @@ public sealed partial class AccessReaderComponent : Component /// /// A limit on the max size of /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public int AccessLogLimit = 20; /// @@ -95,15 +94,10 @@ public readonly partial record struct AccessRecord( public sealed class AccessReaderComponentState : ComponentState { public bool Enabled; - public HashSet> DenyTags; - public List>> AccessLists; - public List<(NetEntity, uint)> AccessKeys; - public Queue AccessLog; - public int AccessLogLimit; public AccessReaderComponentState(bool enabled, HashSet> denyTags, List>> accessLists, List<(NetEntity, uint)> accessKeys, Queue accessLog, int accessLogLimit) @@ -117,9 +111,4 @@ public sealed class AccessReaderComponentState : ComponentState } } -public sealed class AccessReaderConfigurationChangedEvent : EntityEventArgs -{ - public AccessReaderConfigurationChangedEvent() - { - } -} +public sealed class AccessReaderConfigurationChangedEvent : EntityEventArgs; diff --git a/Content.Shared/Access/Systems/AccessReaderSystem.cs b/Content.Shared/Access/Systems/AccessReaderSystem.cs index 74cf74274d..186aef5305 100644 --- a/Content.Shared/Access/Systems/AccessReaderSystem.cs +++ b/Content.Shared/Access/Systems/AccessReaderSystem.cs @@ -3,17 +3,17 @@ using System.Linq; using Content.Shared.Access.Components; using Content.Shared.DeviceLinking.Events; using Content.Shared.Emag.Systems; +using Content.Shared.GameTicking; using Content.Shared.Hands.EntitySystems; +using Content.Shared.IdentityManagement; using Content.Shared.Inventory; using Content.Shared.NameIdentifier; using Content.Shared.PDA; using Content.Shared.StationRecords; -using Robust.Shared.Containers; -using Robust.Shared.GameStates; -using Content.Shared.GameTicking; -using Content.Shared.IdentityManagement; using Content.Shared.Tag; +using Robust.Shared.Containers; using Robust.Shared.Collections; +using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -128,6 +128,11 @@ public sealed class AccessReaderSystem : EntitySystem return true; } + /// + /// Searches an entity for an access reader. This is either the entity itself or an entity in its . + /// + /// The entity being searched for an access reader. + /// The returned access reader entity. public bool GetMainAccessReader(EntityUid uid, [NotNullWhen(true)] out Entity? ent) { ent = null; @@ -157,6 +162,10 @@ public sealed class AccessReaderSystem : EntitySystem /// /// Check whether the given access permissions satisfy an access reader's requirements. /// + /// A collection of access permissions being used on the access reader. + /// A collection of station record keys being used on the access reader. + /// The entity being checked. + /// The access reader being checked. public bool IsAllowed( ICollection> access, ICollection stationKeys, @@ -199,8 +208,8 @@ public sealed class AccessReaderSystem : EntitySystem /// /// Compares the given tags with the readers access list to see if it is allowed. /// - /// A list of access tags - /// An access reader to check against + /// A list of access tags. + /// The access reader to check against. public bool AreAccessTagsAllowed(ICollection> accessTags, AccessReaderComponent reader) { if (reader.DenyTags.Overlaps(accessTags)) @@ -228,6 +237,8 @@ public sealed class AccessReaderSystem : EntitySystem /// /// Compares the given stationrecordkeys with the accessreader to see if it is allowed. /// + /// The collection of station record keys being used against the access reader. + /// The access reader that is being checked. public bool AreStationRecordKeysAllowed(ICollection keys, AccessReaderComponent reader) { foreach (var key in reader.AccessKeys) @@ -240,8 +251,9 @@ public sealed class AccessReaderSystem : EntitySystem } /// - /// Finds all the items that could potentially give access to a given entity + /// Finds all the items that could potentially give access to an entity. /// + /// The entity that is being searched. public HashSet FindPotentialAccessItems(EntityUid uid) { FindAccessItemsInventory(uid, out var items); @@ -261,7 +273,7 @@ public sealed class AccessReaderSystem : EntitySystem } /// - /// Finds the access tags on the given entity + /// Finds the access tags on an entity. /// /// The entity that is being searched. /// All of the items to search for access. If none are passed in, will be used. @@ -277,14 +289,14 @@ public sealed class AccessReaderSystem : EntitySystem FindAccessTagsItem(ent, ref tags, ref owned); } - return (ICollection>?) tags ?? Array.Empty>(); + return (ICollection>?)tags ?? Array.Empty>(); } /// - /// Finds the access tags on the given entity + /// Finds any station record keys on an entity. /// /// The entity that is being searched. - /// + /// A collection of the station record keys that were found. /// All of the items to search for access. If none are passed in, will be used. public bool FindStationRecordKeys(EntityUid uid, out ICollection recordKeys, HashSet? items = null) { @@ -302,11 +314,12 @@ public sealed class AccessReaderSystem : EntitySystem } /// - /// Try to find on this item - /// or inside this item (if it's pda) - /// This version merges into a set or replaces the set. - /// If owned is false, the existing tag-set "isn't ours" and can't be merged with (is read-only). + /// Try to find on this item or inside this item (if it's a PDA). + /// This version merges into a set or replaces the set. /// + /// The entity that is being searched. + /// The access tags being merged or replaced. + /// If true, the tags will be merged. Otherwise they are replaced. private void FindAccessTagsItem(EntityUid uid, ref HashSet>? tags, ref bool owned) { if (!FindAccessTagsItem(uid, out var targetTags)) @@ -333,26 +346,288 @@ public sealed class AccessReaderSystem : EntitySystem } } - public void SetAccesses(EntityUid uid, AccessReaderComponent component, List> accesses) + #region: AccessLists API + + /// + /// Clears the entity's . + /// + /// The access reader entity which is having its access permissions cleared. + public void ClearAccesses(Entity ent) + { + ent.Comp.AccessLists.Clear(); + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + /// Replaces the access permissions in an entity's with a supplied list. + /// + /// The access reader entity which is having its list of access permissions replaced. + /// The list of access permissions replacing the original one. + public void SetAccesses(Entity ent, List>> accesses) + { + ent.Comp.AccessLists.Clear(); + + AddAccesses(ent, accesses); + } + + /// + public void SetAccesses(Entity ent, List> accesses) + { + ent.Comp.AccessLists.Clear(); + + AddAccesses(ent, accesses); + } + + /// + /// Adds a collection of access permissions to an access reader entity's + /// + /// The access reader entity to which the new access permissions are being added. + /// The list of access permissions being added. + public void AddAccesses(Entity ent, List>> accesses) { - component.AccessLists.Clear(); foreach (var access in accesses) { - component.AccessLists.Add(new HashSet>(){access}); + AddAccess(ent, access, false); } - Dirty(uid, component); - RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent()); + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); } - public bool FindAccessItemsInventory(EntityUid uid, out HashSet items) + /// + public void AddAccesses(Entity ent, List> accesses) + { + foreach (var access in accesses) + { + AddAccess(ent, access, false); + } + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + /// Adds an access permission to an access reader entity's + /// + /// The access reader entity to which the access permission is being added. + /// The access permission being added. + /// If true, the component will be marked as changed afterward. + public void AddAccess(Entity ent, HashSet> access, bool dirty = true) + { + ent.Comp.AccessLists.Add(access); + + if (!dirty) + return; + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + public void AddAccess(Entity ent, ProtoId access, bool dirty = true) + { + AddAccess(ent, new HashSet>() { access }, dirty); + } + + /// + /// Removes a collection of access permissions from an access reader entity's + /// + /// The access reader entity from which the access permissions are being removed. + /// The list of access permissions being removed. + public void RemoveAccesses(Entity ent, List>> accesses) + { + foreach (var access in accesses) + { + RemoveAccess(ent, access, false); + } + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + public void RemoveAccesses(Entity ent, List> accesses) + { + foreach (var access in accesses) + { + RemoveAccess(ent, access, false); + } + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + /// Removes an access permission from an access reader entity's + /// + /// The access reader entity from which the access permission is being removed. + /// The access permission being removed. + /// If true, the component will be marked as changed afterward. + public void RemoveAccess(Entity ent, HashSet> access, bool dirty = true) + { + for (int i = ent.Comp.AccessLists.Count - 1; i >= 0; i--) + { + if (ent.Comp.AccessLists[i].SetEquals(access)) + { + ent.Comp.AccessLists.RemoveAt(i); + } + } + + if (!dirty) + return; + + Dirty(ent); + RaiseLocalEvent(ent, new AccessReaderConfigurationChangedEvent()); + } + + /// + public void RemoveAccess(Entity ent, ProtoId access, bool dirty = true) { - items = new(); + RemoveAccess(ent, new HashSet>() { access }, dirty); + } - foreach (var item in _handsSystem.EnumerateHeld(uid)) + #endregion + + #region: AccessKeys API + + /// + /// Clears all access keys from an access reader. + /// + /// The access reader entity. + public void ClearAccessKeys(Entity ent) + { + ent.Comp.AccessKeys.Clear(); + Dirty(ent); + } + + /// + /// Replaces all access keys on an access reader with those from a supplied list. + /// + /// The access reader entity. + /// The new access keys that are replacing the old ones. + public void SetAccessKeys(Entity ent, HashSet keys) + { + ent.Comp.AccessKeys.Clear(); + + foreach (var key in keys) { - items.Add(item); + ent.Comp.AccessKeys.Add(key); } + Dirty(ent); + } + + /// + /// Adds an access key to an access reader. + /// + /// The access reader entity. + /// The access key being added. + public void AddAccessKey(Entity ent, StationRecordKey key) + { + ent.Comp.AccessKeys.Add(key); + Dirty(ent); + } + + /// + /// Removes an access key from an access reader. + /// + /// The access reader entity. + /// The access key being removed. + public void RemoveAccessKey(Entity ent, StationRecordKey key) + { + ent.Comp.AccessKeys.Remove(key); + Dirty(ent); + } + + #endregion + + #region: DenyTags API + + /// + /// Clears all deny tags from an access reader. + /// + /// The access reader entity. + public void ClearDenyTags(Entity ent) + { + ent.Comp.DenyTags.Clear(); + Dirty(ent); + } + + /// + /// Replaces all deny tags on an access reader with those from a supplied list. + /// + /// The access reader entity. + /// The new tags that are replacing the old. + public void SetDenyTags(Entity ent, HashSet> tags) + { + ent.Comp.DenyTags.Clear(); + + foreach (var tag in tags) + { + ent.Comp.DenyTags.Add(tag); + } + + Dirty(ent); + } + + /// + /// Adds a tag to an access reader that will be used to deny access. + /// + /// The access reader entity. + /// The tag being added. + public void AddDenyTag(Entity ent, ProtoId tag) + { + ent.Comp.DenyTags.Add(tag); + Dirty(ent); + } + + /// + /// Removes a tag from an access reader that denied a user access. + /// + /// The access reader entity. + /// The tag being removed. + public void RemoveDenyTag(Entity ent, ProtoId tag) + { + ent.Comp.DenyTags.Remove(tag); + Dirty(ent); + } + + #endregion + + /// + /// Enables/disables the access reader on an entity. + /// + /// The access reader entity. + /// Enable/disable the access reader. + public void SetActive(Entity ent, bool enabled) + { + ent.Comp.Enabled = enabled; + Dirty(ent); + } + + /// + /// Enables/disables the logging of access attempts on an access reader entity. + /// + /// The access reader entity. + /// Enable/disable logging. + public void SetLoggingActive(Entity ent, bool enabled) + { + ent.Comp.LoggingDisabled = !enabled; + Dirty(ent); + } + + /// + /// Searches an entity's hand and ID slot for any contained items. + /// + /// The entity being searched. + /// The collection of found items. + /// True if one or more items were found. + public bool FindAccessItemsInventory(EntityUid uid, out HashSet items) + { + items = new(_handsSystem.EnumerateHeld(uid)); + // maybe its inside an inventory slot? if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid)) { @@ -363,9 +638,11 @@ public sealed class AccessReaderSystem : EntitySystem } /// - /// Try to find on this item - /// or inside this item (if it's pda) + /// Try to find on this entity or inside it (if it's a PDA). /// + /// The entity being searched. + /// The access tags that were found. + /// True if one or more access tags were found. private bool FindAccessTagsItem(EntityUid uid, out HashSet> tags) { tags = new(); @@ -376,9 +653,11 @@ public sealed class AccessReaderSystem : EntitySystem } /// - /// Try to find on this item - /// or inside this item (if it's pda) + /// Try to find on this entity or inside it (if it's a PDA). /// + /// The entity being searched. + /// The station record key that was found. + /// True if a station record key was found. private bool FindStationRecordKeyItem(EntityUid uid, [NotNullWhen(true)] out StationRecordKey? key) { if (TryComp(uid, out StationRecordKeyStorageComponent? storage) && storage.Key != null) @@ -432,15 +711,20 @@ public sealed class AccessReaderSystem : EntitySystem /// /// The reader to log the access on /// The name to log as - public void LogAccess(Entity ent, string name) + public void LogAccess(Entity ent, string name, TimeSpan? accessTime = null, bool force = false) { - if (IsPaused(ent) || ent.Comp.LoggingDisabled) - return; + if (!force) + { + if (IsPaused(ent) || ent.Comp.LoggingDisabled) + return; - if (ent.Comp.AccessLog.Count >= ent.Comp.AccessLogLimit) - ent.Comp.AccessLog.Dequeue(); + if (ent.Comp.AccessLog.Count >= ent.Comp.AccessLogLimit) + ent.Comp.AccessLog.Dequeue(); + } - var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); + var stationTime = accessTime ?? _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); ent.Comp.AccessLog.Enqueue(new AccessRecord(stationTime, name)); + + Dirty(ent); } } -- 2.51.2