2023-12-28 20:32:46 -04:00
using System.Diagnostics.CodeAnalysis ;
using System.Linq ;
2021-12-30 22:56:10 +01:00
using Content.Shared.Access.Components ;
2023-05-07 08:07:24 +02:00
using Content.Shared.DeviceLinking.Events ;
2023-07-27 22:25:55 +02:00
using Content.Shared.Emag.Systems ;
2025-06-05 18:28:55 -05:00
using Content.Shared.GameTicking ;
2022-03-17 20:13:31 +13:00
using Content.Shared.Hands.EntitySystems ;
2025-06-05 18:28:55 -05:00
using Content.Shared.IdentityManagement ;
2023-07-27 22:25:55 +02:00
using Content.Shared.Inventory ;
2024-06-03 20:48:44 +02:00
using Content.Shared.NameIdentifier ;
2023-07-27 22:25:55 +02:00
using Content.Shared.PDA ;
2023-02-28 11:03:55 -05:00
using Content.Shared.StationRecords ;
2025-04-04 00:24:30 +02:00
using Content.Shared.Tag ;
2025-06-05 18:28:55 -05:00
using Robust.Shared.Containers ;
2023-08-14 19:34:23 -04:00
using Robust.Shared.Collections ;
2025-06-05 18:28:55 -05:00
using Robust.Shared.GameStates ;
2023-08-12 17:39:58 -04:00
using Robust.Shared.Prototypes ;
2023-12-26 16:24:53 -06:00
using Robust.Shared.Timing ;
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
namespace Content.Shared.Access.Systems ;
public sealed class AccessReaderSystem : EntitySystem
2021-10-22 05:31:07 +03:00
{
2023-08-12 17:39:58 -04:00
[Dependency] private readonly IPrototypeManager _prototype = default ! ;
2023-07-22 21:19:51 -07:00
[Dependency] private readonly InventorySystem _inventorySystem = default ! ;
2023-12-26 16:24:53 -06:00
[Dependency] private readonly IGameTiming _gameTiming = default ! ;
2025-01-30 05:05:47 +01:00
[Dependency] private readonly EmagSystem _emag = default ! ;
2025-04-04 00:24:30 +02:00
[Dependency] private readonly TagSystem _tag = default ! ;
2023-12-26 16:24:53 -06:00
[Dependency] private readonly SharedGameTicker _gameTicker = default ! ;
2023-07-22 21:19:51 -07:00
[Dependency] private readonly SharedHandsSystem _handsSystem = default ! ;
2023-07-26 05:34:08 +00:00
[Dependency] private readonly SharedContainerSystem _containerSystem = default ! ;
2023-12-28 20:32:46 -04:00
[Dependency] private readonly SharedStationRecordsSystem _recordsSystem = default ! ;
2023-07-22 21:19:51 -07:00
2025-04-04 00:24:30 +02:00
private static readonly ProtoId < TagPrototype > PreventAccessLoggingTag = "PreventAccessLogging" ;
2023-07-22 21:19:51 -07:00
public override void Initialize ( )
2021-10-22 05:31:07 +03:00
{
2023-07-22 21:19:51 -07:00
base . Initialize ( ) ;
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
SubscribeLocalEvent < AccessReaderComponent , GotEmaggedEvent > ( OnEmagged ) ;
SubscribeLocalEvent < AccessReaderComponent , LinkAttemptEvent > ( OnLinkAttempt ) ;
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
SubscribeLocalEvent < AccessReaderComponent , ComponentGetState > ( OnGetState ) ;
SubscribeLocalEvent < AccessReaderComponent , ComponentHandleState > ( OnHandleState ) ;
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
private void OnGetState ( EntityUid uid , AccessReaderComponent component , ref ComponentGetState args )
{
args . State = new AccessReaderComponentState ( component . Enabled , component . DenyTags , component . AccessLists ,
2023-12-28 20:32:46 -04:00
_recordsSystem . Convert ( component . AccessKeys ) , component . AccessLog , component . AccessLogLimit ) ;
2023-07-22 21:19:51 -07:00
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
private void OnHandleState ( EntityUid uid , AccessReaderComponent component , ref ComponentHandleState args )
{
if ( args . Current is not AccessReaderComponentState state )
return ;
component . Enabled = state . Enabled ;
2023-09-11 09:42:41 +10:00
component . AccessKeys . Clear ( ) ;
foreach ( var key in state . AccessKeys )
{
var id = EnsureEntity < AccessReaderComponent > ( key . Item1 , uid ) ;
if ( ! id . IsValid ( ) )
continue ;
component . AccessKeys . Add ( new StationRecordKey ( key . Item2 , id ) ) ;
}
2023-07-27 22:25:55 +02:00
component . AccessLists = new ( state . AccessLists ) ;
component . DenyTags = new ( state . DenyTags ) ;
2023-12-26 16:24:53 -06:00
component . AccessLog = new ( state . AccessLog ) ;
component . AccessLogLimit = state . AccessLogLimit ;
2023-07-22 21:19:51 -07:00
}
2022-04-22 15:04:54 +12:00
2023-07-22 21:19:51 -07:00
private void OnLinkAttempt ( EntityUid uid , AccessReaderComponent component , LinkAttemptEvent args )
{
if ( args . User = = null ) // AutoLink (and presumably future external linkers) have no user.
return ;
2025-01-30 05:05:47 +01:00
if ( ! IsAllowed ( args . User . Value , uid , component ) )
2023-07-22 21:19:51 -07:00
args . Cancel ( ) ;
}
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
private void OnEmagged ( EntityUid uid , AccessReaderComponent reader , ref GotEmaggedEvent args )
{
2025-01-30 05:05:47 +01:00
if ( ! _emag . CompareFlag ( args . Type , EmagType . Access ) )
2024-01-15 01:35:28 -05:00
return ;
2025-01-30 05:05:47 +01:00
if ( ! reader . BreakOnAccessBreaker )
return ;
if ( ! GetMainAccessReader ( uid , out var accessReader ) )
return ;
if ( accessReader . Value . Comp . AccessLists . Count < 1 )
return ;
args . Repeatable = true ;
2023-07-22 21:19:51 -07:00
args . Handled = true ;
2025-01-30 05:05:47 +01:00
accessReader . Value . Comp . AccessLists . Clear ( ) ;
accessReader . Value . Comp . AccessLog . Clear ( ) ;
2023-10-05 05:12:40 -04:00
Dirty ( uid , reader ) ;
2023-07-22 21:19:51 -07:00
}
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
/// <summary>
/// Searches the source for access tags
2023-07-26 05:34:08 +00:00
/// then compares it with the all targets accesses to see if it is allowed.
2023-07-22 21:19:51 -07:00
/// </summary>
2023-09-03 13:05:22 +12:00
/// <param name="user">The entity that wants access.</param>
2023-07-22 21:19:51 -07:00
/// <param name="target">The entity to search for an access reader</param>
/// <param name="reader">Optional reader from the target entity</param>
2023-09-03 13:05:22 +12:00
public bool IsAllowed ( EntityUid user , EntityUid target , AccessReaderComponent ? reader = null )
2023-07-22 21:19:51 -07:00
{
2023-07-27 22:25:55 +02:00
if ( ! Resolve ( target , ref reader , false ) )
return true ;
2023-07-26 05:34:08 +00:00
2023-09-03 13:05:22 +12:00
if ( ! reader . Enabled )
return true ;
2023-02-19 01:03:06 +00:00
2023-09-03 13:05:22 +12:00
var accessSources = FindPotentialAccessItems ( user ) ;
var access = FindAccessTags ( user , accessSources ) ;
FindStationRecordKeys ( user , out var stationKeys , accessSources ) ;
2023-07-27 22:25:55 +02:00
2025-04-04 00:24:30 +02:00
if ( ! IsAllowed ( access , stationKeys , target , reader ) )
return false ;
if ( ! _tag . HasTag ( user , PreventAccessLoggingTag ) )
2023-12-26 16:24:53 -06:00
LogAccess ( ( target , reader ) , user ) ;
2025-04-04 00:24:30 +02:00
return true ;
2023-07-27 22:25:55 +02:00
}
2023-09-03 13:05:22 +12:00
2025-06-05 18:28:55 -05:00
/// <summary>
/// Searches an entity for an access reader. This is either the entity itself or an entity in its <see cref="AccessReaderComponent.ContainerAccessProvider"/>.
/// </summary>
/// <param name="uid">The entity being searched for an access reader.</param>
/// <param name="ent">The returned access reader entity.</param>
2025-01-10 01:22:28 +01:00
public bool GetMainAccessReader ( EntityUid uid , [ NotNullWhen ( true ) ] out Entity < AccessReaderComponent > ? ent )
2024-04-01 09:06:13 +03:00
{
2025-01-10 01:22:28 +01:00
ent = null ;
if ( ! TryComp < AccessReaderComponent > ( uid , out var accessReader ) )
2024-04-01 09:06:13 +03:00
return false ;
2025-01-10 01:22:28 +01:00
ent = ( uid , accessReader ) ;
2024-04-01 09:06:13 +03:00
2025-01-10 01:22:28 +01:00
if ( ent . Value . Comp . ContainerAccessProvider = = null )
2024-04-01 09:06:13 +03:00
return true ;
2025-01-10 01:22:28 +01:00
if ( ! _containerSystem . TryGetContainer ( uid , ent . Value . Comp . ContainerAccessProvider , out var container ) )
2024-04-01 09:06:13 +03:00
return true ;
foreach ( var entity in container . ContainedEntities )
{
2025-01-10 01:22:28 +01:00
if ( TryComp < AccessReaderComponent > ( entity , out var containedReader ) )
2024-04-01 09:06:13 +03:00
{
2025-01-10 01:22:28 +01:00
ent = ( entity , containedReader ) ;
2024-04-01 09:06:13 +03:00
return true ;
}
}
2025-01-30 05:05:47 +01:00
2024-04-01 09:06:13 +03:00
return true ;
}
2023-07-22 21:19:51 -07:00
/// <summary>
2023-09-03 13:05:22 +12:00
/// Check whether the given access permissions satisfy an access reader's requirements.
2023-07-22 21:19:51 -07:00
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="access">A collection of access permissions being used on the access reader.</param>
/// <param name="stationKeys">A collection of station record keys being used on the access reader.</param>
/// <param name="target">The entity being checked.</param>
/// <param name="reader">The access reader being checked.</param>
2023-09-03 13:05:22 +12:00
public bool IsAllowed (
2024-04-01 09:06:13 +03:00
ICollection < ProtoId < AccessLevelPrototype > > access ,
2023-09-03 13:05:22 +12:00
ICollection < StationRecordKey > stationKeys ,
EntityUid target ,
AccessReaderComponent reader )
2023-07-22 21:19:51 -07:00
{
if ( ! reader . Enabled )
return true ;
2023-02-28 11:03:55 -05:00
2023-09-03 13:05:22 +12:00
if ( reader . ContainerAccessProvider = = null )
return IsAllowedInternal ( access , stationKeys , reader ) ;
2023-07-27 22:25:55 +02:00
2023-09-03 13:05:22 +12:00
if ( ! _containerSystem . TryGetContainer ( target , reader . ContainerAccessProvider , out var container ) )
2024-07-19 16:19:15 +10:00
return false ;
// If entity is paused then always allow it at this point.
// Door electronics is kind of a mess but yeah, it should only be an unpaused ent interacting with it
if ( Paused ( target ) )
return true ;
2023-03-13 22:55:18 -04:00
2023-09-03 13:05:22 +12:00
foreach ( var entity in container . ContainedEntities )
{
if ( ! TryComp ( entity , out AccessReaderComponent ? containedReader ) )
continue ;
if ( IsAllowed ( access , stationKeys , entity , containedReader ) )
return true ;
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
return false ;
}
2024-04-01 09:06:13 +03:00
private bool IsAllowedInternal ( ICollection < ProtoId < AccessLevelPrototype > > access , ICollection < StationRecordKey > stationKeys , AccessReaderComponent reader )
2023-09-03 13:05:22 +12:00
{
return ! reader . Enabled
| | AreAccessTagsAllowed ( access , reader )
| | AreStationRecordKeysAllowed ( stationKeys , reader ) ;
}
2023-07-22 21:19:51 -07:00
/// <summary>
/// Compares the given tags with the readers access list to see if it is allowed.
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="accessTags">A list of access tags.</param>
/// <param name="reader">The access reader to check against.</param>
2024-04-01 09:06:13 +03:00
public bool AreAccessTagsAllowed ( ICollection < ProtoId < AccessLevelPrototype > > accessTags , AccessReaderComponent reader )
2023-07-22 21:19:51 -07:00
{
if ( reader . DenyTags . Overlaps ( accessTags ) )
{
// Sec owned by cargo.
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
// Note that in resolving the issue with only one specific item "counting" for access, this became a bit more strict.
// As having an ID card in any slot that "counts" with a denied access group will cause denial of access.
// DenyTags doesn't seem to be used right now anyway, though, so it'll be dependent on whoever uses it to figure out if this matters.
2023-02-28 11:03:55 -05:00
return false ;
2021-10-22 05:31:07 +03:00
}
2023-09-03 13:05:22 +12:00
if ( reader . AccessLists . Count = = 0 )
return true ;
foreach ( var set in reader . AccessLists )
{
if ( set . IsSubsetOf ( accessTags ) )
return true ;
}
return false ;
2023-07-22 21:19:51 -07:00
}
2022-04-24 01:21:17 +01:00
2023-07-22 21:19:51 -07:00
/// <summary>
/// Compares the given stationrecordkeys with the accessreader to see if it is allowed.
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="keys">The collection of station record keys being used against the access reader.</param>
/// <param name="reader">The access reader that is being checked.</param>
2023-07-22 21:19:51 -07:00
public bool AreStationRecordKeysAllowed ( ICollection < StationRecordKey > keys , AccessReaderComponent reader )
{
2023-09-03 13:05:22 +12:00
foreach ( var key in reader . AccessKeys )
{
if ( keys . Contains ( key ) )
return true ;
}
return false ;
2023-07-22 21:19:51 -07:00
}
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Finds all the items that could potentially give access to an entity.
2023-07-22 21:19:51 -07:00
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="uid">The entity that is being searched.</param>
2023-07-22 21:19:51 -07:00
public HashSet < EntityUid > FindPotentialAccessItems ( EntityUid uid )
{
FindAccessItemsInventory ( uid , out var items ) ;
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
var ev = new GetAdditionalAccessEvent
2021-10-22 05:31:07 +03:00
{
2023-07-22 21:19:51 -07:00
Entities = items
} ;
RaiseLocalEvent ( uid , ref ev ) ;
2023-10-05 05:12:40 -04:00
foreach ( var item in new ValueList < EntityUid > ( items ) )
{
items . UnionWith ( FindPotentialAccessItems ( item ) ) ;
}
2023-07-22 21:19:51 -07:00
items . Add ( uid ) ;
return items ;
}
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Finds the access tags on an entity.
2023-07-22 21:19:51 -07:00
/// </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>
2024-04-01 09:06:13 +03:00
public ICollection < ProtoId < AccessLevelPrototype > > FindAccessTags ( EntityUid uid , HashSet < EntityUid > ? items = null )
2023-07-22 21:19:51 -07:00
{
2024-04-01 09:06:13 +03:00
HashSet < ProtoId < AccessLevelPrototype > > ? tags = null ;
2023-07-22 21:19:51 -07:00
var owned = false ;
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
items ? ? = FindPotentialAccessItems ( uid ) ;
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
foreach ( var ent in items )
2023-02-28 11:03:55 -05:00
{
2023-07-22 21:19:51 -07:00
FindAccessTagsItem ( ent , ref tags , ref owned ) ;
}
2023-02-28 11:03:55 -05:00
2025-06-05 18:28:55 -05:00
return ( ICollection < ProtoId < AccessLevelPrototype > > ? ) tags ? ? Array . Empty < ProtoId < AccessLevelPrototype > > ( ) ;
2023-07-22 21:19:51 -07:00
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Finds any station record keys on an entity.
2023-07-22 21:19:51 -07:00
/// </summary>
/// <param name="uid">The entity that is being searched.</param>
2025-06-05 18:28:55 -05:00
/// <param name="recordKeys">A collection of the station record keys that were found.</param>
2023-07-22 21:19:51 -07:00
/// <param name="items">All of the items to search for access. If none are passed in, <see cref="FindPotentialAccessItems"/> will be used.</param>
2023-07-27 22:25:55 +02:00
public bool FindStationRecordKeys ( EntityUid uid , out ICollection < StationRecordKey > recordKeys , HashSet < EntityUid > ? items = null )
2023-07-22 21:19:51 -07:00
{
2023-07-27 22:25:55 +02:00
recordKeys = new HashSet < StationRecordKey > ( ) ;
2022-04-24 01:21:17 +01:00
2023-07-22 21:19:51 -07:00
items ? ? = FindPotentialAccessItems ( uid ) ;
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
foreach ( var ent in items )
2023-02-28 11:03:55 -05:00
{
2023-07-22 21:19:51 -07:00
if ( FindStationRecordKeyItem ( ent , out var key ) )
2023-07-27 22:25:55 +02:00
recordKeys . Add ( key . Value ) ;
2023-07-22 21:19:51 -07:00
}
2023-02-28 11:03:55 -05:00
2023-07-27 22:25:55 +02:00
return recordKeys . Any ( ) ;
2023-07-22 21:19:51 -07:00
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Try to find <see cref="AccessComponent"/> on this item or inside this item (if it's a PDA).
/// This version merges into a set or replaces the set.
2023-07-22 21:19:51 -07:00
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="uid">The entity that is being searched.</param>
/// <param name="tags">The access tags being merged or replaced.</param>
/// <param name="owned">If true, the tags will be merged. Otherwise they are replaced.</param>
2024-04-01 09:06:13 +03:00
private void FindAccessTagsItem ( EntityUid uid , ref HashSet < ProtoId < AccessLevelPrototype > > ? tags , ref bool owned )
2023-07-22 21:19:51 -07:00
{
if ( ! FindAccessTagsItem ( uid , out var targetTags ) )
{
// no tags, no problem
return ;
2023-02-28 11:03:55 -05:00
}
2023-07-22 21:19:51 -07:00
if ( tags ! = null )
2022-04-24 01:21:17 +01:00
{
2023-07-22 21:19:51 -07:00
// existing tags, so copy to make sure we own them
if ( ! owned )
2022-04-24 01:21:17 +01:00
{
2023-07-22 21:19:51 -07:00
tags = new ( tags ) ;
owned = true ;
2022-04-24 01:21:17 +01:00
}
2023-07-22 21:19:51 -07:00
// then merge
tags . UnionWith ( targetTags ) ;
2021-10-22 05:31:07 +03:00
}
2023-07-22 21:19:51 -07:00
else
{
// no existing tags, so now they're ours
tags = targetTags ;
owned = false ;
}
}
2025-06-05 18:28:55 -05:00
#region : AccessLists API
/// <summary>
/// Clears the entity's <see cref="AccessReaderComponent.AccessLists"/>.
/// </summary>
/// <param name="ent">The access reader entity which is having its access permissions cleared.</param>
public void ClearAccesses ( Entity < AccessReaderComponent > ent )
{
ent . Comp . AccessLists . Clear ( ) ;
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <summary>
/// Replaces the access permissions in an entity's <see cref="AccessReaderComponent.AccessLists"/> with a supplied list.
/// </summary>
/// <param name="ent">The access reader entity which is having its list of access permissions replaced.</param>
/// <param name="accesses">The list of access permissions replacing the original one.</param>
public void SetAccesses ( Entity < AccessReaderComponent > ent , List < HashSet < ProtoId < AccessLevelPrototype > > > accesses )
{
ent . Comp . AccessLists . Clear ( ) ;
AddAccesses ( ent , accesses ) ;
}
/// <inheritdoc cref = "SetAccesses"/>
public void SetAccesses ( Entity < AccessReaderComponent > ent , List < ProtoId < AccessLevelPrototype > > accesses )
{
ent . Comp . AccessLists . Clear ( ) ;
AddAccesses ( ent , accesses ) ;
}
/// <summary>
/// Adds a collection of access permissions to an access reader entity's <see cref="AccessReaderComponent.AccessLists"/>
/// </summary>
/// <param name="ent">The access reader entity to which the new access permissions are being added.</param>
/// <param name="accesses">The list of access permissions being added.</param>
public void AddAccesses ( Entity < AccessReaderComponent > ent , List < HashSet < ProtoId < AccessLevelPrototype > > > accesses )
2024-04-01 09:06:13 +03:00
{
foreach ( var access in accesses )
{
2025-06-05 18:28:55 -05:00
AddAccess ( ent , access , false ) ;
2024-04-01 09:06:13 +03:00
}
2025-06-05 18:28:55 -05:00
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
2024-04-01 09:06:13 +03:00
}
2025-06-05 18:28:55 -05:00
/// <inheritdoc cref = "AddAccesses"/>
public void AddAccesses ( Entity < AccessReaderComponent > ent , List < ProtoId < AccessLevelPrototype > > accesses )
{
foreach ( var access in accesses )
{
AddAccess ( ent , access , false ) ;
}
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <summary>
/// Adds an access permission to an access reader entity's <see cref="AccessReaderComponent.AccessLists"/>
/// </summary>
/// <param name="ent">The access reader entity to which the access permission is being added.</param>
/// <param name="access">The access permission being added.</param>
/// <param name="dirty">If true, the component will be marked as changed afterward.</param>
public void AddAccess ( Entity < AccessReaderComponent > ent , HashSet < ProtoId < AccessLevelPrototype > > access , bool dirty = true )
{
ent . Comp . AccessLists . Add ( access ) ;
if ( ! dirty )
return ;
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <inheritdoc cref = "AddAccess"/>
public void AddAccess ( Entity < AccessReaderComponent > ent , ProtoId < AccessLevelPrototype > access , bool dirty = true )
{
AddAccess ( ent , new HashSet < ProtoId < AccessLevelPrototype > > ( ) { access } , dirty ) ;
}
/// <summary>
/// Removes a collection of access permissions from an access reader entity's <see cref="AccessReaderComponent.AccessLists"/>
/// </summary>
/// <param name="ent">The access reader entity from which the access permissions are being removed.</param>
/// <param name="accesses">The list of access permissions being removed.</param>
public void RemoveAccesses ( Entity < AccessReaderComponent > ent , List < HashSet < ProtoId < AccessLevelPrototype > > > accesses )
{
foreach ( var access in accesses )
{
RemoveAccess ( ent , access , false ) ;
}
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <inheritdoc cref = "RemoveAccesses"/>
public void RemoveAccesses ( Entity < AccessReaderComponent > ent , List < ProtoId < AccessLevelPrototype > > accesses )
{
foreach ( var access in accesses )
{
RemoveAccess ( ent , access , false ) ;
}
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <summary>
/// Removes an access permission from an access reader entity's <see cref="AccessReaderComponent.AccessLists"/>
/// </summary>
/// <param name="ent">The access reader entity from which the access permission is being removed.</param>
/// <param name="access">The access permission being removed.</param>
/// <param name="dirty">If true, the component will be marked as changed afterward.</param>
public void RemoveAccess ( Entity < AccessReaderComponent > ent , HashSet < ProtoId < AccessLevelPrototype > > access , bool dirty = true )
{
for ( int i = ent . Comp . AccessLists . Count - 1 ; i > = 0 ; i - - )
{
if ( ent . Comp . AccessLists [ i ] . SetEquals ( access ) )
{
ent . Comp . AccessLists . RemoveAt ( i ) ;
}
}
if ( ! dirty )
return ;
Dirty ( ent ) ;
RaiseLocalEvent ( ent , new AccessReaderConfigurationChangedEvent ( ) ) ;
}
/// <inheritdoc cref = "RemoveAccess"/>
public void RemoveAccess ( Entity < AccessReaderComponent > ent , ProtoId < AccessLevelPrototype > access , bool dirty = true )
2023-07-22 21:19:51 -07:00
{
2025-06-05 18:28:55 -05:00
RemoveAccess ( ent , new HashSet < ProtoId < AccessLevelPrototype > > ( ) { access } , dirty ) ;
}
2021-10-22 05:31:07 +03:00
2025-06-05 18:28:55 -05:00
#endregion
#region : AccessKeys API
/// <summary>
/// Clears all access keys from an access reader.
/// </summary>
/// <param name="ent">The access reader entity.</param>
public void ClearAccessKeys ( Entity < AccessReaderComponent > ent )
{
ent . Comp . AccessKeys . Clear ( ) ;
Dirty ( ent ) ;
}
/// <summary>
/// Replaces all access keys on an access reader with those from a supplied list.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="keys">The new access keys that are replacing the old ones.</param>
public void SetAccessKeys ( Entity < AccessReaderComponent > ent , HashSet < StationRecordKey > keys )
{
ent . Comp . AccessKeys . Clear ( ) ;
foreach ( var key in keys )
2022-12-04 19:23:59 -05:00
{
2025-06-05 18:28:55 -05:00
ent . Comp . AccessKeys . Add ( key ) ;
2023-07-22 21:19:51 -07:00
}
2022-12-04 19:23:59 -05:00
2025-06-05 18:28:55 -05:00
Dirty ( ent ) ;
}
/// <summary>
/// Adds an access key to an access reader.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="key">The access key being added.</param>
public void AddAccessKey ( Entity < AccessReaderComponent > ent , StationRecordKey key )
{
ent . Comp . AccessKeys . Add ( key ) ;
Dirty ( ent ) ;
}
/// <summary>
/// Removes an access key from an access reader.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="key">The access key being removed.</param>
public void RemoveAccessKey ( Entity < AccessReaderComponent > ent , StationRecordKey key )
{
ent . Comp . AccessKeys . Remove ( key ) ;
Dirty ( ent ) ;
}
#endregion
#region : DenyTags API
/// <summary>
/// Clears all deny tags from an access reader.
/// </summary>
/// <param name="ent">The access reader entity.</param>
public void ClearDenyTags ( Entity < AccessReaderComponent > ent )
{
ent . Comp . DenyTags . Clear ( ) ;
Dirty ( ent ) ;
}
/// <summary>
/// Replaces all deny tags on an access reader with those from a supplied list.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="tag">The new tags that are replacing the old.</param>
public void SetDenyTags ( Entity < AccessReaderComponent > ent , HashSet < ProtoId < AccessLevelPrototype > > tags )
{
ent . Comp . DenyTags . Clear ( ) ;
foreach ( var tag in tags )
{
ent . Comp . DenyTags . Add ( tag ) ;
}
Dirty ( ent ) ;
}
/// <summary>
/// Adds a tag to an access reader that will be used to deny access.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="tag">The tag being added.</param>
public void AddDenyTag ( Entity < AccessReaderComponent > ent , ProtoId < AccessLevelPrototype > tag )
{
ent . Comp . DenyTags . Add ( tag ) ;
Dirty ( ent ) ;
}
/// <summary>
/// Removes a tag from an access reader that denied a user access.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="tag">The tag being removed.</param>
public void RemoveDenyTag ( Entity < AccessReaderComponent > ent , ProtoId < AccessLevelPrototype > tag )
{
ent . Comp . DenyTags . Remove ( tag ) ;
Dirty ( ent ) ;
}
#endregion
/// <summary>
/// Enables/disables the access reader on an entity.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="enabled">Enable/disable the access reader.</param>
public void SetActive ( Entity < AccessReaderComponent > ent , bool enabled )
{
ent . Comp . Enabled = enabled ;
Dirty ( ent ) ;
}
/// <summary>
/// Enables/disables the logging of access attempts on an access reader entity.
/// </summary>
/// <param name="ent">The access reader entity.</param>
/// <param name="enabled">Enable/disable logging.</param>
public void SetLoggingActive ( Entity < AccessReaderComponent > ent , bool enabled )
{
ent . Comp . LoggingDisabled = ! enabled ;
Dirty ( ent ) ;
}
/// <summary>
/// Searches an entity's hand and ID slot for any contained items.
/// </summary>
/// <param name="uid">The entity being searched.</param>
/// <param name="items">The collection of found items.</param>
/// <returns>True if one or more items were found.</returns>
public bool FindAccessItemsInventory ( EntityUid uid , out HashSet < EntityUid > items )
{
items = new ( _handsSystem . EnumerateHeld ( uid ) ) ;
2023-07-22 21:19:51 -07:00
// maybe its inside an inventory slot?
if ( _inventorySystem . TryGetSlotEntity ( uid , "id" , out var idUid ) )
{
items . Add ( idUid . Value ) ;
}
2022-12-04 19:23:59 -05:00
2023-07-22 21:19:51 -07:00
return items . Any ( ) ;
}
2022-12-04 19:23:59 -05:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Try to find <see cref="AccessComponent"/> on this entity or inside it (if it's a PDA).
2023-07-22 21:19:51 -07:00
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="uid">The entity being searched.</param>
/// <param name="tags">The access tags that were found.</param>
/// <returns>True if one or more access tags were found.</returns>
2024-04-01 09:06:13 +03:00
private bool FindAccessTagsItem ( EntityUid uid , out HashSet < ProtoId < AccessLevelPrototype > > tags )
2023-07-22 21:19:51 -07:00
{
2023-08-12 17:39:58 -04:00
tags = new ( ) ;
var ev = new GetAccessTagsEvent ( tags , _prototype ) ;
RaiseLocalEvent ( uid , ref ev ) ;
return tags . Count ! = 0 ;
2023-07-22 21:19:51 -07:00
}
2021-10-22 05:31:07 +03:00
2023-07-22 21:19:51 -07:00
/// <summary>
2025-06-05 18:28:55 -05:00
/// Try to find <see cref="StationRecordKeyStorageComponent"/> on this entity or inside it (if it's a PDA).
2023-07-22 21:19:51 -07:00
/// </summary>
2025-06-05 18:28:55 -05:00
/// <param name="uid">The entity being searched.</param>
/// <param name="key">The station record key that was found.</param>
/// <returns>True if a station record key was found.</returns>
2023-07-22 21:19:51 -07:00
private bool FindStationRecordKeyItem ( EntityUid uid , [ NotNullWhen ( true ) ] out StationRecordKey ? key )
{
if ( TryComp ( uid , out StationRecordKeyStorageComponent ? storage ) & & storage . Key ! = null )
{
key = storage . Key ;
return true ;
2021-10-22 05:31:07 +03:00
}
2023-02-28 11:03:55 -05:00
2023-07-22 21:19:51 -07:00
if ( TryComp < PdaComponent > ( uid , out var pda ) & &
pda . ContainedId is { Valid : true } id )
2023-02-28 11:03:55 -05:00
{
2023-07-22 21:19:51 -07:00
if ( TryComp < StationRecordKeyStorageComponent > ( id , out var pdastorage ) & & pdastorage . Key ! = null )
2023-02-28 11:03:55 -05:00
{
2023-07-22 21:19:51 -07:00
key = pdastorage . Key ;
2023-02-28 11:03:55 -05:00
return true ;
}
}
2023-07-22 21:19:51 -07:00
key = null ;
return false ;
2021-10-22 05:31:07 +03:00
}
2023-12-26 16:24:53 -06:00
/// <summary>
2024-06-22 15:12:58 +00:00
/// Logs an access for a specific entity.
2023-12-26 16:24:53 -06:00
/// </summary>
/// <param name="ent">The reader to log the access on</param>
/// <param name="accessor">The accessor to log</param>
2024-06-22 15:12:58 +00:00
public void LogAccess ( Entity < AccessReaderComponent > ent , EntityUid accessor )
2023-12-26 16:24:53 -06:00
{
2024-06-22 15:12:58 +00:00
if ( IsPaused ( ent ) | | ent . Comp . LoggingDisabled )
2023-12-28 20:32:46 -04:00
return ;
2023-12-26 16:24:53 -06:00
string? name = null ;
2024-06-03 20:48:44 +02:00
if ( TryComp < NameIdentifierComponent > ( accessor , out var nameIdentifier ) )
name = nameIdentifier . FullIdentifier ;
2023-12-26 16:24:53 -06:00
// 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
2024-09-03 16:01:38 +03:00
var getIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent ( ent , accessor , true ) ;
RaiseLocalEvent ( getIdentityShortInfoEvent ) ;
if ( getIdentityShortInfoEvent . Title ! = null )
{
name = getIdentityShortInfoEvent . Title ;
}
2023-12-26 16:24:53 -06:00
2024-06-22 15:12:58 +00:00
LogAccess ( ent , name ? ? Loc . GetString ( "access-reader-unknown-id" ) ) ;
}
/// <summary>
/// Logs an access with a predetermined name
/// </summary>
/// <param name="ent">The reader to log the access on</param>
/// <param name="name">The name to log as</param>
2025-06-05 18:28:55 -05:00
public void LogAccess ( Entity < AccessReaderComponent > ent , string name , TimeSpan ? accessTime = null , bool force = false )
2024-06-22 15:12:58 +00:00
{
2025-06-05 18:28:55 -05:00
if ( ! force )
{
if ( IsPaused ( ent ) | | ent . Comp . LoggingDisabled )
return ;
2024-06-22 15:12:58 +00:00
2025-06-05 18:28:55 -05:00
if ( ent . Comp . AccessLog . Count > = ent . Comp . AccessLogLimit )
ent . Comp . AccessLog . Dequeue ( ) ;
}
2023-12-26 16:24:53 -06:00
2025-06-05 18:28:55 -05:00
var stationTime = accessTime ? ? _gameTiming . CurTime . Subtract ( _gameTicker . RoundStartTimeSpan ) ;
2023-12-26 16:24:53 -06:00
ent . Comp . AccessLog . Enqueue ( new AccessRecord ( stationTime , name ) ) ;
2025-06-05 18:28:55 -05:00
Dirty ( ent ) ;
2023-12-26 16:24:53 -06:00
}
2021-10-22 05:31:07 +03:00
}