Add door electronics access configuration menu (#17778)

* Add door electronics configuration menu

* Use file-scoped namespaces

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Open door electronics configuration menu only with network configurator

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Doors will now try to move their AccessReaderComponent to their door electronics when the map is initialized

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Make the access list in the id card computer a separate control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix merge conflict

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove DoorElectronics tag

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Integrate doors with #17927

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move door electornics ui stuff to the right place

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Some review fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* More fixes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* review fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move all accesses from airlock prototypes to door electronics

Signed-off-by: c4llv07e <kseandi@gmail.com>

* rework door electronics config access list

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove Linq from the door electronics user interface

* [WIP] Add EntityWhitelist to the activatable ui component

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Better interaction system

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Refactor

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix some door electronics not working without AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Move AccessReaderComponent update code to the AccessReaderSystem

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecesary newlines in the door access prototypes

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused variables in access level control

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unnecessary method from the door electronics configuration menu

Signed-off-by: c4llv07e <kseandi@gmail.com>

* [WIP] change access type from string to ProtoId<AccessLevelPrototype>

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Remove unused methods

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Newline fix

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Restored to a functional state

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Fix access configurator not working with door electronics AccessReaderComponent

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Replace all string access fields with ProtoId

Signed-off-by: c4llv07e <kseandi@gmail.com>

* move access level control initialization into Populate method

Signed-off-by: c4llv07e <kseandi@gmail.com>

* Review

---------

Signed-off-by: c4llv07e <kseandi@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
c4llv07e
2024-04-01 09:06:13 +03:00
committed by GitHub
parent 72bdcac1e2
commit 64bb8dbdd5
40 changed files with 1079 additions and 532 deletions

View File

@@ -0,0 +1,4 @@
<GridContainer xmlns="https://spacestation14.io"
Columns="5"
HorizontalAlignment="Center">
</GridContainer>

View File

@@ -0,0 +1,52 @@
using System.Linq;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
namespace Content.Client.Access.UI;
[GenerateTypedNameReferences]
public sealed partial class AccessLevelControl : GridContainer
{
public readonly Dictionary<ProtoId<AccessLevelPrototype>, Button> ButtonsList = new();
public AccessLevelControl()
{
RobustXamlLoader.Load(this);
}
public void Populate(List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
{
foreach (var access in accessLevels)
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
Logger.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = accessLevel.GetAccessLevelName(),
ToggleMode = true,
};
AddChild(newButton);
ButtonsList.Add(accessLevel.ID, newButton);
}
}
public void UpdateState(
List<ProtoId<AccessLevelPrototype>> pressedList,
List<ProtoId<AccessLevelPrototype>>? enabledList = null)
{
foreach (var (accessName, button) in ButtonsList)
{
button.Pressed = pressedList.Contains(accessName);
button.Disabled = !(enabledList?.Contains(accessName) ?? true);
}
}
}

View File

@@ -64,7 +64,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState);
}
public void SubmitData(List<string> newAccessList)
public void SubmitData(List<ProtoId<AccessLevelPrototype>> newAccessList)
{
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
}

View File

