From 0f43e5e6ad1895f2f163d2bff39a366c56cf9c21 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Wed, 3 Jun 2020 11:46:59 +0200 Subject: [PATCH] Access refactor Access is now done through a list of access lists, instead of the necessary/sufficient system that was extremely confusing. Added a "deny" list so you can screw over sec. Cleaned the API up so it all uses sets and such. PDA now relays access read-only to fix edge cases. --- .../Components/Access/AccessComponent.cs | 31 ++- .../Access/AccessReaderComponent.cs | 89 ++++++--- .../Access/IdCardConsoleComponent.cs | 3 +- .../Components/PDA/PDAComponent.cs | 178 +++++++++++++++--- Content.Server/GameTicking/GameTicker.cs | 4 +- Content.Server/Interfaces/IAccess.cs | 27 ++- .../Access/SharedIdCardConsoleComponent.cs | 4 +- .../Components/Access/AccessReaderTest.cs | 85 +++++++++ Resources/Maps/saltern.yml | 98 +++++----- .../Buildings/Doors/airlock_access.yml | 54 +++--- .../Entities/Buildings/computers.yml | 2 +- 11 files changed, 424 insertions(+), 151 deletions(-) create mode 100644 Content.Tests/Server/GameObjects/Components/Access/AccessReaderTest.cs diff --git a/Content.Server/GameObjects/Components/Access/AccessComponent.cs b/Content.Server/GameObjects/Components/Access/AccessComponent.cs index 829f47ed0e..060f418634 100644 --- a/Content.Server/GameObjects/Components/Access/AccessComponent.cs +++ b/Content.Server/GameObjects/Components/Access/AccessComponent.cs @@ -4,29 +4,42 @@ using Robust.Shared.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +#nullable enable + namespace Content.Server.GameObjects.Components.Access { + /// + /// Simple mutable access provider found on ID cards and such. + /// [RegisterComponent] [ComponentReference(typeof(IAccess))] public class AccessComponent : Component, IAccess { public override string Name => "Access"; + [ViewVariables] - private List _tags; + private readonly HashSet _tags = new HashSet(); + + public ISet Tags => _tags; + public bool IsReadOnly => false; + public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(ref _tags, "tags", new List()); + + serializer.DataReadWriteFunction("tags", new List(), + value => + { + _tags.Clear(); + _tags.UnionWith(value); + }, + () => new List(_tags)); } - public List GetTags() + public void SetTags(IEnumerable newTags) { - return _tags; - } - - public void SetTags(List newTags) - { - _tags = newTags; + _tags.Clear(); + _tags.UnionWith(newTags); } } } diff --git a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs index 8f1e89da30..f9e6c07a9d 100644 --- a/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs +++ b/Content.Server/GameObjects/Components/Access/AccessReaderComponent.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; using Content.Shared.GameObjects.Components.Inventory; @@ -9,54 +10,70 @@ using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; +#nullable enable + namespace Content.Server.GameObjects.Components.Access { + /// + /// Stores access levels necessary to "use" an entity + /// and allows checking if something or somebody is authorized with these access levels. + /// + [PublicAPI] [RegisterComponent] public class AccessReader : Component { public override string Name => "AccessReader"; - [ViewVariables] - private List _necessaryTags; - [ViewVariables] - private List _sufficientTags; + + private readonly List> _accessLists = new List>(); + private readonly HashSet _denyTags = new HashSet(); /// - /// Searches an in the entity itself, in its active hand or in its ID slot. - /// Returns true if an is found and its tags list contains - /// at least one of or all of . + /// List of access lists to check allowed against. For an access check to pass + /// there has to be an access list that is a subset of the access in the checking list. /// + [ViewVariables] public IList> AccessLists => _accessLists; + + /// + /// The set of tags that will automatically deny an allowed check, if any of them are present. + /// + [ViewVariables] public ISet DenyTags => _denyTags; + + /// + /// Searches an in the entity itself, in its active hand or in its ID slot. + /// Then compares the found access with the configured access lists to see if it is allowed. + /// + /// + /// If no access is found, an empty set is used instead. + /// /// The entity to be searched for access. public bool IsAllowed(IEntity entity) { var tags = FindAccessTags(entity); - return tags != null && IsAllowed(tags); + return IsAllowed(tags); } - private bool IsAllowed(List accessTags) + public bool IsAllowed(IAccess access) { - foreach (var sufficient in _sufficientTags) + return IsAllowed(access.Tags); + } + + public bool IsAllowed(ICollection accessTags) + { + if (_denyTags.Overlaps(accessTags)) { - if (accessTags.Contains(sufficient)) - { - return true; - } + // Sec owned by cargo. + return false; } - foreach (var necessary in _necessaryTags) - { - if (!accessTags.Contains(necessary)) - { - return false; - } - } - return true; + + return _accessLists.Count == 0 || _accessLists.Any(a => a.IsSubsetOf(accessTags)); } [CanBeNull] - private static List FindAccessTags(IEntity entity) + private static ICollection FindAccessTags(IEntity entity) { if (entity.TryGetComponent(out IAccess accessComponent)) { - return accessComponent.GetTags(); + return accessComponent.Tags; } if (entity.TryGetComponent(out IHandsComponent handsComponent)) @@ -65,32 +82,42 @@ namespace Content.Server.GameObjects.Components.Access if (activeHandEntity != null && activeHandEntity.TryGetComponent(out IAccess handAccessComponent)) { - return handAccessComponent.GetTags(); + return handAccessComponent.Tags; } } else { - return null; + return Array.Empty(); } + if (entity.TryGetComponent(out InventoryComponent inventoryComponent)) { if (inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) && inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent item) && item.Owner.TryGetComponent(out IAccess idAccessComponent) - ) + ) { - return idAccessComponent.GetTags(); + return idAccessComponent.Tags; } } - return null; + + return Array.Empty(); } public override void ExposeData(ObjectSerializer serializer) { base.ExposeData(serializer); - serializer.DataField(ref _necessaryTags, "necessary" ,new List()); - serializer.DataField(ref _sufficientTags, "sufficient", new List()); + serializer.DataReadWriteFunction("access", new List>(), + v => + { + if (v.Count != 0) + { + _accessLists.Clear(); + _accessLists.AddRange(v.Select(a => new HashSet(a))); + } + }, + () => _accessLists.Select(p => new List(p)).ToList()); } } } diff --git a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs index 868c46a0db..cad078d673 100644 --- a/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Access/IdCardConsoleComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Interfaces.GameObjects; @@ -180,7 +181,7 @@ namespace Content.Server.GameObjects.Components.Access true, targetIdComponent.FullName, targetIdComponent.JobTitle, - targetAccessComponent.GetTags(), + targetAccessComponent.Tags.ToArray(), _privilegedIdContainer.ContainedEntity?.Name ?? "", _targetIdContainer.ContainedEntity?.Name ?? ""); } diff --git a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs index 9fb8ead381..52f9b4f670 100644 --- a/Content.Server/GameObjects/Components/PDA/PDAComponent.cs +++ b/Content.Server/GameObjects/Components/PDA/PDAComponent.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using Content.Server.GameObjects.Components.Access; @@ -6,18 +8,18 @@ using Content.Server.Interfaces; using Content.Server.Interfaces.PDA; using Content.Shared.GameObjects; using Content.Shared.GameObjects.Components.PDA; -using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Server.GameObjects.Components.Container; using Robust.Server.GameObjects.Components.UserInterface; using Robust.Server.Interfaces.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Players; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +#nullable enable namespace Content.Server.GameObjects.Components.PDA { @@ -26,24 +28,29 @@ namespace Content.Server.GameObjects.Components.PDA [ComponentReference(typeof(IAccess))] public class PDAComponent : SharedPDAComponent, IInteractUsing, IActivate, IUse, IAccess { -#pragma warning disable 649 - [Dependency] protected readonly IPDAUplinkManager _uplinkManager; - [Dependency] protected readonly IEntityManager _entityManager; -#pragma warning restore 649 + [Dependency] private readonly IPDAUplinkManager _uplinkManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; - private Container _idSlot; - private PointLightComponent _pdaLight; - private bool _lightOn = false; - private BoundUserInterface _interface; - private string _startingIdCard; - public bool IdSlotEmpty => _idSlot.ContainedEntities.Count < 1; - public IEntity OwnerMob { get; private set; } + [ViewVariables] private Container _idSlot = default!; + [ViewVariables] private PointLightComponent _pdaLight = default!; + [ViewVariables] private bool _lightOn; + [ViewVariables] private BoundUserInterface _interface = default!; + [ViewVariables] private string _startingIdCard = default!; + [ViewVariables] public bool IdSlotEmpty => _idSlot.ContainedEntities.Count < 1; + [ViewVariables] public IEntity? OwnerMob { get; private set; } - public IdCardComponent ContainedID { get; private set; } + [ViewVariables] public IdCardComponent? ContainedID { get; private set; } - private AppearanceComponent _appearance; + [ViewVariables] private AppearanceComponent _appearance = default!; - private UplinkAccount _syndicateUplinkAccount; + [ViewVariables] private UplinkAccount? _syndicateUplinkAccount; + + [ViewVariables] private readonly PdaAccessSet _accessSet; + + public PDAComponent() + { + _accessSet = new PdaAccessSet(this); + } public override void ExposeData(ObjectSerializer serializer) { @@ -89,7 +96,6 @@ namespace Content.Server.GameObjects.Components.PDA case PDAUplinkBuyListingMessage buyMsg: { - if (!_uplinkManager.TryPurchaseItem(_syndicateUplinkAccount, buyMsg.ListingToBuy)) { //TODO: Send a message that tells the buyer they are too poor or something. @@ -112,13 +118,14 @@ namespace Content.Server.GameObjects.Components.PDA //Do we have an account? If so provide the info. if (_syndicateUplinkAccount != null) { - var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder, _syndicateUplinkAccount.Balance); + var accData = new UplinkAccountData(_syndicateUplinkAccount.AccountHolder, + _syndicateUplinkAccount.Balance); var listings = _uplinkManager.FetchListings.ToArray(); - _interface.SetState(new PDAUpdateState(_lightOn,ownerInfo,accData,listings)); + _interface.SetState(new PDAUpdateState(_lightOn, ownerInfo, accData, listings)); } else { - _interface.SetState(new PDAUpdateState(_lightOn,ownerInfo)); + _interface.SetState(new PDAUpdateState(_lightOn, ownerInfo)); } UpdatePDAAppearance(); @@ -141,10 +148,10 @@ namespace Content.Server.GameObjects.Components.PDA { return false; } + InsertIdCard(idCardComponent); UpdatePDAUserInterface(); return true; - } void IActivate.Activate(ActivateEventArgs eventArgs) @@ -153,6 +160,7 @@ namespace Content.Server.GameObjects.Components.PDA { return; } + _interface.Open(actor.playerSession); UpdatePDAAppearance(); } @@ -163,6 +171,7 @@ namespace Content.Server.GameObjects.Components.PDA { return false; } + _interface.Open(actor.playerSession); UpdatePDAAppearance(); return true; @@ -211,7 +220,7 @@ namespace Content.Server.GameObjects.Components.PDA private void HandleIDEjection(IEntity pdaUser) { - if (IdSlotEmpty) + if (ContainedID == null) { return; } @@ -241,14 +250,129 @@ namespace Content.Server.GameObjects.Components.PDA } } - List IAccess.GetTags() + private ISet? GetContainedAccess() { - return ContainedID?.Owner.GetComponent()?.GetTags(); + return ContainedID?.Owner?.GetComponent()?.Tags; } - void IAccess.SetTags(List newTags) + ISet IAccess.Tags => _accessSet; + bool IAccess.IsReadOnly => true; + + void IAccess.SetTags(IEnumerable newTags) { - ContainedID?.Owner.GetComponent().SetTags(newTags); + throw new NotSupportedException("PDA access list is read-only."); + } + + private sealed class PdaAccessSet : ISet + { + private readonly PDAComponent _pdaComponent; + private static readonly HashSet EmptySet = new HashSet(); + + public PdaAccessSet(PDAComponent pdaComponent) + { + _pdaComponent = pdaComponent; + } + + public IEnumerator GetEnumerator() + { + var contained = _pdaComponent.GetContainedAccess() ?? EmptySet; + return contained.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void ICollection.Add(string item) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public void ExceptWith(IEnumerable other) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public void IntersectWith(IEnumerable other) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.Overlaps(other); + } + + public bool SetEquals(IEnumerable other) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + return set.SetEquals(other); + } + + public void SymmetricExceptWith(IEnumerable other) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public void UnionWith(IEnumerable other) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + bool ISet.Add(string item) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public void Clear() + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public bool Contains(string item) + { + return _pdaComponent.GetContainedAccess()?.Contains(item) ?? false; + } + + public void CopyTo(string[] array, int arrayIndex) + { + var set = _pdaComponent.GetContainedAccess() ?? EmptySet; + set.CopyTo(array, arrayIndex); + } + + public bool Remove(string item) + { + throw new NotSupportedException("PDA access list is read-only."); + } + + public int Count => _pdaComponent.GetContainedAccess()?.Count ?? 0; + public bool IsReadOnly => true; } } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index 796e2e5c0a..de97df2a5d 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -647,8 +647,8 @@ namespace Content.Server.GameTicking card.JobTitle = jobPrototype.Name; var access = card.Owner.GetComponent(); - var accessTags = access.GetTags(); - accessTags.AddRange(jobPrototype.Access); + var accessTags = access.Tags; + accessTags.UnionWith(jobPrototype.Access); access.SetTags(accessTags); pdaComponent.SetPDAOwner(mob); var mindComponent = mob.GetComponent(); diff --git a/Content.Server/Interfaces/IAccess.cs b/Content.Server/Interfaces/IAccess.cs index 1729f99d3f..f50cae8214 100644 --- a/Content.Server/Interfaces/IAccess.cs +++ b/Content.Server/Interfaces/IAccess.cs @@ -1,11 +1,34 @@ +using System; +using Content.Server.GameObjects.Components.Access; using System.Collections.Generic; +#nullable enable + namespace Content.Server.Interfaces { + /// + /// Contains access levels that can be checked to see if somebody has access with an . + /// public interface IAccess { - public List GetTags(); + /// + /// The set of access tags this thing has. + /// + /// + /// This set may be read-only. Check if you want to mutate it. + /// + ISet Tags { get; } - public void SetTags(List newTags); + /// + /// Whether the list is read-only. + /// + bool IsReadOnly { get; } + + /// + /// Replaces the set of access tags we have with the provided set. + /// + /// The new access tags + /// If this access tag list is read-only. + void SetTags(IEnumerable newTags); } } diff --git a/Content.Shared/GameObjects/Components/Access/SharedIdCardConsoleComponent.cs b/Content.Shared/GameObjects/Components/Access/SharedIdCardConsoleComponent.cs index 49250832ff..fc4eb9c78c 100644 --- a/Content.Shared/GameObjects/Components/Access/SharedIdCardConsoleComponent.cs +++ b/Content.Shared/GameObjects/Components/Access/SharedIdCardConsoleComponent.cs @@ -52,14 +52,14 @@ namespace Content.Shared.GameObjects.Components.Access public readonly string TargetIdName; public readonly string TargetIdFullName; public readonly string TargetIdJobTitle; - public readonly List TargetIdAccessList; + public readonly string[] TargetIdAccessList; public IdCardConsoleBoundUserInterfaceState(bool isPrivilegedIdPresent, bool isPrivilegedIdAuthorized, bool isTargetIdPresent, string targetIdFullName, string targetIdJobTitle, - List targetIdAccessList, string privilegedIdName, string targetIdName) + string[] targetIdAccessList, string privilegedIdName, string targetIdName) { IsPrivilegedIdPresent = isPrivilegedIdPresent; IsPrivilegedIdAuthorized = isPrivilegedIdAuthorized; diff --git a/Content.Tests/Server/GameObjects/Components/Access/AccessReaderTest.cs b/Content.Tests/Server/GameObjects/Components/Access/AccessReaderTest.cs new file mode 100644 index 0000000000..724e1e5c37 --- /dev/null +++ b/Content.Tests/Server/GameObjects/Components/Access/AccessReaderTest.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using Content.Server.GameObjects.Components.Access; +using NUnit.Framework; + +namespace Content.Tests.Server.GameObjects.Components.Access +{ + [TestFixture] + [TestOf(typeof(AccessReader))] + public class AccessReaderTest : ContentUnitTest + { + [Test] + public void TestEmpty() + { + var reader = new AccessReader(); + + Assert.That(reader.IsAllowed(new[] {"Foo"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"Bar"}), Is.True); + Assert.That(reader.IsAllowed(new string[] {}), Is.True); + } + + [Test] + public void TestDeny() + { + var reader = new AccessReader(); + reader.DenyTags.Add("A"); + + Assert.That(reader.IsAllowed(new[] {"Foo"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"A"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"A", "Foo"}), Is.False); + Assert.That(reader.IsAllowed(new string[] {}), Is.True); + } + + [Test] + public void TestOneList() + { + var reader = new AccessReader(); + reader.AccessLists.Add(new HashSet {"A"}); + + Assert.That(reader.IsAllowed(new[] {"A"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"B"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"A", "B"}), Is.True); + Assert.That(reader.IsAllowed(new string[] {}), Is.False); + } + + [Test] + public void TestOneListTwoItems() + { + var reader = new AccessReader(); + reader.AccessLists.Add(new HashSet {"A", "B"}); + + Assert.That(reader.IsAllowed(new[] {"A"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"B"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"A", "B"}), Is.True); + Assert.That(reader.IsAllowed(new string[] {}), Is.False); + } + + [Test] + public void TestTwoList() + { + var reader = new AccessReader(); + reader.AccessLists.Add(new HashSet {"A"}); + reader.AccessLists.Add(new HashSet {"B", "C"}); + + Assert.That(reader.IsAllowed(new[] {"A"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"B"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"A", "B"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"C", "B"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"C", "B", "A"}), Is.True); + Assert.That(reader.IsAllowed(new string[] {}), Is.False); + } + + [Test] + public void TestDenyList() + { + var reader = new AccessReader(); + reader.AccessLists.Add(new HashSet {"A"}); + reader.DenyTags.Add("B"); + + Assert.That(reader.IsAllowed(new[] {"A"}), Is.True); + Assert.That(reader.IsAllowed(new[] {"B"}), Is.False); + Assert.That(reader.IsAllowed(new[] {"A", "B"}), Is.False); + Assert.That(reader.IsAllowed(new string[] {}), Is.False); + } + } +} diff --git a/Resources/Maps/saltern.yml b/Resources/Maps/saltern.yml index 875cb20e69..a25dcdb111 100644 --- a/Resources/Maps/saltern.yml +++ b/Resources/Maps/saltern.yml @@ -7484,8 +7484,8 @@ entities: pos: 6.5,17.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - HeadOfPersonnel + - access: + - - HeadOfPersonnel type: AccessReader - uid: 1042 type: reinforced_wall @@ -22369,8 +22369,8 @@ entities: pos: 5.5,25.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Captain + - access: + - - Captain type: AccessReader - uid: 2893 type: AirlockMaintCommonLocked @@ -22527,9 +22527,9 @@ entities: pos: 38.5,1.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Engineering - - Command + - access: + - - Engineering + - Command type: AccessReader - uid: 2912 type: Catwalk @@ -22545,8 +22545,8 @@ entities: pos: 11.5,20.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - HeadOfPersonnel + - access: + - - HeadOfPersonnel type: AccessReader - uid: 2914 type: AirlockExternalLocked @@ -22601,8 +22601,8 @@ entities: pos: -8.5,-7.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Service + - access: + - - Service type: AccessReader - uid: 2921 type: AirlockServiceGlassLocked @@ -22687,8 +22687,8 @@ entities: pos: -20.5,11.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Janitor + - access: + - - Janitor type: AccessReader - uid: 2932 type: AirlockEngineeringGlassLocked @@ -22735,9 +22735,9 @@ entities: pos: 20.5,-13.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Medical - - Command + - access: + - - Medical + - Command type: AccessReader - uid: 2937 type: AirlockCommandGlassLocked @@ -22748,9 +22748,9 @@ entities: pos: -8.5,-21.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Research - - Command + - access: + - - Research + - Command type: AccessReader - uid: 2938 type: AirlockCommandGlassLocked @@ -22761,9 +22761,9 @@ entities: pos: -3.5,-21.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Research - - Command + - access: + - - Research + - Command type: AccessReader - uid: 2939 type: AirlockScienceLocked @@ -22801,8 +22801,8 @@ entities: pos: -25.5,10.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Maintenance + - access: + - - Maintenance type: AccessReader - uid: 2943 type: AirlockServiceGlassLocked @@ -22813,8 +22813,8 @@ entities: pos: -25.5,8.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Maintenance + - access: + - - Maintenance type: AccessReader - uid: 2944 type: AirlockGlass @@ -22834,8 +22834,8 @@ entities: pos: -20.5,-7.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Theatre + - access: + - - Theatre type: AccessReader - uid: 2946 type: Airlock @@ -22889,8 +22889,8 @@ entities: pos: -14.5,-4.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Service + - access: + - - Service type: AccessReader - uid: 2952 type: AirlockMaintCommonLocked @@ -22899,8 +22899,8 @@ entities: pos: -16.5,-7.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Service + - access: + - - Service type: AccessReader - uid: 2953 type: AirlockMaintCommonLocked @@ -23002,8 +23002,8 @@ entities: pos: 7.5,6.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - External + - access: + - - External type: AccessReader - uid: 2966 type: AirlockCommandGlassLocked @@ -23014,8 +23014,8 @@ entities: pos: 9.5,6.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - External + - access: + - - External type: AccessReader - uid: 2967 type: AirlockMaintCommandLocked @@ -23024,8 +23024,8 @@ entities: pos: 8.5,12.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - External + - access: + - - External type: AccessReader - uid: 2968 type: AirlockCargoGlassLocked @@ -23063,8 +23063,8 @@ entities: pos: 20.5,14.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Cargo + - access: + - - Cargo type: AccessReader - uid: 2972 type: AirlockExternalLocked @@ -23075,8 +23075,8 @@ entities: pos: 20.5,16.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Cargo + - access: + - - Cargo type: AccessReader - uid: 2973 type: AirlockExternalLocked @@ -23087,8 +23087,8 @@ entities: pos: 22.5,16.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Cargo + - access: + - - Cargo type: AccessReader - uid: 2974 type: AirlockExternalLocked @@ -23099,8 +23099,8 @@ entities: pos: 22.5,14.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Cargo + - access: + - - Cargo type: AccessReader - uid: 2975 type: AirlockExternalLocked @@ -23118,9 +23118,9 @@ entities: pos: -8.5,18.5 rot: -1.5707963267948966 rad type: Transform - - necessary: - - Security - - Command + - access: + - - Security + - Command type: AccessReader - uid: 2977 type: Poweredlight diff --git a/Resources/Prototypes/Entities/Buildings/Doors/airlock_access.yml b/Resources/Prototypes/Entities/Buildings/Doors/airlock_access.yml index 31df9ef456..eef2f65d98 100644 --- a/Resources/Prototypes/Entities/Buildings/Doors/airlock_access.yml +++ b/Resources/Prototypes/Entities/Buildings/Doors/airlock_access.yml @@ -5,7 +5,7 @@ suffix: Service, Locked components: - type: AccessReader - necessary: ["Service"] + access: [["Service"]] - type: entity parent: AirlockExternal @@ -13,7 +13,7 @@ suffix: External, Locked components: - type: AccessReader - necessary: ["External"] + access: [["External"]] - type: entity parent: AirlockEngineering @@ -21,7 +21,7 @@ suffix: Engineering, Locked components: - type: AccessReader - necessary: ["Engineering"] + access: [["Engineering"]] - type: entity parent: AirlockCargo @@ -29,7 +29,7 @@ suffix: Cargo, Locked components: - type: AccessReader - necessary: ["Cargo"] + access: [["Cargo"]] - type: entity parent: AirlockMedical @@ -37,7 +37,7 @@ suffix: Medical, Locked components: - type: AccessReader - necessary: ["Medical"] + access: [["Medical"]] - type: entity parent: AirlockScience @@ -45,7 +45,7 @@ suffix: Science, Locked components: - type: AccessReader - necessary: ["Research"] + access: [["Research"]] - type: entity parent: AirlockCommand @@ -53,7 +53,7 @@ suffix: Command, Locked components: - type: AccessReader - necessary: ["Command"] + access: [["Command"]] - type: entity parent: AirlockSecurity @@ -61,7 +61,7 @@ suffix: Security, Locked components: - type: AccessReader - necessary: ["Security"] + access: [["Security"]] - type: entity parent: AirlockSecurity @@ -69,7 +69,7 @@ suffix: Vault, Locked components: - type: AccessReader - necessary: ["Security", "Command"] + access: [["Security", "Command"]] - type: entity parent: AirlockCommand @@ -77,7 +77,7 @@ suffix: EVA, Locked components: - type: AccessReader - necessary: ["External"] + access: [["External"]] # Glass Airlocks - type: entity @@ -86,7 +86,7 @@ suffix: Service, Locked components: - type: AccessReader - necessary: ["Service"] + access: [["Service"]] - type: entity parent: AirlockEngineeringGlass @@ -94,7 +94,7 @@ suffix: Glass, Locked components: - type: AccessReader - necessary: ["Engineering"] + access: [["Engineering"]] - type: entity parent: AirlockCargoGlass @@ -102,7 +102,7 @@ suffix: Cargo, Locked components: - type: AccessReader - necessary: ["Cargo"] + access: [["Cargo"]] - type: entity parent: AirlockMedicalGlass @@ -110,7 +110,7 @@ suffix: Medical, Locked components: - type: AccessReader - necessary: ["Medical"] + access: [["Medical"]] - type: entity parent: AirlockScienceGlass @@ -118,7 +118,7 @@ suffix: Science, Locked components: - type: AccessReader - necessary: ["Research"] + access: [["Research"]] - type: entity parent: AirlockCommandGlass @@ -126,7 +126,7 @@ suffix: Command, Locked components: - type: AccessReader - necessary: ["Command"] + access: [["Command"]] - type: entity parent: AirlockSecurityGlass @@ -134,7 +134,7 @@ suffix: Security, Locked components: - type: AccessReader - necessary: ["Security"] + access: [["Security"]] # Maintenance Hatchs - type: entity @@ -143,7 +143,7 @@ suffix: Locked components: - type: AccessReader - necessary: ["Maintenance"] + access: [["Maintenance"]] - type: entity parent: AirlockMaintCargo @@ -151,7 +151,7 @@ suffix: Cargo, Locked components: - type: AccessReader - necessary: ["Cargo", "Maintenance"] + access: [["Cargo"]] - type: entity parent: AirlockMaintCommand @@ -159,7 +159,7 @@ suffix: Command, Locked components: - type: AccessReader - necessary: ["Command", "Maintenance"] + access: [["Command"]] - type: entity parent: AirlockMaintCommon @@ -167,7 +167,7 @@ suffix: Common, Locked components: - type: AccessReader - necessary: ["Maintenance"] + access: [["Maintenance"]] - type: entity parent: AirlockMaintEngi @@ -175,15 +175,15 @@ suffix: Engineering, Locked components: - type: AccessReader - necessary: ["Engineering", "Maintenance"] - + access: [["Engineering"]] + - type: entity parent: AirlockMaintInt id: AirlockMaintIntLocked suffix: Interior, Locked components: - type: AccessReader - necessary: ["Maintenance"] + access: [["Maintenance"]] - type: entity parent: AirlockMaintMed @@ -191,7 +191,7 @@ suffix: Medical, Locked components: - type: AccessReader - necessary: ["Medical", "Maintenance"] + access: [["Medical"]] - type: entity parent: AirlockMaintRnD @@ -199,7 +199,7 @@ suffix: RnD, Locked components: - type: AccessReader - necessary: ["Research", "Maintenance"] + access: [["Research"]] - type: entity parent: AirlockMaintSec @@ -207,4 +207,4 @@ suffix: Security, Locked components: - type: AccessReader - necessary: ["Security", "Maintenance"] + access: [["Security"]] diff --git a/Resources/Prototypes/Entities/Buildings/computers.yml b/Resources/Prototypes/Entities/Buildings/computers.yml index 31b58a932b..611bd5c2cb 100644 --- a/Resources/Prototypes/Entities/Buildings/computers.yml +++ b/Resources/Prototypes/Entities/Buildings/computers.yml @@ -158,7 +158,7 @@ name: ID Card Computer components: - type: AccessReader - necessary: ["HeadOfPersonnel"] + access: [["HeadOfPersonnel"]] - type: IdCardConsole - type: UserInterface interfaces: