Add access logs (IC ones) (#17810)
This commit is contained in:
28
Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
Normal file
28
Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Client.UserInterface.Fragments;
|
||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
public sealed partial class LogProbeUi : UIFragment
|
||||||
|
{
|
||||||
|
private LogProbeUiFragment? _fragment;
|
||||||
|
|
||||||
|
public override Control GetUIFragmentRoot()
|
||||||
|
{
|
||||||
|
return _fragment!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||||
|
{
|
||||||
|
_fragment = new LogProbeUiFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
if (state is not LogProbeUiState logProbeUiState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_fragment?.UpdateState(logProbeUiState.PulledLogs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<BoxContainer xmlns="https://spacestation14.io"
|
||||||
|
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||||
|
Margin="4"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Name="NumberLabel"
|
||||||
|
Align="Center"
|
||||||
|
SetWidth="60"
|
||||||
|
ClipText="True"/>
|
||||||
|
<Label Name="TimeLabel"
|
||||||
|
Align="Center"
|
||||||
|
SetWidth="280"
|
||||||
|
ClipText="True"/>
|
||||||
|
<Label Name="AccessorLabel"
|
||||||
|
Align="Center"
|
||||||
|
SetWidth="110"
|
||||||
|
ClipText="True"/>
|
||||||
|
</BoxContainer>
|
||||||
|
<customControls:HSeparator Margin="0 5 0 5"/>
|
||||||
|
</BoxContainer>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class LogProbeUiEntry : BoxContainer
|
||||||
|
{
|
||||||
|
public LogProbeUiEntry(int numberLabel, string timeText, string accessorText)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
NumberLabel.Text = numberLabel.ToString();
|
||||||
|
TimeLabel.Text = timeText;
|
||||||
|
AccessorLabel.Text = accessorText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<cartridges:LogProbeUiFragment xmlns="https://spacestation14.io"
|
||||||
|
xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||||
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
|
Orientation="Vertical"
|
||||||
|
VerticalExpand="True">
|
||||||
|
<PanelContainer>
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BackgroundColor="#000000FF"
|
||||||
|
BorderColor="#5a5a5a"
|
||||||
|
BorderThickness="0 0 0 1"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Orientation="Horizontal" Align="Center" Margin="8">
|
||||||
|
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-number'}"/>
|
||||||
|
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-time'}"/>
|
||||||
|
<Label HorizontalExpand="True" Text="{Loc 'log-probe-label-accessor'}"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
<ScrollContainer VerticalExpand="True" HScrollEnabled="True">
|
||||||
|
<BoxContainer Orientation="Vertical" Name="ProbedDeviceContainer"/>
|
||||||
|
</ScrollContainer>
|
||||||
|
</cartridges:LogProbeUiFragment>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class LogProbeUiFragment : BoxContainer
|
||||||
|
{
|
||||||
|
public LogProbeUiFragment()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(List<PulledAccessLog> logs)
|
||||||
|
{
|
||||||
|
ProbedDeviceContainer.RemoveAllChildren();
|
||||||
|
|
||||||
|
//Reverse the list so the oldest entries appear at the bottom
|
||||||
|
logs.Reverse();
|
||||||
|
|
||||||
|
var count = 1;
|
||||||
|
foreach (var log in logs)
|
||||||
|
{
|
||||||
|
AddAccessLog(log, count);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAccessLog(PulledAccessLog log, int numberLabelText)
|
||||||
|
{
|
||||||
|
var timeLabelText = TimeSpan.FromSeconds(Math.Truncate(log.Time.TotalSeconds)).ToString();
|
||||||
|
var accessorLabelText = log.Accessor;
|
||||||
|
var entry = new LogProbeUiEntry(numberLabelText, timeLabelText, accessorLabelText);
|
||||||
|
|
||||||
|
ProbedDeviceContainer.AddChild(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
|
||||||
|
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
[Access(typeof(LogProbeCartridgeSystem))]
|
||||||
|
public sealed partial class LogProbeCartridgeComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of pulled access logs
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables]
|
||||||
|
public List<PulledAccessLog> PulledAccessLogs = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sound to make when we scan something with access
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public SoundSpecifier SoundScan = new SoundPathSpecifier("/Audio/Machines/scan_finish.ogg");
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Content.Shared.Access.Components;
|
||||||
|
using Content.Shared.Audio;
|
||||||
|
using Content.Shared.CartridgeLoader;
|
||||||
|
using Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
using Content.Shared.Popups;
|
||||||
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
public sealed class LogProbeCartridgeSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly CartridgeLoaderSystem? _cartridgeLoaderSystem = default!;
|
||||||
|
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
SubscribeLocalEvent<LogProbeCartridgeComponent, CartridgeUiReadyEvent>(OnUiReady);
|
||||||
|
SubscribeLocalEvent<LogProbeCartridgeComponent, CartridgeAfterInteractEvent>(AfterInteract);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="CartridgeAfterInteractEvent" /> gets relayed to this system if the cartridge loader is running
|
||||||
|
/// the LogProbe program and someone clicks on something with it. <br/>
|
||||||
|
/// <br/>
|
||||||
|
/// Updates the program's list of logs with those from the device.
|
||||||
|
/// </summary>
|
||||||
|
private void AfterInteract(Entity<LogProbeCartridgeComponent> ent, ref CartridgeAfterInteractEvent args)
|
||||||
|
{
|
||||||
|
if (args.InteractEvent.Handled || !args.InteractEvent.CanReach || args.InteractEvent.Target is not { } target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp(target, out AccessReaderComponent? accessReaderComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Play scanning sound with slightly randomized pitch
|
||||||
|
_audioSystem.PlayEntity(ent.Comp.SoundScan, args.InteractEvent.User, target, AudioHelpers.WithVariation(0.25f, _random));
|
||||||
|
_popupSystem.PopupCursor(Loc.GetString("log-probe-scan", ("device", target)), args.InteractEvent.User);
|
||||||
|
|
||||||
|
ent.Comp.PulledAccessLogs.Clear();
|
||||||
|
|
||||||
|
foreach (var accessRecord in accessReaderComponent.AccessLog)
|
||||||
|
{
|
||||||
|
var log = new PulledAccessLog(
|
||||||
|
accessRecord.AccessTime,
|
||||||
|
accessRecord.Accessor
|
||||||
|
);
|
||||||
|
|
||||||
|
ent.Comp.PulledAccessLogs.Add(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateUiState(ent, args.Loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This gets called when the ui fragment needs to be updated for the first time after activating
|
||||||
|
/// </summary>
|
||||||
|
private void OnUiReady(Entity<LogProbeCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
|
||||||
|
{
|
||||||
|
UpdateUiState(ent, args.Loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateUiState(Entity<LogProbeCartridgeComponent> ent, EntityUid loaderUid)
|
||||||
|
{
|
||||||
|
var state = new LogProbeUiState(ent.Comp.PulledAccessLogs);
|
||||||
|
_cartridgeLoaderSystem?.UpdateCartridgeUiState(loaderUid, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ namespace Content.Server.GameTicking
|
|||||||
private TickerLobbyStatusEvent GetStatusMsg(ICommonSession session)
|
private TickerLobbyStatusEvent GetStatusMsg(ICommonSession session)
|
||||||
{
|
{
|
||||||
_playerGameStatuses.TryGetValue(session.UserId, out var status);
|
_playerGameStatuses.TryGetValue(session.UserId, out var status);
|
||||||
return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, LobbyBackground,status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, _roundStartTimeSpan, Paused);
|
return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, LobbyBackground,status == PlayerGameStatus.ReadyToPlay, _roundStartTime, RoundPreloadTime, RoundStartTimeSpan, Paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendStatusToAll()
|
private void SendStatusToAll()
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ namespace Content.Server.GameTicking
|
|||||||
private int _roundStartFailCount = 0;
|
private int _roundStartFailCount = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[ViewVariables]
|
|
||||||
private TimeSpan _roundStartTimeSpan;
|
|
||||||
|
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private bool _startingRound;
|
private bool _startingRound;
|
||||||
|
|
||||||
@@ -247,7 +244,7 @@ namespace Content.Server.GameTicking
|
|||||||
_roundStartDateTime = DateTime.UtcNow;
|
_roundStartDateTime = DateTime.UtcNow;
|
||||||
RunLevel = GameRunLevel.InRound;
|
RunLevel = GameRunLevel.InRound;
|
||||||
|
|
||||||
_roundStartTimeSpan = _gameTiming.CurTime;
|
RoundStartTimeSpan = _gameTiming.CurTime;
|
||||||
SendStatusToAll();
|
SendStatusToAll();
|
||||||
ReqWindowAttentionAll();
|
ReqWindowAttentionAll();
|
||||||
UpdateLateJoinStatus();
|
UpdateLateJoinStatus();
|
||||||
@@ -595,7 +592,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
public TimeSpan RoundDuration()
|
public TimeSpan RoundDuration()
|
||||||
{
|
{
|
||||||
return _gameTiming.CurTime.Subtract(_roundStartTimeSpan);
|
return _gameTiming.CurTime.Subtract(RoundStartTimeSpan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AnnounceRound()
|
private void AnnounceRound()
|
||||||
|
|||||||
@@ -3,24 +3,24 @@ using Robust.Shared.GameStates;
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
|
||||||
|
|
||||||
namespace Content.Shared.Access.Components
|
namespace Content.Shared.Access.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple mutable access provider found on ID cards and such.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent]
|
||||||
|
[Access(typeof(SharedAccessSystem))]
|
||||||
|
[AutoGenerateComponentState]
|
||||||
|
public sealed partial class AccessComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Simple mutable access provider found on ID cards and such.
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent, NetworkedComponent]
|
|
||||||
[Access(typeof(SharedAccessSystem))]
|
|
||||||
[AutoGenerateComponentState]
|
|
||||||
public sealed partial class AccessComponent : Component
|
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if the access provider is enabled and can grant access.
|
/// True if the access provider is enabled and can grant access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
public bool Enabled = true;
|
public bool Enabled = true;
|
||||||
|
|
||||||
[DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||||
[Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
[Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
public HashSet<string> Tags = new();
|
public HashSet<string> Tags = new();
|
||||||
@@ -28,27 +28,27 @@ namespace Content.Shared.Access.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
|
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("groups", readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
[DataField(readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
public HashSet<string> Groups = new();
|
public HashSet<string> Groups = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event raised on an entity to find additional entities which provide access.
|
/// Event raised on an entity to find additional entities which provide access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public struct GetAdditionalAccessEvent
|
public struct GetAdditionalAccessEvent
|
||||||
{
|
{
|
||||||
public HashSet<EntityUid> Entities = new();
|
public HashSet<EntityUid> Entities = new();
|
||||||
|
|
||||||
public GetAdditionalAccessEvent()
|
public GetAdditionalAccessEvent()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
|
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
|
||||||
{
|
{
|
||||||
public void AddGroup(string group)
|
public void AddGroup(string group)
|
||||||
{
|
{
|
||||||
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
|
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
|
||||||
@@ -56,5 +56,4 @@ namespace Content.Shared.Access.Components
|
|||||||
|
|
||||||
Tags.UnionWith(groupPrototype.Tags);
|
Tags.UnionWith(groupPrototype.Tags);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,27 +16,28 @@ public sealed partial class AccessReaderComponent : Component
|
|||||||
/// Whether or not the accessreader is enabled.
|
/// Whether or not the accessreader is enabled.
|
||||||
/// If not, it will always let people through.
|
/// If not, it will always let people through.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("enabled")]
|
[DataField]
|
||||||
public bool Enabled = true;
|
public bool Enabled = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The set of tags that will automatically deny an allowed check, if any of them are present.
|
/// The set of tags that will automatically deny an allowed check, if any of them are present.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("denyTags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||||
public HashSet<string> DenyTags = new();
|
public HashSet<string> DenyTags = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of access groups that grant access to this reader. Only a single matching group is required to gain access.
|
/// 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.
|
/// A group matches if it is a subset of the set being checked against.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("access")]
|
[DataField("access")] [ViewVariables(VVAccess.ReadWrite)]
|
||||||
public List<HashSet<string>> AccessLists = new();
|
public List<HashSet<string>> AccessLists = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of <see cref="StationRecordKey"/>s that grant access. Only a single matching key is required tp gaim
|
/// A list of <see cref="StationRecordKey"/>s that grant access. Only a single matching key is required tp gaim
|
||||||
/// access.
|
/// access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("accessKeys")]
|
[DataField]
|
||||||
public HashSet<StationRecordKey> AccessKeys = new();
|
public HashSet<StationRecordKey> AccessKeys = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -48,10 +49,25 @@ public sealed partial class AccessReaderComponent : Component
|
|||||||
/// ignored, though <see cref="Enabled"/> is still respected. Access is denied if there are no valid entities or
|
/// ignored, though <see cref="Enabled"/> is still respected. Access is denied if there are no valid entities or
|
||||||
/// they all deny access.
|
/// they all deny access.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("containerAccessProvider")]
|
[DataField]
|
||||||
public string? ContainerAccessProvider;
|
public string? ContainerAccessProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of past authentications
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public Queue<AccessRecord> AccessLog = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A limit on the max size of <see cref="AccessLog"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public int AccessLogLimit = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public record struct AccessRecord(TimeSpan AccessTime, string Accessor);
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class AccessReaderComponentState : ComponentState
|
public sealed class AccessReaderComponentState : ComponentState
|
||||||
{
|
{
|
||||||
@@ -63,11 +79,17 @@ public sealed class AccessReaderComponentState : ComponentState
|
|||||||
|
|
||||||
public List<(NetEntity, uint)> AccessKeys;
|
public List<(NetEntity, uint)> AccessKeys;
|
||||||
|
|
||||||
public AccessReaderComponentState(bool enabled, HashSet<string> denyTags, List<HashSet<string>> accessLists, List<(NetEntity, uint)> accessKeys)
|
public Queue<AccessRecord> AccessLog;
|
||||||
|
|
||||||
|
public int AccessLogLimit;
|
||||||
|
|
||||||
|
public AccessReaderComponentState(bool enabled, HashSet<string> denyTags, List<HashSet<string>> accessLists, List<(NetEntity, uint)> accessKeys, Queue<AccessRecord> accessLog, int accessLogLimit)
|
||||||
{
|
{
|
||||||
Enabled = enabled;
|
Enabled = enabled;
|
||||||
DenyTags = denyTags;
|
DenyTags = denyTags;
|
||||||
AccessLists = accessLists;
|
AccessLists = accessLists;
|
||||||
AccessKeys = accessKeys;
|
AccessKeys = accessKeys;
|
||||||
|
AccessLog = accessLog;
|
||||||
|
AccessLogLimit = accessLogLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,10 @@ public sealed partial class IdCardComponent : Component
|
|||||||
[DataField("jobDepartments")]
|
[DataField("jobDepartments")]
|
||||||
[AutoNetworkedField]
|
[AutoNetworkedField]
|
||||||
public List<LocId> JobDepartments = new();
|
public List<LocId> JobDepartments = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if accesses from this card should be logged by <see cref="AccessReaderComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool BypassLogging;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ using Robust.Shared.Containers;
|
|||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.GameTicking;
|
||||||
using Robust.Shared.Collections;
|
using Robust.Shared.Collections;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Shared.Access.Systems;
|
namespace Content.Shared.Access.Systems;
|
||||||
|
|
||||||
@@ -19,7 +21,10 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
[Dependency] private readonly InventorySystem _inventorySystem = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||||
|
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||||
|
[Dependency] private readonly SharedIdCardSystem _idCardSystem = default!;
|
||||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||||
[Dependency] private readonly SharedStationRecordsSystem _records = default!;
|
[Dependency] private readonly SharedStationRecordsSystem _records = default!;
|
||||||
|
|
||||||
@@ -37,7 +42,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
private void OnGetState(EntityUid uid, AccessReaderComponent component, ref ComponentGetState args)
|
private void OnGetState(EntityUid uid, AccessReaderComponent component, ref ComponentGetState args)
|
||||||
{
|
{
|
||||||
args.State = new AccessReaderComponentState(component.Enabled, component.DenyTags, component.AccessLists,
|
args.State = new AccessReaderComponentState(component.Enabled, component.DenyTags, component.AccessLists,
|
||||||
_records.Convert(component.AccessKeys));
|
_records.Convert(component.AccessKeys), component.AccessLog, component.AccessLogLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnHandleState(EntityUid uid, AccessReaderComponent component, ref ComponentHandleState args)
|
private void OnHandleState(EntityUid uid, AccessReaderComponent component, ref ComponentHandleState args)
|
||||||
@@ -57,6 +62,8 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
|
|
||||||
component.AccessLists = new(state.AccessLists);
|
component.AccessLists = new(state.AccessLists);
|
||||||
component.DenyTags = new(state.DenyTags);
|
component.DenyTags = new(state.DenyTags);
|
||||||
|
component.AccessLog = new(state.AccessLog);
|
||||||
|
component.AccessLogLimit = state.AccessLogLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnLinkAttempt(EntityUid uid, AccessReaderComponent component, LinkAttemptEvent args)
|
private void OnLinkAttempt(EntityUid uid, AccessReaderComponent component, LinkAttemptEvent args)
|
||||||
@@ -71,6 +78,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
reader.Enabled = false;
|
reader.Enabled = false;
|
||||||
|
reader.AccessLog.Clear();
|
||||||
Dirty(uid, reader);
|
Dirty(uid, reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +101,13 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
var access = FindAccessTags(user, accessSources);
|
var access = FindAccessTags(user, accessSources);
|
||||||
FindStationRecordKeys(user, out var stationKeys, accessSources);
|
FindStationRecordKeys(user, out var stationKeys, accessSources);
|
||||||
|
|
||||||
return IsAllowed(access, stationKeys, target, reader);
|
if (IsAllowed(access, stationKeys, target, reader))
|
||||||
|
{
|
||||||
|
LogAccess((target, reader), user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -326,4 +340,27 @@ public sealed class AccessReaderSystem : EntitySystem
|
|||||||
key = null;
|
key = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs an access
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ent">The reader to log the access on</param>
|
||||||
|
/// <param name="accessor">The accessor to log</param>
|
||||||
|
private void LogAccess(Entity<AccessReaderComponent> ent, EntityUid accessor)
|
||||||
|
{
|
||||||
|
if (ent.Comp.AccessLog.Count >= ent.Comp.AccessLogLimit)
|
||||||
|
ent.Comp.AccessLog.Dequeue();
|
||||||
|
|
||||||
|
string? name = null;
|
||||||
|
// TODO pass the ID card on IsAllowed() instead of using this expensive method
|
||||||
|
// Set name if the accessor has a card and that card has a name and allows itself to be recorded
|
||||||
|
if (_idCardSystem.TryFindIdCard(accessor, out var idCard)
|
||||||
|
&& idCard.Comp is { BypassLogging: false, FullName: not null })
|
||||||
|
name = idCard.Comp.FullName;
|
||||||
|
|
||||||
|
name ??= Loc.GetString("access-reader-unknown-id");
|
||||||
|
|
||||||
|
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
|
||||||
|
ent.Comp.AccessLog.Enqueue(new AccessRecord(stationTime, name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
Content.Shared/CartridgeLoader/Cartridges/LogProbeUiState.cs
Normal file
30
Content.Shared/CartridgeLoader/Cartridges/LogProbeUiState.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.CartridgeLoader.Cartridges;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class LogProbeUiState : BoundUserInterfaceState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of probed network devices
|
||||||
|
/// </summary>
|
||||||
|
public List<PulledAccessLog> PulledLogs;
|
||||||
|
|
||||||
|
public LogProbeUiState(List<PulledAccessLog> pulledLogs)
|
||||||
|
{
|
||||||
|
PulledLogs = pulledLogs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable, DataRecord]
|
||||||
|
public sealed class PulledAccessLog
|
||||||
|
{
|
||||||
|
public readonly TimeSpan Time;
|
||||||
|
public readonly string Accessor;
|
||||||
|
|
||||||
|
public PulledAccessLog(TimeSpan time, string accessor)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
Accessor = accessor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ namespace Content.Shared.GameTicking
|
|||||||
// Probably most useful for replays, round end info, and probably things like lobby menus.
|
// Probably most useful for replays, round end info, and probably things like lobby menus.
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int RoundId { get; protected set; }
|
public int RoundId { get; protected set; }
|
||||||
|
[ViewVariables] public TimeSpan RoundStartTimeSpan { get; protected set; }
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -188,4 +189,3 @@ namespace Content.Shared.GameTicking
|
|||||||
JoinedGame,
|
JoinedGame,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
access-reader-unknown-id = Unknown
|
||||||
@@ -11,3 +11,9 @@ net-probe-label-name = Name
|
|||||||
net-probe-label-address = Address
|
net-probe-label-address = Address
|
||||||
net-probe-label-frequency = Frequency
|
net-probe-label-frequency = Frequency
|
||||||
net-probe-label-network = Network
|
net-probe-label-network = Network
|
||||||
|
|
||||||
|
log-probe-program-name = LogProbe
|
||||||
|
log-probe-scan = Downloaded logs from {$device}!
|
||||||
|
log-probe-label-time = Time
|
||||||
|
log-probe-label-accessor = Accessed by
|
||||||
|
log-probe-label-number = Number
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ guide-entry-machine-upgrading = Machine Upgrading
|
|||||||
guide-entry-robotics = Robotics
|
guide-entry-robotics = Robotics
|
||||||
guide-entry-cyborgs = Cyborgs
|
guide-entry-cyborgs = Cyborgs
|
||||||
guide-entry-security = Security
|
guide-entry-security = Security
|
||||||
guide-entry-dna = DNA
|
guide-entry-forensics = Forensics
|
||||||
guide-entry-defusal = Large Bomb Defusal
|
guide-entry-defusal = Large Bomb Defusal
|
||||||
|
|
||||||
guide-entry-antagonists = Antagonists
|
guide-entry-antagonists = Antagonists
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
- id: ClothingOuterCoatDetective
|
- id: ClothingOuterCoatDetective
|
||||||
- id: FlashlightSeclite
|
- id: FlashlightSeclite
|
||||||
- id: ForensicScanner
|
- id: ForensicScanner
|
||||||
|
- id: LogProbeCartridge
|
||||||
- id: BoxForensicPad
|
- id: BoxForensicPad
|
||||||
- id: DrinkDetFlask
|
- id: DrinkDetFlask
|
||||||
- id: ClothingHandsGlovesForensic
|
- id: ClothingHandsGlovesForensic
|
||||||
|
|||||||
@@ -390,7 +390,7 @@
|
|||||||
- type: FingerprintMask
|
- type: FingerprintMask
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- DNA
|
- Forensics
|
||||||
|
|
||||||
# TODO Make lubed items not slip in hands
|
# TODO Make lubed items not slip in hands
|
||||||
- type: entity
|
- type: entity
|
||||||
|
|||||||
@@ -70,4 +70,25 @@
|
|||||||
state: server
|
state: server
|
||||||
- type: NetProbeCartridge
|
- type: NetProbeCartridge
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
parent: BaseItem
|
||||||
|
id: LogProbeCartridge
|
||||||
|
name: LogProbe cartridge
|
||||||
|
description: A program for getting access logs from devices
|
||||||
|
components:
|
||||||
|
- type: Sprite
|
||||||
|
sprite: Objects/Devices/cartridge.rsi
|
||||||
|
state: cart-log
|
||||||
|
- type: Icon
|
||||||
|
sprite: Objects/Devices/cartridge.rsi
|
||||||
|
state: cart-log
|
||||||
|
- type: UIFragment
|
||||||
|
ui: !type:LogProbeUi
|
||||||
|
- type: Cartridge
|
||||||
|
programName: log-probe-program-name
|
||||||
|
icon:
|
||||||
|
sprite: Structures/Doors/Airlocks/Standard/security.rsi
|
||||||
|
state: closed
|
||||||
|
- type: LogProbeCartridge
|
||||||
|
guides:
|
||||||
|
- Forensics
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
- type: ForensicScanner
|
- type: ForensicScanner
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- DNA
|
- Forensics
|
||||||
- type: StealTarget
|
- type: StealTarget
|
||||||
stealGroup: ForensicScanner
|
stealGroup: ForensicScanner
|
||||||
|
|
||||||
@@ -55,4 +55,4 @@
|
|||||||
maxWritableArea: 368.0, 256.0
|
maxWritableArea: 368.0, 256.0
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- DNA
|
- Forensics
|
||||||
|
|||||||
@@ -17,4 +17,4 @@
|
|||||||
- Document
|
- Document
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- DNA
|
- Forensics
|
||||||
|
|||||||
@@ -321,7 +321,7 @@
|
|||||||
board: StationRecordsComputerCircuitboard
|
board: StationRecordsComputerCircuitboard
|
||||||
- type: GuideHelp
|
- type: GuideHelp
|
||||||
guides:
|
guides:
|
||||||
- DNA
|
- Forensics
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
parent: BaseComputer
|
parent: BaseComputer
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
name: guide-entry-security
|
name: guide-entry-security
|
||||||
text: "/ServerInfo/Guidebook/Security/Security.xml"
|
text: "/ServerInfo/Guidebook/Security/Security.xml"
|
||||||
children:
|
children:
|
||||||
- DNA
|
- Forensics
|
||||||
- Defusal
|
- Defusal
|
||||||
|
|
||||||
- type: guideEntry
|
- type: guideEntry
|
||||||
id: DNA
|
id: Forensics
|
||||||
name: guide-entry-dna
|
name: guide-entry-forensics
|
||||||
text: "/ServerInfo/Guidebook/Security/DNA.xml"
|
text: "/ServerInfo/Guidebook/Security/Forensics.xml"
|
||||||
|
|
||||||
- type: guideEntry
|
- type: guideEntry
|
||||||
id: Defusal
|
id: Defusal
|
||||||
|
|||||||
@@ -1,4 +1,21 @@
|
|||||||
<Document>
|
<Document>
|
||||||
|
# Forensics
|
||||||
|
|
||||||
|
There are a lot of tools to help you gather and examine the evidence at your disposal
|
||||||
|
|
||||||
|
# Log probe
|
||||||
|
|
||||||
|
This little add-on to your PDA is incredibly useful, just install the cartridge and your PDA will acquire the ability to scan anything with access (like airlocks) and see who has used them recently.
|
||||||
|
|
||||||
|
You can normally find it inside the detective locker. After inserting it on your PDA, go to the programs tab and the log probe application should be there, to use the application you just have to interact with anything that requires access with your PDA while the application is open and the information will be instantly displayed in it.
|
||||||
|
|
||||||
|
It should be noted that the name shown in the application is not to be trusted 100% of the time since it gets the name from the identification card of whoever used the thing we are scanning, so if for example someone opened an airlock with no card the application would display "Unknown" as the name.
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<GuideEntityEmbed Entity="LockerDetective"/>
|
||||||
|
<GuideEntityEmbed Entity="LogProbeCartridge"/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
# DNA and Fingerprints
|
# DNA and Fingerprints
|
||||||
|
|
||||||
## How to get someone’s DNA?
|
## How to get someone’s DNA?
|
||||||
BIN
Resources/Textures/Objects/Devices/cartridge.rsi/cart-log.png
Normal file
BIN
Resources/Textures/Objects/Devices/cartridge.rsi/cart-log.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 298 B |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"license": "CC-BY-SA-3.0",
|
"license": "CC-BY-SA-3.0",
|
||||||
"copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d",
|
"copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d , cart-log made by Skarletto (github)",
|
||||||
"size": {
|
"size": {
|
||||||
"x": 32,
|
"x": 32,
|
||||||
"y": 32
|
"y": 32
|
||||||
@@ -72,6 +72,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "cart-y"
|
"name": "cart-y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cart-log"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user