@@ -16,7 +16,6 @@ namespace Content.Client.Access.UI
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly ISawmill _logMill = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
@@ -25,7 +24,7 @@ namespace Content.Client.Access.UI
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
var logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
_owner = owner;
@@ -33,13 +32,13 @@ namespace Content.Client.Access.UI
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
_logMill.Error($"Unable to find accesslevel for {access}");
logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
Text = GetAccessLevelName(accessLevel),
Text = accessLevel.GetAccessLevelName(),
ToggleMode = true,
};
@@ -49,14 +48,6 @@ namespace Content.Client.Access.UI
}
}
private static string GetAccessLevelName(AccessLevelPrototype prototype)
{
if (prototype.Name is { } name)
return Loc.GetString(name);
return prototype.ID;
}
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdLabel.Text = state.PrivilegedIdName;
@@ -105,7 +96,7 @@ namespace Content.Client.Access.UI
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
_accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId<AccessLevelPrototype>(x.Key)).ToList());
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
@@ -28,7 +29,6 @@ namespace Content.Client.Access.UI
if (EntMan.TryGetComponent<IdCardConsoleComponent>(Owner, out var idCard))
{
accessLevels = idCard.AccessLevels;
accessLevels.Sort();
}
else
{
@@ -65,7 +65,7 @@ namespace Content.Client.Access.UI
_window?.UpdateState(castState);
}
public void SubmitData(string newFullName, string newJobTitle, List<string> newAccessList, string newJobPrototype)
public void SubmitData(string newFullName, string newJobTitle, List<ProtoId<AccessLevelPrototype>> newAccessList, string newJobPrototype)
{
if (newFullName.Length > MaxFullNameLength)
newFullName = newFullName[..MaxFullNameLength];

View File

@@ -30,10 +30,6 @@
<Label Text="{Loc 'id-card-console-window-job-selection-label'}" />
<OptionButton Name="JobPresetOptionButton" />
</GridContainer>
<GridContainer Name="AccessLevelGrid" Columns="5" HorizontalAlignment="Center">
<!-- Access level buttons are added here by the C# code -->
</GridContainer>
<Control Name="AccessLevelControlContainer" />
</BoxContainer>
</DefaultWindow>

View File

@@ -20,7 +20,7 @@ namespace Content.Client.Access.UI
private readonly IdCardConsoleBoundUserInterface _owner;
private readonly Dictionary<string, Button> _accessButtons = new();
private AccessLevelControl _accessButtons = new();
private readonly List<string> _jobPrototypeIds = new();
private string? _lastFullName;
@@ -66,36 +66,18 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
foreach (var access in accessLevels)
{
if (!prototypeManager.TryIndex<AccessLevelPrototype>(access, out var accessLevel))
{
_logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
_accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons);
var newButton = new Button
foreach (var (id, button) in _accessButtons.ButtonsList)
{
Text = GetAccessLevelName(accessLevel),
ToggleMode = true,
};
AccessLevelGrid.AddChild(newButton);
_accessButtons.Add(accessLevel.ID, newButton);
newButton.OnPressed += _ => SubmitData();
button.OnPressed += _ => SubmitData();
}
}
private static string GetAccessLevelName(AccessLevelPrototype prototype)
{
if (prototype.Name is { } name)
return Loc.GetString(name);
return prototype.ID;
}
private void ClearAllAccess()
{
foreach (var button in _accessButtons.Values)
foreach (var button in _accessButtons.ButtonsList.Values)
{
if (button.Pressed)
{
@@ -119,7 +101,7 @@ namespace Content.Client.Access.UI
// this is a sussy way to do this
foreach (var access in job.Access)
{
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -134,7 +116,7 @@ namespace Content.Client.Access.UI
foreach (var access in groupPrototype.Tags)
{
if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -184,15 +166,10 @@ namespace Content.Client.Access.UI
JobPresetOptionButton.Disabled = !interfaceEnabled;
foreach (var (accessName, button) in _accessButtons)
{
button.Disabled = !interfaceEnabled;
if (interfaceEnabled)
{
button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
}
}
_accessButtons.UpdateState(state.TargetIdAccessList?.ToList() ??
new List<ProtoId<AccessLevelPrototype>>(),
state.AllowedModifyAccessList?.ToList() ??
new List<ProtoId<AccessLevelPrototype>>());
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
if (jobIndex >= 0)
@@ -215,7 +192,7 @@ namespace Content.Client.Access.UI
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
_accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}

View File

@@ -0,0 +1,59 @@
using Content.Shared.Access;
using Content.Shared.Doors.Electronics;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.Doors.Electronics;
public sealed class DoorElectronicsBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private DoorElectronicsConfigurationMenu? _window;
public DoorElectronicsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
List<ProtoId<AccessLevelPrototype>> accessLevels = new();
foreach (var accessLevel in _prototypeManager.EnumeratePrototypes<AccessLevelPrototype>())
{
if (accessLevel.Name != null)
{
accessLevels.Add(accessLevel.ID);
}
}
accessLevels.Sort();
_window = new DoorElectronicsConfigurationMenu(this, accessLevels, _prototypeManager);
_window.OnClose += Close;
_window.OpenCentered();
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
var castState = (DoorElectronicsConfigurationState) state;
_window?.UpdateState(castState);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_window?.Dispose();
}
public void UpdateConfiguration(List<ProtoId<AccessLevelPrototype>> newAccessList)
{
SendMessage(new DoorElectronicsUpdateConfigurationMessage(newAccessList));
}
}

View File

@@ -0,0 +1,6 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<Control Name="AccessLevelControlContainer" />
</controls:FancyWindow>

View File

@@ -0,0 +1,41 @@
using System.Linq;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Content.Client.Access.UI;
using Content.Client.Doors.Electronics;
using Content.Shared.Access;
using Content.Shared.Doors.Electronics;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
namespace Content.Client.Doors.Electronics;
[GenerateTypedNameReferences]
public sealed partial class DoorElectronicsConfigurationMenu : FancyWindow
{
private readonly DoorElectronicsBoundUserInterface _owner;
private AccessLevelControl _buttonsList = new();
public DoorElectronicsConfigurationMenu(DoorElectronicsBoundUserInterface ui, List<ProtoId<AccessLevelPrototype>> accessLevels, IPrototypeManager prototypeManager)
{
RobustXamlLoader.Load(this);
_owner = ui;
_buttonsList.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_buttonsList);
foreach (var (id, button) in _buttonsList.ButtonsList)
{
button.OnPressed += _ => _owner.UpdateConfiguration(
_buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
}
}
public void UpdateState(DoorElectronicsConfigurationState state)
{
_buttonsList.UpdateState(state.AccessList);
}
}

View File

@@ -57,9 +57,9 @@ namespace Content.IntegrationTests.Tests.Access
var reader = new AccessReaderComponent();
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "Bar" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Bar" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.True);
});
// test deny
@@ -67,58 +67,58 @@ namespace Content.IntegrationTests.Tests.Access
reader.DenyTags.Add("A");
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "Foo" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "Foo" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "Foo" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.True);
});
// test one list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
});
// test one list - two items
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A", "B" });
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A", "B" });
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
});
// test two list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
reader.AccessLists.Add(new HashSet<string> { "B", "C" });
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "B", "C" });
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "C", "B", "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "C", "B" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "C", "B", "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
});
// test deny list
reader = new AccessReaderComponent();
reader.AccessLists.Add(new HashSet<string> { "A" });
reader.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>> { "A" });
reader.DenyTags.Add("B");
Assert.Multiple(() =>
{
Assert.That(system.AreAccessTagsAllowed(new[] { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new[] { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new[] { "A", "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<string>(), reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A" }, reader), Is.True);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(new List<ProtoId<AccessLevelPrototype>> { "A", "B" }, reader), Is.False);
Assert.That(system.AreAccessTagsAllowed(Array.Empty<ProtoId<AccessLevelPrototype>>(), reader), Is.False);
});
});
await pair.CleanReturnAsync();

View File

