2023-02-05 21:02:28 +01:00
using Content.Server.Station.Systems ;
2023-02-28 11:03:55 -05:00
using Content.Server.StationRecords.Systems ;
2023-02-05 21:02:28 +01:00
using Content.Shared.Access.Components ;
2021-12-16 23:42:02 +13:00
using Content.Shared.Access.Systems ;
2023-02-05 21:02:28 +01:00
using Content.Shared.Administration.Logs ;
using Content.Shared.Database ;
using Content.Shared.Roles ;
using Content.Shared.StationRecords ;
2023-07-29 10:25:27 +02:00
using Content.Shared.StatusIcon ;
2021-11-24 20:03:07 +13:00
using JetBrains.Annotations ;
2023-02-05 21:02:28 +01:00
using Robust.Server.GameObjects ;
2021-11-24 20:03:07 +13:00
using Robust.Shared.Containers ;
2023-02-05 21:02:28 +01:00
using Robust.Shared.Prototypes ;
2023-07-29 10:25:27 +02:00
using System.Linq ;
2023-04-08 13:15:52 -07:00
using static Content . Shared . Access . Components . IdCardConsoleComponent ;
2021-11-24 20:03:07 +13:00
2023-02-05 21:02:28 +01:00
namespace Content.Server.Access.Systems ;
[UsedImplicitly]
public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
2021-11-24 20:03:07 +13:00
{
2023-02-05 21:02:28 +01:00
[Dependency] private readonly IPrototypeManager _prototype = default ! ;
[Dependency] private readonly StationRecordsSystem _record = default ! ;
[Dependency] private readonly StationSystem _station = default ! ;
[Dependency] private readonly UserInterfaceSystem _userInterface = default ! ;
[Dependency] private readonly AccessReaderSystem _accessReader = default ! ;
[Dependency] private readonly AccessSystem _access = default ! ;
[Dependency] private readonly IdCardSystem _idCard = default ! ;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
public override void Initialize ( )
{
base . Initialize ( ) ;
2023-04-08 13:15:52 -07:00
SubscribeLocalEvent < IdCardConsoleComponent , WriteToTargetIdMessage > ( OnWriteToTargetIdMessage ) ;
2023-02-05 21:02:28 +01:00
// one day, maybe bound user interfaces can be shared too.
2023-04-08 13:15:52 -07:00
SubscribeLocalEvent < IdCardConsoleComponent , ComponentStartup > ( UpdateUserInterface ) ;
SubscribeLocalEvent < IdCardConsoleComponent , EntInsertedIntoContainerMessage > ( UpdateUserInterface ) ;
SubscribeLocalEvent < IdCardConsoleComponent , EntRemovedFromContainerMessage > ( UpdateUserInterface ) ;
2023-02-05 21:02:28 +01:00
}
2023-04-08 13:15:52 -07:00
private void OnWriteToTargetIdMessage ( EntityUid uid , IdCardConsoleComponent component , WriteToTargetIdMessage args )
2023-02-05 21:02:28 +01:00
{
if ( args . Session . AttachedEntity is not { Valid : true } player )
return ;
TryWriteToTargetId ( uid , args . FullName , args . JobTitle , args . AccessList , args . JobPrototype , player , component ) ;
UpdateUserInterface ( uid , component , args ) ;
}
2023-04-08 13:15:52 -07:00
private void UpdateUserInterface ( EntityUid uid , IdCardConsoleComponent component , EntityEventArgs args )
2023-02-05 21:02:28 +01:00
{
if ( ! component . Initialized )
return ;
2023-05-05 08:56:54 -05:00
var privilegedIdName = string . Empty ;
string [ ] ? possibleAccess = null ;
if ( component . PrivilegedIdSlot . Item is { Valid : true } item )
{
privilegedIdName = EntityManager . GetComponent < MetaDataComponent > ( item ) . EntityName ;
possibleAccess = _accessReader . FindAccessTags ( item ) . ToArray ( ) ;
}
2023-02-05 21:02:28 +01:00
IdCardConsoleBoundUserInterfaceState newState ;
// this could be prettier
if ( component . TargetIdSlot . Item is not { Valid : true } targetId )
{
newState = new IdCardConsoleBoundUserInterfaceState (
component . PrivilegedIdSlot . HasItem ,
PrivilegedIdIsAuthorized ( uid , component ) ,
false ,
null ,
null ,
null ,
2023-05-05 08:56:54 -05:00
possibleAccess ,
2023-02-05 21:02:28 +01:00
string . Empty ,
privilegedIdName ,
string . Empty ) ;
}
else
{
var targetIdComponent = EntityManager . GetComponent < IdCardComponent > ( targetId ) ;
var targetAccessComponent = EntityManager . GetComponent < AccessComponent > ( targetId ) ;
var jobProto = string . Empty ;
if ( _station . GetOwningStation ( uid ) is { } station
& & EntityManager . TryGetComponent < StationRecordKeyStorageComponent > ( targetId , out var keyStorage )
& & keyStorage . Key ! = null
& & _record . TryGetRecord < GeneralStationRecord > ( station , keyStorage . Key . Value , out var record ) )
{
jobProto = record . JobPrototype ;
}
newState = new IdCardConsoleBoundUserInterfaceState (
component . PrivilegedIdSlot . HasItem ,
PrivilegedIdIsAuthorized ( uid , component ) ,
true ,
targetIdComponent . FullName ,
targetIdComponent . JobTitle ,
targetAccessComponent . Tags . ToArray ( ) ,
2023-05-05 08:56:54 -05:00
possibleAccess ,
2023-02-05 21:02:28 +01:00
jobProto ,
2023-05-05 08:56:54 -05:00
privilegedIdName ,
2023-02-05 21:02:28 +01:00
EntityManager . GetComponent < MetaDataComponent > ( targetId ) . EntityName ) ;
}
_userInterface . TrySetUiState ( uid , IdCardConsoleUiKey . Key , newState ) ;
}
/// <summary>
/// Called whenever an access button is pressed, adding or removing that access from the target ID card.
2023-04-08 13:15:52 -07:00
/// Writes data passed from the UI into the ID stored in <see cref="IdCardConsoleComponent.TargetIdSlot"/>, if present.
2023-02-05 21:02:28 +01:00
/// </summary>
private void TryWriteToTargetId ( EntityUid uid ,
string newFullName ,
string newJobTitle ,
List < string > newAccessList ,
string newJobProto ,
EntityUid player ,
2023-04-08 13:15:52 -07:00
IdCardConsoleComponent ? component = null )
2021-11-24 20:03:07 +13:00
{
2023-02-05 21:02:28 +01:00
if ( ! Resolve ( uid , ref component ) )
return ;
if ( component . TargetIdSlot . Item is not { Valid : true } targetId | | ! PrivilegedIdIsAuthorized ( uid , component ) )
return ;
_idCard . TryChangeFullName ( targetId , newFullName , player : player ) ;
_idCard . TryChangeJobTitle ( targetId , newJobTitle , player : player ) ;
2023-07-29 10:25:27 +02:00
if ( _prototype . TryIndex < JobPrototype > ( newJobProto , out var job )
& & _prototype . TryIndex < StatusIconPrototype > ( job . Icon , out var jobIcon ) )
{
_idCard . TryChangeJobIcon ( targetId , jobIcon , player : player ) ;
}
2023-02-05 21:02:28 +01:00
if ( ! newAccessList . TrueForAll ( x = > component . AccessLevels . Contains ( x ) ) )
2021-11-24 20:03:07 +13:00
{
2023-05-05 08:56:54 -05:00
_sawmill . Warning ( $"User {ToPrettyString(uid)} tried to write unknown access tag." ) ;
2023-02-05 21:02:28 +01:00
return ;
}
var oldTags = _access . TryGetTags ( targetId ) ? ? new List < string > ( ) ;
oldTags = oldTags . ToList ( ) ;
2021-11-24 20:03:07 +13:00
2023-05-05 08:56:54 -05:00
var privilegedId = component . PrivilegedIdSlot . Item ;
2023-02-05 21:02:28 +01:00
if ( oldTags . SequenceEqual ( newAccessList ) )
return ;
2023-05-05 08:56:54 -05:00
// I hate that C# doesn't have an option for this and don't desire to write this out the hard way.
// var difference = newAccessList.Difference(oldTags);
2023-07-29 10:25:27 +02:00
var difference = newAccessList . Union ( oldTags ) . Except ( newAccessList . Intersect ( oldTags ) ) . ToHashSet ( ) ;
2023-05-05 08:56:54 -05:00
// NULL SAFETY: PrivilegedIdIsAuthorized checked this earlier.
var privilegedPerms = _accessReader . FindAccessTags ( privilegedId ! . Value ) . ToHashSet ( ) ;
if ( ! difference . IsSubsetOf ( privilegedPerms ) )
{
_sawmill . Warning ( $"User {ToPrettyString(uid)} tried to modify permissions they could not give/take!" ) ;
return ;
}
2023-02-05 21:02:28 +01:00
var addedTags = newAccessList . Except ( oldTags ) . Select ( tag = > "+" + tag ) . ToList ( ) ;
var removedTags = oldTags . Except ( newAccessList ) . Select ( tag = > "-" + tag ) . ToList ( ) ;
_access . TrySetTags ( targetId , newAccessList ) ;
/ * TODO : ECS SharedIdCardConsoleComponent and then log on card ejection , together with the save .
This current implementation is pretty shit as it logs 27 entries ( 27 lines ) if someone decides to give themselves AA * /
_adminLogger . Add ( LogType . Action , LogImpact . Medium ,
$"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(" , ", addedTags.Union(removedTags))}] [{string.Join(" , ", newAccessList)}]" ) ;
2023-07-29 10:25:27 +02:00
UpdateStationRecord ( uid , targetId , newFullName , newJobTitle , job ) ;
2023-02-05 21:02:28 +01:00
}
/// <summary>
2023-04-08 13:15:52 -07:00
/// Returns true if there is an ID in <see cref="IdCardConsoleComponent.PrivilegedIdSlot"/> and said ID satisfies the requirements of <see cref="AccessReaderComponent"/>.
2023-02-05 21:02:28 +01:00
/// </summary>
2023-05-05 08:56:54 -05:00
/// <remarks>
/// Other code relies on the fact this returns false if privileged Id is null. Don't break that invariant.
/// </remarks>
2023-04-08 13:15:52 -07:00
private bool PrivilegedIdIsAuthorized ( EntityUid uid , IdCardConsoleComponent ? component = null )
2023-02-05 21:02:28 +01:00
{
if ( ! Resolve ( uid , ref component ) )
return true ;
if ( ! EntityManager . TryGetComponent < AccessReaderComponent > ( uid , out var reader ) )
return true ;
var privilegedId = component . PrivilegedIdSlot . Item ;
return privilegedId ! = null & & _accessReader . IsAllowed ( privilegedId . Value , reader ) ;
}
2023-07-29 10:25:27 +02:00
private void UpdateStationRecord ( EntityUid uid , EntityUid targetId , string newFullName , string newJobTitle , JobPrototype ? newJobProto )
2023-02-05 21:02:28 +01:00
{
if ( _station . GetOwningStation ( uid ) is not { } station
| | ! EntityManager . TryGetComponent < StationRecordKeyStorageComponent > ( targetId , out var keyStorage )
| | keyStorage . Key is not { } key
| | ! _record . TryGetRecord < GeneralStationRecord > ( station , key , out var record ) )
{
return ;
2021-11-24 20:03:07 +13:00
}
2023-02-05 21:02:28 +01:00
record . Name = newFullName ;
record . JobTitle = newJobTitle ;
2023-07-29 10:25:27 +02:00
if ( newJobProto ! = null )
2023-02-05 21:02:28 +01:00
{
2023-07-29 10:25:27 +02:00
record . JobPrototype = newJobProto . ID ;
record . JobIcon = newJobProto . Icon ;
2023-02-05 21:02:28 +01:00
}
_record . Synchronize ( station ) ;
2021-11-24 20:03:07 +13:00
}
}