2024-02-04 23:29:35 +00:00
using Content.Client.UserInterface.Controls ;
using Content.Shared.Access.Systems ;
using Content.Shared.Administration ;
using Content.Shared.CriminalRecords ;
using Content.Shared.Dataset ;
2025-03-17 02:43:37 +03:00
using Content.Shared.Random.Helpers ;
2024-02-04 23:29:35 +00:00
using Content.Shared.Security ;
using Content.Shared.StationRecords ;
using Robust.Client.AutoGenerated ;
using Robust.Client.Player ;
2024-08-09 08:41:29 +01:00
using Robust.Client.UserInterface.Controls ;
2024-02-04 23:29:35 +00:00
using Robust.Client.UserInterface.XAML ;
using Robust.Shared.Prototypes ;
using Robust.Shared.Random ;
using Robust.Shared.Utility ;
2024-08-09 08:41:29 +01:00
using System.Linq ;
2025-01-29 18:34:49 +08:00
using System.Numerics ;
using Content.Shared.StatusIcon ;
using Robust.Client.GameObjects ;
2024-02-04 23:29:35 +00:00
namespace Content.Client.CriminalRecords ;
// TODO: dedupe shitcode from general records theres a lot
[GenerateTypedNameReferences]
public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
{
private readonly IPlayerManager _player ;
private readonly IPrototypeManager _proto ;
private readonly IRobustRandom _random ;
private readonly AccessReaderSystem _accessReader ;
2025-01-29 18:34:49 +08:00
[Dependency] private readonly IEntityManager _entManager = default ! ;
private readonly SpriteSystem _spriteSystem ;
2024-02-04 23:29:35 +00:00
public readonly EntityUid Console ;
2025-07-07 15:57:05 -04:00
private static readonly ProtoId < LocalizedDatasetPrototype > ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders" ;
2024-02-04 23:29:35 +00:00
public Action < uint? > ? OnKeySelected ;
public Action < StationRecordFilterType , string > ? OnFiltersChanged ;
public Action < SecurityStatus > ? OnStatusSelected ;
2025-01-29 18:34:49 +08:00
public Action < uint > ? OnCheckStatus ;
2024-02-04 23:29:35 +00:00
public Action < CriminalRecord , bool , bool > ? OnHistoryUpdated ;
public Action ? OnHistoryClosed ;
public Action < SecurityStatus , string > ? OnDialogConfirmed ;
2025-01-29 18:34:49 +08:00
public Action < SecurityStatus > ? OnStatusFilterPressed ;
2024-02-05 12:55:39 +00:00
private uint _maxLength ;
2024-02-04 23:29:35 +00:00
private bool _access ;
private uint? _selectedKey ;
private CriminalRecord ? _selectedRecord ;
private DialogWindow ? _reasonDialog ;
private StationRecordFilterType _currentFilterType ;
2025-01-29 18:34:49 +08:00
private SecurityStatus _currentCrewListFilter ;
2024-02-05 12:55:39 +00:00
public CriminalRecordsConsoleWindow ( EntityUid console , uint maxLength , IPlayerManager playerManager , IPrototypeManager prototypeManager , IRobustRandom robustRandom , AccessReaderSystem accessReader )
2024-02-04 23:29:35 +00:00
{
RobustXamlLoader . Load ( this ) ;
Console = console ;
_player = playerManager ;
_proto = prototypeManager ;
_random = robustRandom ;
_accessReader = accessReader ;
2025-01-29 18:34:49 +08:00
IoCManager . InjectDependencies ( this ) ;
_spriteSystem = _entManager . System < SpriteSystem > ( ) ;
2024-02-04 23:29:35 +00:00
2024-02-05 12:55:39 +00:00
_maxLength = maxLength ;
2024-02-04 23:29:35 +00:00
_currentFilterType = StationRecordFilterType . Name ;
2025-01-29 18:34:49 +08:00
_currentCrewListFilter = SecurityStatus . None ;
2024-02-04 23:29:35 +00:00
OpenCentered ( ) ;
foreach ( var item in Enum . GetValues < StationRecordFilterType > ( ) )
{
FilterType . AddItem ( GetTypeFilterLocals ( item ) , ( int ) item ) ;
}
foreach ( var status in Enum . GetValues < SecurityStatus > ( ) )
{
AddStatusSelect ( status ) ;
}
2025-01-29 18:34:49 +08:00
//Populate status to filter crew list
foreach ( var item in Enum . GetValues < SecurityStatus > ( ) )
{
CrewListFilter . AddItem ( GetCrewListFilterLocals ( item ) , ( int ) item ) ;
}
2024-02-04 23:29:35 +00:00
OnClose + = ( ) = > _reasonDialog ? . Close ( ) ;
RecordListing . OnItemSelected + = args = >
{
2024-08-09 08:41:29 +01:00
if ( RecordListing [ args . ItemIndex ] . Metadata is not uint cast )
2024-02-04 23:29:35 +00:00
return ;
OnKeySelected ? . Invoke ( cast ) ;
} ;
RecordListing . OnItemDeselected + = _ = >
{
2024-08-09 08:41:29 +01:00
OnKeySelected ? . Invoke ( null ) ;
2024-02-04 23:29:35 +00:00
} ;
FilterType . OnItemSelected + = eventArgs = >
{
var type = ( StationRecordFilterType ) eventArgs . Id ;
if ( _currentFilterType ! = type )
{
_currentFilterType = type ;
FilterListingOfRecords ( FilterText . Text ) ;
}
} ;
2025-01-29 18:34:49 +08:00
//Select Status to filter crew
CrewListFilter . OnItemSelected + = eventArgs = >
{
var type = ( SecurityStatus ) eventArgs . Id ;
if ( _currentCrewListFilter ! = type )
{
_currentCrewListFilter = type ;
StatusFilterPressed ( type ) ;
}
} ;
2024-02-04 23:29:35 +00:00
FilterText . OnTextEntered + = args = >
{
FilterListingOfRecords ( args . Text ) ;
} ;
StatusOptionButton . OnItemSelected + = args = >
{
2025-01-29 18:34:49 +08:00
SetStatus ( ( SecurityStatus ) args . Id ) ;
2024-02-04 23:29:35 +00:00
} ;
HistoryButton . OnPressed + = _ = >
{
2025-01-29 18:34:49 +08:00
if ( _selectedRecord is { } record )
2024-02-04 23:29:35 +00:00
OnHistoryUpdated ? . Invoke ( record , _access , true ) ;
} ;
}
2025-01-29 18:34:49 +08:00
public void StatusFilterPressed ( SecurityStatus statusSelected )
{
OnStatusFilterPressed ? . Invoke ( statusSelected ) ;
}
2024-02-04 23:29:35 +00:00
public void UpdateState ( CriminalRecordsConsoleState state )
{
if ( state . Filter ! = null )
{
if ( state . Filter . Type ! = _currentFilterType )
{
_currentFilterType = state . Filter . Type ;
}
if ( state . Filter . Value ! = FilterText . Text )
{
FilterText . Text = state . Filter . Value ;
}
}
2025-01-29 18:34:49 +08:00
if ( state . FilterStatus ! = _currentCrewListFilter )
{
_currentCrewListFilter = state . FilterStatus ;
}
2024-02-04 23:29:35 +00:00
2025-01-29 18:34:49 +08:00
_selectedKey = state . SelectedKey ;
2024-02-04 23:29:35 +00:00
FilterType . SelectId ( ( int ) _currentFilterType ) ;
2025-01-29 18:34:49 +08:00
CrewListFilter . SelectId ( ( int ) _currentCrewListFilter ) ;
2024-08-09 08:41:29 +01:00
NoRecords . Visible = state . RecordListing = = null | | state . RecordListing . Count = = 0 ;
PopulateRecordListing ( state . RecordListing ) ;
2024-02-04 23:29:35 +00:00
// set up the selected person's record
var selected = _selectedKey ! = null ;
PersonContainer . Visible = selected ;
RecordUnselected . Visible = ! selected ;
_access = _player . LocalSession ? . AttachedEntity is { } player
& & _accessReader . IsAllowed ( player , Console ) ;
// hide access-required editing parts when no access
var editing = _access & & selected ;
StatusOptionButton . Disabled = ! editing ;
if ( state is { CriminalRecord : not null , StationRecord : not null } )
{
PopulateRecordContainer ( state . StationRecord , state . CriminalRecord ) ;
OnHistoryUpdated ? . Invoke ( state . CriminalRecord , _access , false ) ;
_selectedRecord = state . CriminalRecord ;
}
else
{
_selectedRecord = null ;
OnHistoryClosed ? . Invoke ( ) ;
}
}
2024-08-09 08:41:29 +01:00
private void PopulateRecordListing ( Dictionary < uint , string > ? listing )
2024-02-04 23:29:35 +00:00
{
2024-08-09 08:41:29 +01:00
if ( listing = = null )
{
RecordListing . Clear ( ) ;
return ;
}
2025-04-17 11:15:35 +01:00
var entries = listing . Select ( i = > new ItemList . Item ( RecordListing ) {
Text = i . Value ,
Metadata = i . Key
} ) . ToList ( ) ;
entries . Sort ( ( a , b ) = > string . Compare ( a . Text , b . Text , StringComparison . Ordinal ) ) ;
RecordListing . SetItems ( entries , ( a , b ) = > string . Compare ( a . Text , b . Text ) ) ;
2024-02-04 23:29:35 +00:00
}
2025-04-17 11:15:35 +01:00
2024-02-04 23:29:35 +00:00
private void PopulateRecordContainer ( GeneralStationRecord stationRecord , CriminalRecord criminalRecord )
{
2025-01-29 18:34:49 +08:00
var specifier = new SpriteSpecifier . Rsi ( new ResPath ( "Interface/Misc/job_icons.rsi" ) , "Unknown" ) ;
2024-02-04 23:29:35 +00:00
var na = Loc . GetString ( "generic-not-available-shorthand" ) ;
PersonName . Text = stationRecord . Name ;
2025-01-29 18:34:49 +08:00
PersonJob . Text = stationRecord . JobTitle ? ? na ;
// Job icon
if ( _proto . TryIndex < JobIconPrototype > ( stationRecord . JobIcon , out var proto ) )
{
PersonJobIcon . Texture = _spriteSystem . Frame0 ( proto . Icon ) ;
}
PersonPrints . Text = stationRecord . Fingerprint ? ? Loc . GetString ( "generic-not-available-shorthand" ) ;
PersonDna . Text = stationRecord . DNA ? ? Loc . GetString ( "generic-not-available-shorthand" ) ;
if ( criminalRecord . Status ! = SecurityStatus . None )
{
specifier = new SpriteSpecifier . Rsi ( new ResPath ( "Interface/Misc/security_icons.rsi" ) , GetStatusIcon ( criminalRecord . Status ) ) ;
}
PersonStatusTX . SetFromSpriteSpecifier ( specifier ) ;
PersonStatusTX . DisplayRect . TextureScale = new Vector2 ( 3f , 3f ) ;
2024-02-04 23:29:35 +00:00
2025-01-29 18:34:49 +08:00
StatusOptionButton . SelectId ( ( int ) criminalRecord . Status ) ;
if ( criminalRecord . Reason is { } reason )
2024-02-04 23:29:35 +00:00
{
2024-09-01 14:00:53 +03:00
var message = FormattedMessage . FromMarkupOrThrow ( Loc . GetString ( "criminal-records-console-wanted-reason" ) ) ;
2025-01-29 18:34:49 +08:00
if ( criminalRecord . Status = = SecurityStatus . Suspected )
{
message = FormattedMessage . FromMarkupOrThrow ( Loc . GetString ( "criminal-records-console-suspected-reason" ) ) ;
}
2024-02-04 23:29:35 +00:00
message . AddText ( $": {reason}" ) ;
2025-01-29 18:34:49 +08:00
2024-02-04 23:29:35 +00:00
WantedReason . SetMessage ( message ) ;
WantedReason . Visible = true ;
}
else
{
WantedReason . Visible = false ;
}
}
private void AddStatusSelect ( SecurityStatus status )
{
var name = Loc . GetString ( $"criminal-records-status-{status.ToString().ToLower()}" ) ;
StatusOptionButton . AddItem ( name , ( int ) status ) ;
}
private void FilterListingOfRecords ( string text = "" )
{
2024-08-09 08:41:29 +01:00
OnFiltersChanged ? . Invoke ( _currentFilterType , text ) ;
2024-02-04 23:29:35 +00:00
}
private void SetStatus ( SecurityStatus status )
{
2024-03-11 04:12:52 +01:00
if ( status = = SecurityStatus . Wanted | | status = = SecurityStatus . Suspected )
2024-02-04 23:29:35 +00:00
{
2024-03-11 04:12:52 +01:00
GetReason ( status ) ;
2024-02-04 23:29:35 +00:00
return ;
}
OnStatusSelected ? . Invoke ( status ) ;
}
2024-03-11 04:12:52 +01:00
private void GetReason ( SecurityStatus status )
2024-02-04 23:29:35 +00:00
{
if ( _reasonDialog ! = null )
{
_reasonDialog . MoveToFront ( ) ;
return ;
}
var field = "reason" ;
2024-03-11 04:12:52 +01:00
var title = Loc . GetString ( "criminal-records-status-" + status . ToString ( ) . ToLower ( ) ) ;
2025-07-07 15:57:05 -04:00
var placeholders = _proto . Index ( ReasonPlaceholders ) ;
2025-03-17 02:43:37 +03:00
var placeholder = Loc . GetString ( "criminal-records-console-reason-placeholder" , ( "placeholder" , _random . Pick ( placeholders ) ) ) ; // just funny it doesn't actually get used
2024-02-04 23:29:35 +00:00
var prompt = Loc . GetString ( "criminal-records-console-reason" ) ;
var entry = new QuickDialogEntry ( field , QuickDialogEntryType . LongText , prompt , placeholder ) ;
var entries = new List < QuickDialogEntry > ( ) { entry } ;
_reasonDialog = new DialogWindow ( title , entries ) ;
_reasonDialog . OnConfirmed + = responses = >
{
var reason = responses [ field ] ;
2024-02-05 12:55:39 +00:00
if ( reason . Length < 1 | | reason . Length > _maxLength )
2024-02-04 23:29:35 +00:00
return ;
2024-03-11 04:12:52 +01:00
OnDialogConfirmed ? . Invoke ( status , reason ) ;
2024-02-04 23:29:35 +00:00
} ;
_reasonDialog . OnClose + = ( ) = > { _reasonDialog = null ; } ;
}
2025-01-29 18:34:49 +08:00
private string GetStatusIcon ( SecurityStatus status )
{
return status switch
{
SecurityStatus . Paroled = > "hud_paroled" ,
SecurityStatus . Wanted = > "hud_wanted" ,
SecurityStatus . Detained = > "hud_incarcerated" ,
SecurityStatus . Discharged = > "hud_discharged" ,
SecurityStatus . Suspected = > "hud_suspected" ,
_ = > "SecurityIconNone"
} ;
}
2024-02-04 23:29:35 +00:00
private string GetTypeFilterLocals ( StationRecordFilterType type )
{
return Loc . GetString ( $"criminal-records-{type.ToString().ToLower()}-filter" ) ;
}
2025-01-29 18:34:49 +08:00
private string GetCrewListFilterLocals ( SecurityStatus type )
{
string result ;
// If "NONE" override to "show all"
if ( type = = SecurityStatus . None )
{
result = Loc . GetString ( "criminal-records-console-show-all" ) ;
}
else
{
result = Loc . GetString ( $"criminal-records-status-{type.ToString().ToLower()}" ) ;
}
return result ;
}
2024-02-04 23:29:35 +00:00
}