@@ -1,5 +1,6 @@
using System.Linq;
using Content.Server.Popups;
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.Administration.Logs;
@@ -12,6 +13,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using static Content.Shared.Access.Components.AccessOverriderComponent;
namespace Content.Server.Access.Systems;
@@ -26,6 +28,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
public override void Initialize()
{
@@ -108,17 +111,20 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
var targetLabel = Loc.GetString("access-overrider-window-no-target");
var targetLabelColor = Color.Red;
string[]? possibleAccess = null;
string[]? currentAccess = null;
string[]? missingAccess = null;
ProtoId<AccessLevelPrototype>[]? possibleAccess = null;
ProtoId<AccessLevelPrototype>[]? currentAccess = null;
ProtoId<AccessLevelPrototype>[]? missingAccess = null;
if (component.TargetAccessReaderId is { Valid: true } accessReader)
{
targetLabel = Loc.GetString("access-overrider-window-target-label") + " " + EntityManager.GetComponent<MetaDataComponent>(component.TargetAccessReaderId).EntityName;
targetLabelColor = Color.White;
List<HashSet<string>> currentAccessHashsets = EntityManager.GetComponent<AccessReaderComponent>(accessReader).AccessLists;
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets)?.ToArray();
if (!_accessReader.GetMainAccessReader(accessReader, out var accessReaderComponent))
return;
var currentAccessHashsets = accessReaderComponent.AccessLists;
currentAccess = ConvertAccessHashSetsToList(currentAccessHashsets).ToArray();
}
if (component.PrivilegedIdSlot.Item is { Valid: true } idCard)
@@ -151,15 +157,15 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
_userInterface.TrySetUiState(uid, AccessOverriderUiKey.Key, newState);
}
private List<string> ConvertAccessHashSetsToList(List<HashSet<string>> accessHashsets)
private List<ProtoId<AccessLevelPrototype>> ConvertAccessHashSetsToList(List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets)
{
List<string> accessList = new List<string>();
List<ProtoId<AccessLevelPrototype>> accessList = new List<ProtoId<AccessLevelPrototype>>();
if (accessHashsets != null && accessHashsets.Any())
{
foreach (HashSet<string> hashSet in accessHashsets)
foreach (HashSet<ProtoId<AccessLevelPrototype>> hashSet in accessHashsets)
{
foreach (string hash in hashSet.ToArray())
foreach (ProtoId<AccessLevelPrototype> hash in hashSet.ToArray())
{
accessList.Add(hash);
}
@@ -169,15 +175,15 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
return accessList;
}
private List<HashSet<string>> ConvertAccessListToHashSet(List<string> accessList)
private List<HashSet<ProtoId<AccessLevelPrototype>>> ConvertAccessListToHashSet(List<ProtoId<AccessLevelPrototype>> accessList)
{
List<HashSet<string>> accessHashsets = new List<HashSet<string>>();
List<HashSet<ProtoId<AccessLevelPrototype>>> accessHashsets = new List<HashSet<ProtoId<AccessLevelPrototype>>>();
if (accessList != null && accessList.Any())
{
foreach (string access in accessList)
foreach (ProtoId<AccessLevelPrototype> access in accessList)
{
accessHashsets.Add(new HashSet<string>() { access });
accessHashsets.Add(new HashSet<ProtoId<AccessLevelPrototype>>() { access });
}
}
@@ -188,7 +194,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
/// Called whenever an access button is pressed, adding or removing that access requirement from the target access reader.
/// </summary>
private void TryWriteToTargetAccessReaderId(EntityUid uid,
List<string> newAccessList,
List<ProtoId<AccessLevelPrototype>> newAccessList,
EntityUid player,
AccessOverriderComponent? component = null)
{
@@ -211,9 +217,7 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
return;
}
TryComp(component.TargetAccessReaderId, out AccessReaderComponent? accessReader);
if (accessReader == null)
if (!_accessReader.GetMainAccessReader(component.TargetAccessReaderId, out var accessReader))
return;
var oldTags = ConvertAccessHashSetsToList(accessReader.AccessLists);
@@ -262,10 +266,10 @@ public sealed class AccessOverriderSystem : SharedAccessOverriderSystem
if (!Resolve(uid, ref component))
return true;
if (!EntityManager.TryGetComponent<AccessReaderComponent>(uid, out var reader))
if (_accessReader.GetMainAccessReader(uid, out var accessReader))
return true;
var privilegedId = component.PrivilegedIdSlot.Item;
return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, reader);
return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, accessReader);
}
}

View File

