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)
|
||||
{
|
||||
_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()
|
||||
|
||||
@@ -40,9 +40,6 @@ namespace Content.Server.GameTicking
|
||||
private int _roundStartFailCount = 0;
|
||||
#endif
|
||||
|
||||
[ViewVariables]
|
||||
private TimeSpan _roundStartTimeSpan;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _startingRound;
|
||||
|
||||
@@ -247,7 +244,7 @@ namespace Content.Server.GameTicking
|
||||
_roundStartDateTime = DateTime.UtcNow;
|
||||
RunLevel = GameRunLevel.InRound;
|
||||
|
||||
_roundStartTimeSpan = _gameTiming.CurTime;
|
||||
RoundStartTimeSpan = _gameTiming.CurTime;
|
||||
SendStatusToAll();
|
||||
ReqWindowAttentionAll();
|
||||
UpdateLateJoinStatus();
|
||||
@@ -595,7 +592,7 @@ namespace Content.Server.GameTicking
|
||||
|
||||
public TimeSpan RoundDuration()
|
||||
{
|
||||
return _gameTiming.CurTime.Subtract(_roundStartTimeSpan);
|
||||
return _gameTiming.CurTime.Subtract(RoundStartTimeSpan);
|
||||
}
|
||||
|
||||
private void AnnounceRound()
|
||||
|
||||
@@ -3,58 +3,57 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
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.
|
||||
/// True if the access provider is enabled and can grant access.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedAccessSystem))]
|
||||
[AutoGenerateComponentState]
|
||||
public sealed partial class AccessComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the access provider is enabled and can grant access.
|
||||
/// </summary>
|
||||
[DataField("enabled"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
[AutoNetworkedField]
|
||||
public bool Enabled = true;
|
||||
|
||||
[DataField("tags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||
[Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||
[AutoNetworkedField]
|
||||
public HashSet<string> Tags = new();
|
||||
|
||||
/// <summary>
|
||||
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
|
||||
/// </summary>
|
||||
[DataField("groups", readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<string> Groups = new();
|
||||
}
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||
[Access(typeof(SharedAccessSystem), Other = AccessPermissions.ReadExecute)] // FIXME Friends
|
||||
[AutoNetworkedField]
|
||||
public HashSet<string> Tags = new();
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an entity to find additional entities which provide access.
|
||||
/// Access Groups. These are added to the tags during map init. After map init this will have no effect.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct GetAdditionalAccessEvent
|
||||
[DataField(readOnly: true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessGroupPrototype>))]
|
||||
[AutoNetworkedField]
|
||||
public HashSet<string> Groups = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised on an entity to find additional entities which provide access.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public struct GetAdditionalAccessEvent
|
||||
{
|
||||
public HashSet<EntityUid> Entities = new();
|
||||
|
||||
public GetAdditionalAccessEvent()
|
||||
{
|
||||
public HashSet<EntityUid> Entities = new();
|
||||
|
||||
public GetAdditionalAccessEvent()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
|
||||
{
|
||||
public void AddGroup(string group)
|
||||
{
|
||||
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
|
||||
return;
|
||||
|
||||
Tags.UnionWith(groupPrototype.Tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public record struct GetAccessTagsEvent(HashSet<string> Tags, IPrototypeManager PrototypeManager)
|
||||
{
|
||||
public void AddGroup(string group)
|
||||
{
|
||||
if (!PrototypeManager.TryIndex<AccessGroupPrototype>(group, out var groupPrototype))
|
||||
return;
|
||||
|
||||
Tags.UnionWith(groupPrototype.Tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
|
||||
namespace Content.Shared.Access.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Stores access levels necessary to "use" an entity
|
||||
/// and allows checking if something or somebody is authorized with these access levels.
|
||||
/// Stores access levels necessary to "use" an entity
|
||||
/// and allows checking if something or somebody is authorized with these access levels.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class AccessReaderComponent : Component
|
||||
@@ -16,27 +16,28 @@ public sealed partial class AccessReaderComponent : Component
|
||||
/// Whether or not the accessreader is enabled.
|
||||
/// If not, it will always let people through.
|
||||
/// </summary>
|
||||
[DataField("enabled")]
|
||||
[DataField]
|
||||
public bool Enabled = true;
|
||||
|
||||
/// <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>
|
||||
[DataField("denyTags", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<AccessLevelPrototype>))]
|
||||
public HashSet<string> 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")]
|
||||
[DataField("access")] [ViewVariables(VVAccess.ReadWrite)]
|
||||
public List<HashSet<string>> AccessLists = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list of <see cref="StationRecordKey"/>s that grant access. Only a single matching key is required tp gaim
|
||||
/// access.
|
||||
/// </summary>
|
||||
[DataField("accessKeys")]
|
||||
[DataField]
|
||||
public HashSet<StationRecordKey> AccessKeys = new();
|
||||
|
||||
/// <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
|
||||
/// they all deny access.
|
||||
/// </remarks>
|
||||
[DataField("containerAccessProvider")]
|
||||
[DataField]
|
||||
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]
|
||||
public sealed class AccessReaderComponentState : ComponentState
|
||||
{
|
||||
@@ -63,11 +79,17 @@ public sealed class AccessReaderComponentState : ComponentState
|
||||
|
||||
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;
|
||||
DenyTags = denyTags;
|
||||
AccessLists = accessLists;
|
||||
AccessKeys = accessKeys;
|
||||
AccessLog = accessLog;
|
||||
AccessLogLimit = accessLogLimit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,4 +34,10 @@ public sealed partial class IdCardComponent : Component
|
||||
[DataField("jobDepartments")]
|
||||
[AutoNetworkedField]
|
||||
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 System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Access.Systems;
|
||||
|
||||
@@ -19,7 +21,10 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = 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 SharedIdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = 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)
|
||||
{
|
||||
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)
|
||||
@@ -57,6 +62,8 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
|
||||
component.AccessLists = new(state.AccessLists);
|
||||
component.DenyTags = new(state.DenyTags);
|
||||
component.AccessLog = new(state.AccessLog);
|
||||
component.AccessLogLimit = state.AccessLogLimit;
|
||||
}
|
||||
|
||||
private void OnLinkAttempt(EntityUid uid, AccessReaderComponent component, LinkAttemptEvent args)
|
||||
@@ -71,6 +78,7 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
{
|
||||
args.Handled = true;
|
||||
reader.Enabled = false;
|
||||
reader.AccessLog.Clear();
|
||||
Dirty(uid, reader);
|
||||
}
|
||||
|
||||
@@ -93,7 +101,13 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
var access = FindAccessTags(user, 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>
|
||||
@@ -326,4 +340,27 @@ public sealed class AccessReaderSystem : EntitySystem
|
||||
key = null;
|
||||
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.
|
||||
[ViewVariables]
|
||||
public int RoundId { get; protected set; }
|
||||
[ViewVariables] public TimeSpan RoundStartTimeSpan { get; protected set; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -188,4 +189,3 @@ namespace Content.Shared.GameTicking
|
||||
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-frequency = Frequency
|
||||
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-cyborgs = Cyborgs
|
||||
guide-entry-security = Security
|
||||
guide-entry-dna = DNA
|
||||
guide-entry-forensics = Forensics
|
||||
guide-entry-defusal = Large Bomb Defusal
|
||||
|
||||
guide-entry-antagonists = Antagonists
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
- id: ClothingOuterCoatDetective
|
||||
- id: FlashlightSeclite
|
||||
- id: ForensicScanner
|
||||
- id: LogProbeCartridge
|
||||
- id: BoxForensicPad
|
||||
- id: DrinkDetFlask
|
||||
- id: ClothingHandsGlovesForensic
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
- type: FingerprintMask
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- DNA
|
||||
- Forensics
|
||||
|
||||
# TODO Make lubed items not slip in hands
|
||||
- type: entity
|
||||
|
||||
@@ -70,4 +70,25 @@
|
||||
state: server
|
||||
- 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: GuideHelp
|
||||
guides:
|
||||
- DNA
|
||||
- Forensics
|
||||
- type: StealTarget
|
||||
stealGroup: ForensicScanner
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
maxWritableArea: 368.0, 256.0
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- DNA
|
||||
- Forensics
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
- Document
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- DNA
|
||||
- Forensics
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
board: StationRecordsComputerCircuitboard
|
||||
- type: GuideHelp
|
||||
guides:
|
||||
- DNA
|
||||
- Forensics
|
||||
|
||||
- type: entity
|
||||
parent: BaseComputer
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
name: guide-entry-security
|
||||
text: "/ServerInfo/Guidebook/Security/Security.xml"
|
||||
children:
|
||||
- DNA
|
||||
- Forensics
|
||||
- Defusal
|
||||
|
||||
- type: guideEntry
|
||||
id: DNA
|
||||
name: guide-entry-dna
|
||||
text: "/ServerInfo/Guidebook/Security/DNA.xml"
|
||||
id: Forensics
|
||||
name: guide-entry-forensics
|
||||
text: "/ServerInfo/Guidebook/Security/Forensics.xml"
|
||||
|
||||
- type: guideEntry
|
||||
id: Defusal
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
<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
|
||||
|
||||
## 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,
|
||||
"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": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
@@ -72,6 +72,9 @@
|
||||
},
|
||||
{
|
||||
"name": "cart-y"
|
||||
},
|
||||
{
|
||||
"name": "cart-log"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user