@@ -12,6 +12,7 @@ using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using System.Linq;
using static Content.Shared.Access.Components.IdCardConsoleComponent;
using Content.Shared.Access;
namespace Content.Server.Access.Systems;
@@ -54,11 +55,11 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
return;
var privilegedIdName = string.Empty;
string[]? possibleAccess = null;
List<ProtoId<AccessLevelPrototype>>? possibleAccess = null;
if (component.PrivilegedIdSlot.Item is { Valid: true } item)
{
privilegedIdName = EntityManager.GetComponent<MetaDataComponent>(item).EntityName;
possibleAccess = _accessReader.FindAccessTags(item).ToArray();
possibleAccess = _accessReader.FindAccessTags(item).ToList();
}
IdCardConsoleBoundUserInterfaceState newState;
@@ -82,7 +83,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
var targetIdComponent = EntityManager.GetComponent<IdCardComponent>(targetId);
var targetAccessComponent = EntityManager.GetComponent<AccessComponent>(targetId);
var jobProto = string.Empty;
var jobProto = new ProtoId<AccessLevelPrototype>(string.Empty);
if (TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
&& keyStorage.Key is {} key
&& _record.TryGetRecord<GeneralStationRecord>(key, out var record))
@@ -96,7 +97,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
true,
targetIdComponent.FullName,
targetIdComponent.JobTitle,
targetAccessComponent.Tags.ToArray(),
targetAccessComponent.Tags.ToList(),
possibleAccess,
jobProto,
privilegedIdName,
@@ -113,8 +114,8 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
private void TryWriteToTargetId(EntityUid uid,
string newFullName,
string newJobTitle,
List<string> newAccessList,
string newJobProto,
List<ProtoId<AccessLevelPrototype>> newAccessList,
ProtoId<AccessLevelPrototype> newJobProto,
EntityUid player,
IdCardConsoleComponent? component = null)
{
@@ -140,7 +141,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
return;
}
var oldTags = _access.TryGetTags(targetId) ?? new List<string>();
var oldTags = _access.TryGetTags(targetId) ?? new List<ProtoId<AccessLevelPrototype>>();
oldTags = oldTags.ToList();
var privilegedId = component.PrivilegedIdSlot.Item;
@@ -189,7 +190,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
return privilegedId != null && _accessReader.IsAllowed(privilegedId.Value, uid, reader);
}
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, string newJobTitle, JobPrototype? newJobProto)
private void UpdateStationRecord(EntityUid uid, EntityUid targetId, string newFullName, ProtoId<AccessLevelPrototype> newJobTitle, JobPrototype? newJobProto)
{
if (!TryComp<StationRecordKeyStorageComponent>(targetId, out var keyStorage)
|| keyStorage.Key is not { } key

View File

@@ -35,6 +35,7 @@ using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Systems;
@@ -844,14 +845,14 @@ public sealed partial class AdminVerbSystem
{
var allAccess = _prototypeManager
.EnumeratePrototypes<AccessLevelPrototype>()
.Select(p => p.ID).ToArray();
.Select(p => new ProtoId<AccessLevelPrototype>(p.ID)).ToArray();
_accessSystem.TrySetTags(entity, allAccess);
}
private void RevokeAllAccess(EntityUid entity)
{
_accessSystem.TrySetTags(entity, Array.Empty<string>());
_accessSystem.TrySetTags(entity, new List<ProtoId<AccessLevelPrototype>>());
}
public enum TricksVerbPriorities

View File

@@ -0,0 +1,69 @@
using System.Linq;
using Content.Server.Doors.Electronics;
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.Doors.Electronics;
using Content.Shared.Doors;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Server.Doors.Electronics;
public sealed class DoorElectronicsSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DoorElectronicsComponent, DoorElectronicsUpdateConfigurationMessage>(OnChangeConfiguration);
SubscribeLocalEvent<DoorElectronicsComponent, AccessReaderConfigurationChangedEvent>(OnAccessReaderChanged);
SubscribeLocalEvent<DoorElectronicsComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
}
public void UpdateUserInterface(EntityUid uid, DoorElectronicsComponent component)
{
var accesses = new List<ProtoId<AccessLevelPrototype>>();
if (TryComp<AccessReaderComponent>(uid, out var accessReader))
{
foreach (var accessList in accessReader.AccessLists)
{
var access = accessList.FirstOrDefault();
accesses.Add(access);
}
}
var state = new DoorElectronicsConfigurationState(accesses);
_uiSystem.TrySetUiState(uid, DoorElectronicsConfigurationUiKey.Key, state);
}
private void OnChangeConfiguration(
EntityUid uid,
DoorElectronicsComponent component,
DoorElectronicsUpdateConfigurationMessage args)
{
var accessReader = EnsureComp<AccessReaderComponent>(uid);
_accessReader.SetAccesses(uid, accessReader, args.AccessList);
}
private void OnAccessReaderChanged(
EntityUid uid,
DoorElectronicsComponent component,
AccessReaderConfigurationChangedEvent args)
{
UpdateUserInterface(uid, component);
}
private void OnBoundUIOpened(
EntityUid uid,
DoorElectronicsComponent component,
BoundUIOpenedEvent args)
{
UpdateUserInterface(uid, component);
}
}

View File

@@ -14,6 +14,7 @@ using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Sandbox
{
@@ -121,7 +122,7 @@ namespace Content.Server.Sandbox
var allAccess = PrototypeManager
.EnumeratePrototypes<AccessLevelPrototype>()
.Select(p => p.ID).ToArray();
.Select(p => new ProtoId<AccessLevelPrototype>(p.ID)).ToList();
if (_inventory.TryGetSlotEntity(attached, "id", out var slotEntity))
{

View File

@@ -3,6 +3,7 @@ using Content.Server.Explosion.EntitySystems;
using Content.Server.Resist;
using Content.Server.Station.Components;
using Content.Server.Storage.Components;
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Coordinates;
using Content.Shared.DoAfter;
@@ -14,6 +15,7 @@ using Content.Shared.Tools.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Prototypes;
namespace Content.Server.Storage.EntitySystems;
@@ -138,7 +140,7 @@ public sealed class BluespaceLockerSystem : EntitySystem
}
/// <returns>True if any HashSet in <paramref name="a"/> would grant access to <paramref name="b"/></returns>
private bool AccessMatch(IReadOnlyCollection<HashSet<string>>? a, IReadOnlyCollection<HashSet<string>>? b)
private bool AccessMatch(IReadOnlyCollection<HashSet<ProtoId<AccessLevelPrototype>>>? a, IReadOnlyCollection<HashSet<ProtoId<AccessLevelPrototype>>>? b)
{
if ((a == null || a.Count == 0) && (b == null || b.Count == 0))
return true;

View File

@@ -1,3 +1,6 @@
using Content.Shared.Whitelist;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Player;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
@@ -34,12 +37,19 @@ namespace Content.Server.UserInterface
[DataField]
public bool RequireHands = true;
/// <summary>
/// Entities that are required to open this UI.
/// </summary>
[DataField("allowedItems")]
[ViewVariables(VVAccess.ReadWrite)]
public EntityWhitelist? AllowedItems = null;
/// <summary>
/// Whether you can activate this ui with activateinhand or not
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool rightClickOnly = false;
public bool RightClickOnly;
/// <summary>
/// Whether spectators (non-admin ghosts) should be allowed to view this UI.
@@ -63,4 +73,3 @@ namespace Content.Server.UserInterface
public ICommonSession? CurrentSingleUser;
}
}

View File

@@ -26,6 +26,7 @@ public sealed partial class ActivatableUISystem : EntitySystem
SubscribeLocalEvent<ActivatableUIComponent, ActivateInWorldEvent>(OnActivate);
SubscribeLocalEvent<ActivatableUIComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<ActivatableUIComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<ActivatableUIComponent, HandDeselectedEvent>(OnHandDeselected);
SubscribeLocalEvent<ActivatableUIComponent, GotUnequippedHandEvent>((uid, aui, _) => CloseAll(uid, aui));
// *THIS IS A BLATANT WORKAROUND!* RATIONALE: Microwaves need it
@@ -100,12 +101,20 @@ public sealed partial class ActivatableUISystem : EntitySystem
if (args.Handled)
return;
if (component.rightClickOnly)
if (component.RightClickOnly)
return;
args.Handled = InteractUI(args.User, uid, component);
}
private void OnInteractUsing(EntityUid uid, ActivatableUIComponent component, InteractUsingEvent args)
{
if (args.Handled) return;
if (component.AllowedItems == null) return;
if (!component.AllowedItems.IsValid(args.Used, EntityManager)) return;
args.Handled = InteractUI(args.User, uid, component);
}
private void OnParentChanged(EntityUid uid, ActivatableUIComponent aui, ref EntParentChangedMessage args)
{
CloseAll(uid, aui);

View File

@@ -14,6 +14,6 @@ public sealed partial class AccessGroupPrototype : IPrototype
[IdDataField]
public string ID { get; private set; } = default!;
[DataField("tags", required: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
public HashSet<string> Tags = default!;
[DataField("tags", required: true)]
public HashSet<ProtoId<AccessLevelPrototype>> Tags = default!;
}

View File

@@ -17,5 +17,13 @@ namespace Content.Shared.Access
/// </summary>
[DataField("name")]
public string? Name { get; set; }
public string GetAccessLevelName()
{
if (Name is { } name)
return Loc.GetString(name);
return ID;
}
}
}

View File

@@ -20,17 +20,17 @@ public sealed partial class AccessComponent : Component
[AutoNetworkedField]
public bool Enabled = true;
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
[DataField]
[Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
[AutoNetworkedField]
public HashSet<string> Tags = new();
public HashSet<ProtoId<AccessLevelPrototype>> Tags = new();
/// <summary>
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
/// </summary>
[DataField(readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
[DataField(readOnly: true)]
[AutoNetworkedField]
public HashSet<string> Groups = new();
public HashSet<ProtoId<AccessGroupPrototype>> Groups = new();
}
/// <summary>
@@ -47,9 +47,9 @@ public struct GetAdditionalAccessEvent
}
[ByRefEvent]
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
public record struct GetAccessTagsEvent(HashSet<ProtoId<AccessLevelPrototype>> Tags, IPrototypeManager PrototypeManager)
{
public void AddGroup(string group)
public void AddGroup(ProtoId<AccessGroupPrototype> group)
{
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
return;

View File

@@ -25,9 +25,9 @@ public sealed partial class AccessOverriderComponent : Component
[Serializable, NetSerializable]
public sealed class WriteToTargetAccessReaderIdMessage : BoundUserInterfaceMessage
{
public readonly List<string> AccessList;
public readonly List<ProtoId<AccessLevelPrototype>> AccessList;
public WriteToTargetAccessReaderIdMessage(List<string> accessList)
public WriteToTargetAccessReaderIdMessage(List<ProtoId<AccessLevelPrototype>> accessList)
{
AccessList = accessList;
}
@@ -48,15 +48,15 @@ public sealed partial class AccessOverriderComponent : Component
public readonly string PrivilegedIdName;
public readonly bool IsPrivilegedIdPresent;
public readonly bool IsPrivilegedIdAuthorized;
public readonly string[]? TargetAccessReaderIdAccessList;
public readonly string[]? AllowedModifyAccessList;
public readonly string[]? MissingPrivilegesList;
public readonly ProtoId<AccessLevelPrototype>[]? TargetAccessReaderIdAccessList;
public readonly ProtoId<AccessLevelPrototype>[]? AllowedModifyAccessList;
public readonly ProtoId<AccessLevelPrototype>[]? MissingPrivilegesList;
public AccessOverriderBoundUserInterfaceState(bool isPrivilegedIdPresent,
bool isPrivilegedIdAuthorized,
string[]? targetAccessReaderIdAccessList,
string[]? allowedModifyAccessList,
string[]? missingPrivilegesList,
ProtoId<AccessLevelPrototype>[]? targetAccessReaderIdAccessList,
ProtoId<AccessLevelPrototype>[]? allowedModifyAccessList,
ProtoId<AccessLevelPrototype>[]? missingPrivilegesList,
string privilegedIdName,
string targetLabel,
Color targetLabelColor)

View File

@@ -1,5 +1,6 @@
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;
@@ -23,15 +24,15 @@ public sealed partial class AccessReaderComponent : Component
/// The set of tags that will automatically deny an allowed check, if any of them are present.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
public HashSet<string> DenyTags = new();
[DataField]
public HashSet<ProtoId<AccessLevelPrototype>> DenyTags = new();
/// <summary>
/// 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.
/// </summary>
[DataField("access")] [ViewVariables(VVAccess.ReadWrite)]
public List<HashSet<string>> AccessLists = new();
public List<HashSet<ProtoId<AccessLevelPrototype>>> AccessLists = new();
/// <summary>
/// A list of <see cref="StationRecordKey"/>s that grant access. Only a single matching key is required to gain
@@ -88,9 +89,9 @@ public sealed class AccessReaderComponentState : ComponentState
{
public bool Enabled;
public HashSet<string> DenyTags;
public HashSet<ProtoId<AccessLevelPrototype>> DenyTags;
public List<HashSet<string>> AccessLists;
public List<HashSet<ProtoId<AccessLevelPrototype>>> AccessLists;
public List<(NetEntity, uint)> AccessKeys;
@@ -98,7 +99,7 @@ public sealed class AccessReaderComponentState : ComponentState
public int AccessLogLimit;
public AccessReaderComponentState(bool enabled, HashSet<string> denyTags, List<HashSet<string>> accessLists, List<(NetEntity, uint)> accessKeys, Queue<AccessRecord> accessLog, int accessLogLimit)
public AccessReaderComponentState(bool enabled, HashSet<ProtoId<AccessLevelPrototype>> denyTags, List<HashSet<ProtoId<AccessLevelPrototype>>> accessLists, List<(NetEntity, uint)> accessKeys, Queue<AccessRecord> accessLog, int accessLogLimit)
{
Enabled = enabled;
DenyTags = denyTags;
@@ -108,3 +109,10 @@ public sealed class AccessReaderComponentState : ComponentState
AccessLogLimit = accessLogLimit;
}
}
public sealed class AccessReaderConfigurationChangedEvent : EntityEventArgs
{
public AccessReaderConfigurationChangedEvent()
{
}
}

View File

@@ -3,6 +3,8 @@ using Content.Shared.Containers.ItemSlots;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Prototypes;
namespace Content.Shared.Access.Components;
@@ -27,10 +29,10 @@ public sealed partial class IdCardConsoleComponent : Component
{
public readonly string FullName;
public readonly string JobTitle;
public readonly List<string> AccessList;
public readonly string JobPrototype;
public readonly List<ProtoId<AccessLevelPrototype>> AccessList;
public readonly ProtoId<AccessLevelPrototype> JobPrototype;
public WriteToTargetIdMessage(string fullName, string jobTitle, List<string> accessList, string jobPrototype)
public WriteToTargetIdMessage(string fullName, string jobTitle, List<ProtoId<AccessLevelPrototype>> accessList, ProtoId<AccessLevelPrototype> jobPrototype)
{
FullName = fullName;
JobTitle = jobTitle;
@@ -86,18 +88,18 @@ public sealed partial class IdCardConsoleComponent : Component
public readonly string TargetIdName;
public readonly string? TargetIdFullName;
public readonly string? TargetIdJobTitle;
public readonly string[]? TargetIdAccessList;
public readonly string[]? AllowedModifyAccessList;
public readonly string TargetIdJobPrototype;
public readonly List<ProtoId<AccessLevelPrototype>>? TargetIdAccessList;
public readonly List<ProtoId<AccessLevelPrototype>>? AllowedModifyAccessList;
public readonly ProtoId<AccessLevelPrototype> TargetIdJobPrototype;
public IdCardConsoleBoundUserInterfaceState(bool isPrivilegedIdPresent,
bool isPrivilegedIdAuthorized,
bool isTargetIdPresent,
string? targetIdFullName,
string? targetIdJobTitle,
string[]? targetIdAccessList,
string[]? allowedModifyAccessList,
string targetIdJobPrototype,
List<ProtoId<AccessLevelPrototype>>? targetIdAccessList,
List<ProtoId<AccessLevelPrototype>>? allowedModifyAccessList,
ProtoId<AccessLevelPrototype> targetIdJobPrototype,
string privilegedIdName,
string targetIdName)
{

View File

@@ -112,11 +112,36 @@ public sealed class AccessReaderSystem : EntitySystem
return false;
}
public bool GetMainAccessReader(EntityUid uid, [NotNullWhen(true)] out AccessReaderComponent? component)
{
component = null;
if (!TryComp(uid, out AccessReaderComponent? accessReader))
return false;
component = accessReader;
if (component.ContainerAccessProvider == null)
return true;
if (!_containerSystem.TryGetContainer(uid, component.ContainerAccessProvider, out var container))
return true;
foreach (var entity in container.ContainedEntities)
{
if (TryComp(entity, out AccessReaderComponent? containedReader))
{
component = containedReader;
return true;
}
}
return true;
}
/// <summary>
/// Check whether the given access permissions satisfy an access reader's requirements.
/// </summary>
public bool IsAllowed(
ICollection<string> access,
ICollection<ProtoId<AccessLevelPrototype>> access,
ICollection<StationRecordKey> stationKeys,
EntityUid target,
AccessReaderComponent reader)
@@ -142,7 +167,7 @@ public sealed class AccessReaderSystem : EntitySystem
return false;
}
private bool IsAllowedInternal(ICollection<string> access, ICollection<StationRecordKey> stationKeys, AccessReaderComponent reader)
private bool IsAllowedInternal(ICollection<ProtoId<AccessLevelPrototype>> access, ICollection<StationRecordKey> stationKeys, AccessReaderComponent reader)
{
return !reader.Enabled
|| AreAccessTagsAllowed(access, reader)
@@ -154,7 +179,7 @@ public sealed class AccessReaderSystem : EntitySystem
/// </summary>
/// <param name="accessTags">A list of access tags</param>
/// <param name="reader">An access reader to check against</param>
public bool AreAccessTagsAllowed(ICollection<string> accessTags, AccessReaderComponent reader)
public bool AreAccessTagsAllowed(ICollection<ProtoId<AccessLevelPrototype>> accessTags, AccessReaderComponent reader)
{
if (reader.DenyTags.Overlaps(accessTags))
{
@@ -218,9 +243,9 @@ public sealed class AccessReaderSystem : EntitySystem
/// </summary>
/// <param name="uid">The entity that is being searched.</param>
/// <param name="items">All of the items to search for access. If none are passed in, <see cref="FindPotentialAccessItems"/> will be used.</param>
public ICollection<string> FindAccessTags(EntityUid uid, HashSet<EntityUid>? items = null)
public ICollection<ProtoId<AccessLevelPrototype>> FindAccessTags(EntityUid uid, HashSet<EntityUid>? items = null)
{
HashSet<string>? tags = null;
HashSet<ProtoId<AccessLevelPrototype>>? tags = null;
var owned = false;
items ??= FindPotentialAccessItems(uid);
@@ -230,7 +255,7 @@ public sealed class AccessReaderSystem : EntitySystem
FindAccessTagsItem(ent, ref tags, ref owned);
}
return (ICollection<string>?) tags ?? Array.Empty<string>();
return (ICollection<ProtoId<AccessLevelPrototype>>?) tags ?? Array.Empty<ProtoId<AccessLevelPrototype>>();
}
/// <summary>
@@ -260,7 +285,7 @@ public sealed class AccessReaderSystem : EntitySystem
/// 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).
/// </summary>
private void FindAccessTagsItem(EntityUid uid, ref HashSet<string>? tags, ref bool owned)
private void FindAccessTagsItem(EntityUid uid, ref HashSet<ProtoId<AccessLevelPrototype>>? tags, ref bool owned)
{
if (!FindAccessTagsItem(uid, out var targetTags))
{
@@ -286,6 +311,16 @@ public sealed class AccessReaderSystem : EntitySystem
}
}
public void SetAccesses(EntityUid uid, AccessReaderComponent component, List<ProtoId<AccessLevelPrototype>> accesses)
{
component.AccessLists.Clear();
foreach (var access in accesses)
{
component.AccessLists.Add(new HashSet<ProtoId<AccessLevelPrototype>>(){access});
}
RaiseLocalEvent(uid, new AccessReaderConfigurationChangedEvent());
}
public bool FindAccessItemsInventory(EntityUid uid, out HashSet<EntityUid> items)
{
items = new();
@@ -308,7 +343,7 @@ public sealed class AccessReaderSystem : EntitySystem
/// Try to find <see cref="AccessComponent"/> on this item
/// or inside this item (if it's pda)
/// </summary>
private bool FindAccessTagsItem(EntityUid uid, out HashSet<string> tags)
private bool FindAccessTagsItem(EntityUid uid, out HashSet<ProtoId<AccessLevelPrototype>> tags)
{
tags = new();
var ev = new GetAccessTagsEvent(tags, _prototype);

View File

@@ -51,7 +51,7 @@ namespace Content.Shared.Access.Systems
/// Replaces the set of access tags we have with the provided set.
/// </summary>
/// <param name="access">The new access tags</param>
public bool TrySetTags(EntityUid uid, IEnumerable<string> newTags, AccessComponent? access = null)
public bool TrySetTags(EntityUid uid, IEnumerable<ProtoId<AccessLevelPrototype>> newTags, AccessComponent? access = null)
{
if (!Resolve(uid, ref access))
return false;
@@ -67,12 +67,12 @@ namespace Content.Shared.Access.Systems
/// Gets the set of access tags.
/// </summary>
/// <param name="access">The new access tags</param>
public IEnumerable<string>? TryGetTags(EntityUid uid, AccessComponent? access = null)
public IEnumerable<ProtoId<AccessLevelPrototype>>? TryGetTags(EntityUid uid, AccessComponent? access = null)
{
return !Resolve(uid, ref access) ? null : access.Tags;
}
public bool TryAddGroups(EntityUid uid, IEnumerable<string> newGroups, AccessComponent? access = null)
public bool TryAddGroups(EntityUid uid, IEnumerable<ProtoId<AccessGroupPrototype>> newGroups, AccessComponent? access = null)
{
if (!Resolve(uid, ref access))
return false;

View File

@@ -1,6 +1,9 @@
using Content.Shared.Access.Components;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Prototypes;
namespace Content.Shared.Access.Systems
{
@@ -33,5 +36,16 @@ namespace Content.Shared.Access.Systems
_itemSlotsSystem.RemoveItemSlot(uid, component.PrivilegedIdSlot);
_itemSlotsSystem.RemoveItemSlot(uid, component.TargetIdSlot);
}
[Serializable, NetSerializable]
private sealed class IdCardConsoleComponentState : ComponentState
{
public List<string> AccessLevels;
public IdCardConsoleComponentState(List<string> accessLevels)
{
AccessLevels = accessLevels;
}
}
}
}

View File

@@ -0,0 +1,42 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Prototypes;
using Content.Shared.Access;
namespace Content.Shared.Doors.Electronics;
/// <summary>
/// Allows an entity's AccessReader to be configured via UI.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class DoorElectronicsComponent : Component
{
}
[Serializable, NetSerializable]
public sealed class DoorElectronicsUpdateConfigurationMessage : BoundUserInterfaceMessage
{
public List<ProtoId<AccessLevelPrototype>> AccessList;
public DoorElectronicsUpdateConfigurationMessage(List<ProtoId<AccessLevelPrototype>> accessList)
{
AccessList = accessList;
}
}
[Serializable, NetSerializable]
public sealed class DoorElectronicsConfigurationState : BoundUserInterfaceState
{
public List<ProtoId<AccessLevelPrototype>> AccessList;
public DoorElectronicsConfigurationState(List<ProtoId<AccessLevelPrototype>> accessList)
{
AccessList = accessList;
}
}
[Serializable, NetSerializable]
public enum DoorElectronicsConfigurationUiKey : byte
{
Key
}

View File

@@ -116,8 +116,8 @@ public sealed partial class NearbyAccessRule : RulesRule
[DataField("count")]
public int Count = 1;
[DataField("access", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<AccessLevelPrototype>))]
public List<string> Access = new();
[DataField("access", required: true)]
public List<ProtoId<AccessLevelPrototype>> Access = new();
[DataField("range")]
public float Range = 10f;

View File

@@ -105,17 +105,17 @@ namespace Content.Shared.Roles
[DataField("special", serverOnly: true)]
public JobSpecial[] Special { get; private set; } = Array.Empty<JobSpecial>();
[DataField("access", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessLevelPrototype>))]
public IReadOnlyCollection<string> Access { get; private set; } = Array.Empty<string>();
[DataField("access")]
public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> Access { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>();
[DataField("accessGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessGroupPrototype>))]
public IReadOnlyCollection<string> AccessGroups { get; private set; } = Array.Empty<string>();
[DataField("accessGroups")]
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> AccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
[DataField("extendedAccess", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessLevelPrototype>))]
public IReadOnlyCollection<string> ExtendedAccess { get; private set; } = Array.Empty<string>();
[DataField("extendedAccess")]
public IReadOnlyCollection<ProtoId<AccessLevelPrototype>> ExtendedAccess { get; private set; } = Array.Empty<ProtoId<AccessLevelPrototype>>();
[DataField("extendedAccessGroups", customTypeSerializer: typeof(PrototypeIdListSerializer<AccessGroupPrototype>))]
public IReadOnlyCollection<string> ExtendedAccessGroups { get; private set; } = Array.Empty<string>();
[DataField("extendedAccessGroups")]
public IReadOnlyCollection<ProtoId<AccessGroupPrototype>> ExtendedAccessGroups { get; private set; } = Array.Empty<ProtoId<AccessGroupPrototype>>();
}
/// <summary>

View File

@@ -7,9 +7,19 @@
- type: Sprite
sprite: Objects/Misc/module.rsi
state: door_electronics
- type: AccessReader
- type: Tag
tags:
- DoorElectronics
- type: DoorElectronics
- type: StaticPrice
price: 55
- type: AccessReader
- type: ActivatableUI
key: enum.DoorElectronicsConfigurationUiKey.Key
allowedItems:
tags:
- Multitool
- type: UserInterface
interfaces:
- key: enum.DoorElectronicsConfigurationUiKey.Key
type: DoorElectronicsBoundUserInterface

View File

@@ -0,0 +1,264 @@
- type: entity
parent: DoorElectronics
id: DoorElectronicsService
suffix: Service, Locked
components:
- type: AccessReader
access: [["Service"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsTheatre
suffix: Theatre, Locked
components:
- type: AccessReader
access: [["Theatre"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsChapel
suffix: Chapel, Locked
components:
- type: AccessReader
access: [["Chapel"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsJanitor
suffix: Janitor, Locked
components:
- type: AccessReader
access: [["Janitor"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsKitchen
suffix: Kitchen, Locked
components:
- type: AccessReader
access: [["Kitchen"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsBar
suffix: Bar, Locked
components:
- type: AccessReader
access: [["Bar"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsHydroponics
suffix: Hydroponics, Locked
components:
- type: AccessReader
access: [["Hydroponics"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsCaptain
suffix: Captain, Locked
components:
- type: AccessReader
access: [["Captain"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsExternal
suffix: External, Locked
components:
- type: AccessReader
access: [["External"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsCargo
suffix: Cargo, Locked
components:
- type: AccessReader
access: [["Cargo"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsEngineering
suffix: Engineering, Locked
components:
- type: AccessReader
access: [["Engineering"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsAtmospherics
suffix: Atmospherics, Locked
components:
- type: AccessReader
access: [["Atmospherics"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsFreezer
suffix: Freezer, Locked
components:
- type: AccessReader
access: [["Kitchen"], ["Hydroponics"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsSalvage
suffix: Salvage, Locked
components:
- type: AccessReader
access: [["Salvage"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsMedical
suffix: Medical, Locked
components:
- type: AccessReader
access: [["Medical"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsChemistry
suffix: Chemistry, Locked
components:
- type: AccessReader
access: [["Chemistry"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsResearch
suffix: Research, Locked
components:
- type: AccessReader
access: [["Research"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsScience
suffix: Science, Locked
components:
- type: AccessReader
access: [["Research"], ["Medical"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsCommand
suffix: Command, Locked
components:
- type: AccessReader
access: [["Command"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsChiefMedicalOfficer
suffix: ChiefMedicalOfficer, Locked
components:
- type: AccessReader
access: [["ChiefMedicalOfficer"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsChiefEngineer
suffix: ChiefEngineer, Locked
components:
- type: AccessReader
access: [["ChiefEngineer"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsHeadOfSecurity
suffix: HeadOfSecurity, Locked
components:
- type: AccessReader
access: [["HeadOfSecurity"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsResearchDirector
suffix: ResearchDirector, Locked
components:
- type: AccessReader
access: [["ResearchDirector"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsHeadOfPersonnel
suffix: HeadOfPersonnel, Locked
components:
- type: AccessReader
access: [["HeadOfPersonnel"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsQuartermaster
suffix: Quartermaster, Locked
components:
- type: AccessReader
access: [["Quartermaster"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsSecurity
suffix: Security, Locked
components:
- type: AccessReader
access: [["Security"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsDetective
suffix: Detective, Locked
components:
- type: AccessReader
access: [["Detective"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsBrig
suffix: Brig, Locked
components:
- type: AccessReader
access: [["Brig"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsArmory
suffix: Armory, Locked
components:
- type: AccessReader
access: [["Armory"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsVault
suffix: Vault, Locked
components:
- type: AccessReader
access: [["Security", "Command"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsMaintenance
suffix: Maintenance, Locked
components:
- type: AccessReader
access: [["Maintenance"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsSyndicateAgent
suffix: SyndicateAgent, Locked
components:
- type: AccessReader
access: [["SyndicateAgent"]]
- type: entity
parent: DoorElectronics
id: DoorElectronicsRnDMed
suffix: Medical/Science, Locked
components:
- type: AccessReader
access: [["Research"], ["Medical"]]

View File

@@ -48,6 +48,8 @@
- type: ContainerFill
containers:
board: [ DoorElectronics ]
- type: AccessReader
containerAccessProvider: board
- type: Door
crushDamage:
types:
@@ -140,7 +142,6 @@
- type: PaintableAirlock
group: Standard
department: Civilian
- type: AccessReader
- type: StaticPrice
price: 150
- type: LightningTarget

View File

@@ -60,6 +60,7 @@
- type: NavMapDoor
- type: DoorBolt
- type: AccessReader
containerAccessProvider: board
- type: Appearance
- type: WiresVisuals
- type: ApcPowerReceiver

View File

@@ -46,7 +46,7 @@
conditions:
- !type:EntityAnchored {}
steps:
- tag: DoorElectronics
- component: DoorElectronics
store: board
name: "door electronics circuit board"
icon:

View File

@@ -44,7 +44,7 @@
- !type:EntityAnchored
anchored: true
steps:
- tag: DoorElectronics
- component: DoorElectronics
name: Door Electronics
icon:
sprite: "Objects/Misc/module.rsi"

View File

@@ -23,7 +23,7 @@
conditions:
- !type:EntityAnchored {}
steps:
- tag: DoorElectronics
- component: DoorElectronics
store: board
name: "door electronics circuit board"
icon:

View File

@@ -107,7 +107,7 @@
conditions:
- !type:EntityAnchored {}
steps:
- tag: DoorElectronics
- component: DoorElectronics
store: board
name: "door electronics circuit board"
icon:
@@ -378,7 +378,7 @@
conditions:
- !type:EntityAnchored { }
steps:
- tag: DoorElectronics
- component: DoorElectronics
store: board
name: "door electronics circuit board"
icon: