SendMessage(new CriminalRecordChangeStatus(status, null));
_window.OnDialogConfirmed += (status, reason) =>
SendMessage(new CriminalRecordChangeStatus(status, reason));
+ _window.OnStatusFilterPressed += (statusFilter) =>
+ SendMessage(new CriminalRecordSetStatusFilter(statusFilter));
_window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close;
<controls:FancyWindow xmlns="https://spacestation14.io"
- xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
- Title="{Loc 'criminal-records-console-window-title'}"
- MinSize="660 400">
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ Title="{Loc 'criminal-records-console-window-title'}"
+ MinSize="695 440">
<BoxContainer Orientation="Vertical">
- <!-- Record search bar
- TODO: make this into a control shared with general records -->
- <BoxContainer Margin="5 5 5 10" HorizontalExpand="true" VerticalAlignment="Center">
- <OptionButton Name="FilterType" MinWidth="200" Margin="0 0 10 0"/> <!-- Populated in constructor -->
- <LineEdit Name="FilterText" PlaceHolder="{Loc 'criminal-records-filter-placeholder'}" HorizontalExpand="True"/>
- </BoxContainer>
- <BoxContainer Orientation="Horizontal" VerticalExpand="True">
- <!-- Record listing -->
- <BoxContainer Orientation="Vertical" Margin="5" MinWidth="250" MaxWidth="250">
- <Label Name="RecordListingTitle" Text="{Loc 'criminal-records-console-records-list-title'}" HorizontalExpand="True" Align="Center"/>
- <Label Name="NoRecords" Text="{Loc 'criminal-records-console-no-records'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
- <ScrollContainer VerticalExpand="True">
- <ItemList Name="RecordListing"/> <!-- Populated when loading state -->
- </ScrollContainer>
+ <BoxContainer Name="AllList"
+ Orientation="Vertical"
+ VerticalExpand="True"
+ HorizontalExpand="True"
+ Margin="8">
+ <!-- Record search bar -->
+ <BoxContainer Margin="5 5 5 10"
+ HorizontalExpand="true"
+ VerticalAlignment="Center">
+ <OptionButton Name="FilterType"
+ MinWidth="250"
+ Margin="0 0 10 0" />
+ <!-- Populated in constructor -->
+ <LineEdit Name="FilterText"
+ PlaceHolder="{Loc 'criminal-records-filter-placeholder'}"
+ HorizontalExpand="True" />
</BoxContainer>
- <Label Name="RecordUnselected" Text="{Loc 'criminal-records-console-select-record-info'}" HorizontalExpand="True" Align="Center" FontColorOverride="DarkGray"/>
- <!-- Selected record info -->
- <BoxContainer Name="PersonContainer" Orientation="Vertical" Margin="5" Visible="False">
- <Label Name="PersonName" StyleClasses="LabelBig"/>
- <Label Name="PersonPrints"/>
- <Label Name="PersonDna"/>
- <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
- <BoxContainer Orientation="Horizontal" Margin="5 5 5 5">
- <Label Name="StatusLabel" Text="{Loc 'criminal-records-console-status'}" FontColorOverride="DarkGray"/>
- <OptionButton Name="StatusOptionButton"/> <!-- Populated in constructor -->
+ <BoxContainer Orientation="Horizontal"
+ VerticalExpand="True">
+ <!-- Record listing -->
+ <BoxContainer Orientation="Vertical"
+ Margin="10 10"
+ MinWidth="250"
+ MaxWidth="250">
+ <Label Name="RecordListingTitle"
+ Text="{Loc 'criminal-records-console-records-list-title'}"
+ HorizontalExpand="True"
+ Align="Center" />
+ <Label Name="NoRecords"
+ Text="{Loc 'criminal-records-console-no-records'}"
+ HorizontalExpand="True"
+ Align="Center"
+ FontColorOverride="DarkGray" />
+ <ScrollContainer VerticalExpand="True">
+ <ItemList Name="RecordListing" />
+ <!-- Populated when loading state -->
+ </ScrollContainer>
+ </BoxContainer>
+ <Label Name="RecordUnselected"
+ Text="{Loc 'criminal-records-console-select-record-info'}"
+ HorizontalExpand="True"
+ Align="Center"
+ FontColorOverride="DarkGray" />
+ <!-- Selected record info -->
+ <BoxContainer Name="PersonContainer"
+ Orientation="Vertical"
+ VerticalExpand="True"
+ HorizontalExpand="True"
+ Margin="5"
+ Visible="False">
+ <Label Name="PersonName"
+ Margin="0 0 0 5"
+ StyleClasses="LabelBig" />
+ <BoxContainer Orientation="Horizontal"
+ Margin="0 0 0 5">
+ <Label Text="{Loc 'crew-monitoring-user-interface-job'}:"
+ FontColorOverride="DarkGray" />
+ <Label Text=":"
+ FontColorOverride="DarkGray" />
+ <TextureRect Name="PersonJobIcon"
+ TextureScale="2 2"
+ Margin="6 0"
+ VerticalAlignment="Center" />
+ <Label Name="PersonJob" />
+ </BoxContainer>
+ <BoxContainer Orientation="Horizontal"
+ Margin="0 0 0 5">
+ <Label Text="{Loc 'general-station-record-prints-filter'}"
+ FontColorOverride="DarkGray" />
+ <Label Text=":"
+ Margin="0 0 6 0"
+ FontColorOverride="DarkGray" />
+ <Label Name="PersonPrints" />
+ </BoxContainer>
+ <BoxContainer Orientation="Horizontal"
+ Margin="0 0 0 5">
+ <Label Text="{Loc 'general-station-record-dna-filter'}"
+ FontColorOverride="DarkGray" />
+ <Label Text=":"
+ Margin="0 0 6 0"
+ FontColorOverride="DarkGray" />
+ <Label Name="PersonDna" />
+ </BoxContainer>
+ <PanelContainer StyleClasses="LowDivider"
+ Margin="0 5 0 5" />
+ <BoxContainer Orientation="Horizontal"
+ Margin="0 5 0 5">
+ <Label Name="StatusLabel"
+ Text="{Loc 'criminal-records-console-status'}"
+ FontColorOverride="DarkGray" />
+ <Label Text=":"
+ FontColorOverride="DarkGray" />
+ <Label Name="PersonStatus"
+ FontColorOverride="DarkGray" />
+ <AnimatedTextureRect Name="PersonStatusTX"
+ Margin="8 0" />
+ <OptionButton Name="StatusOptionButton"
+ MinWidth="130" />
+ <!-- Populated in constructor -->
+ </BoxContainer>
+ <RichTextLabel Name="WantedReason"
+ Visible="False"
+ MaxWidth="425" />
+ <Button Name="HistoryButton"
+ Text="{Loc 'criminal-records-console-crime-history'}"
+ Margin="0 5" />
</BoxContainer>
- <RichTextLabel Name="WantedReason" Visible="False"/>
- <Button Name="HistoryButton" Text="{Loc 'criminal-records-console-crime-history'}"/>
+ </BoxContainer>
+ <BoxContainer Orientation="Horizontal"
+ Margin="0 0 0 5">
+ <OptionButton
+ Name="CrewListFilter"
+ MinWidth="250"
+ Margin="10 0 10 0" />
+ </BoxContainer>
+ </BoxContainer>
+ <!-- Footer -->
+ <BoxContainer Orientation="Vertical">
+ <PanelContainer StyleClasses="LowDivider" />
+ <BoxContainer Orientation="Horizontal"
+ Margin="10 2 5 0"
+ VerticalAlignment="Bottom">
+ <Label Text="{Loc 'criminal-records-console-flavor-left'}"
+ StyleClasses="WindowFooterText" />
+ <Label Text="{Loc 'criminal-records-console-flavor-right'}"
+ StyleClasses="WindowFooterText"
+ HorizontalAlignment="Right"
+ HorizontalExpand="True"
+ Margin="0 0 5 0" />
+ <TextureRect StyleClasses="NTLogoDark"
+ Stretch="KeepAspectCentered"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Right"
+ SetSize="19 19" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
+using System.Numerics;
+using Content.Shared.StatusIcon;
+using Robust.Client.GameObjects;
namespace Content.Client.CriminalRecords;
private readonly IPrototypeManager _proto;
private readonly IRobustRandom _random;
private readonly AccessReaderSystem _accessReader;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ private readonly SpriteSystem _spriteSystem;
public readonly EntityUid Console;
public Action<uint?>? OnKeySelected;
public Action<StationRecordFilterType, string>? OnFiltersChanged;
public Action<SecurityStatus>? OnStatusSelected;
+ public Action<uint>? OnCheckStatus;
public Action<CriminalRecord, bool, bool>? OnHistoryUpdated;
public Action? OnHistoryClosed;
public Action<SecurityStatus, string>? OnDialogConfirmed;
+ public Action<SecurityStatus>? OnStatusFilterPressed;
private uint _maxLength;
private bool _access;
private uint? _selectedKey;
private StationRecordFilterType _currentFilterType;
+ private SecurityStatus _currentCrewListFilter;
+
public CriminalRecordsConsoleWindow(EntityUid console, uint maxLength, IPlayerManager playerManager, IPrototypeManager prototypeManager, IRobustRandom robustRandom, AccessReaderSystem accessReader)
{
RobustXamlLoader.Load(this);
_proto = prototypeManager;
_random = robustRandom;
_accessReader = accessReader;
+ IoCManager.InjectDependencies(this);
+ _spriteSystem = _entManager.System<SpriteSystem>();
_maxLength = maxLength;
_currentFilterType = StationRecordFilterType.Name;
+ _currentCrewListFilter = SecurityStatus.None;
+
OpenCentered();
foreach (var item in Enum.GetValues<StationRecordFilterType>())
AddStatusSelect(status);
}
+ //Populate status to filter crew list
+ foreach (var item in Enum.GetValues<SecurityStatus>())
+ {
+ CrewListFilter.AddItem(GetCrewListFilterLocals(item), (int)item);
+ }
+
OnClose += () => _reasonDialog?.Close();
RecordListing.OnItemSelected += args =>
}
};
+ //Select Status to filter crew
+ CrewListFilter.OnItemSelected += eventArgs =>
+ {
+ var type = (SecurityStatus)eventArgs.Id;
+
+ if (_currentCrewListFilter != type)
+ {
+ _currentCrewListFilter = type;
+
+ StatusFilterPressed(type);
+
+ }
+ };
+
FilterText.OnTextEntered += args =>
{
FilterListingOfRecords(args.Text);
StatusOptionButton.OnItemSelected += args =>
{
- SetStatus((SecurityStatus) args.Id);
+ SetStatus((SecurityStatus)args.Id);
};
HistoryButton.OnPressed += _ =>
{
- if (_selectedRecord is {} record)
+ if (_selectedRecord is { } record)
OnHistoryUpdated?.Invoke(record, _access, true);
};
}
+ public void StatusFilterPressed(SecurityStatus statusSelected)
+ {
+ OnStatusFilterPressed?.Invoke(statusSelected);
+ }
+
public void UpdateState(CriminalRecordsConsoleState state)
{
if (state.Filter != null)
}
}
- _selectedKey = state.SelectedKey;
+ if (state.FilterStatus != _currentCrewListFilter)
+ {
+ _currentCrewListFilter = state.FilterStatus;
+ }
+ _selectedKey = state.SelectedKey;
FilterType.SelectId((int)_currentFilterType);
-
+ CrewListFilter.SelectId((int)_currentCrewListFilter);
NoRecords.Visible = state.RecordListing == null || state.RecordListing.Count == 0;
PopulateRecordListing(state.RecordListing);
// in parallel to synchronize the items in RecordListing with `entries`.
int i = RecordListing.Count - 1;
int j = entries.Count - 1;
- while(i >= 0 && j >= 0)
+ while (i >= 0 && j >= 0)
{
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
if (strcmp == 0)
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
while (j >= 0)
{
- RecordListing.Insert(0, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
+ RecordListing.Insert(0, new ItemList.Item(RecordListing){ Text = entries[j].Value, Metadata = entries[j].Key });
j--;
}
}
-
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
{
+ var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");
var na = Loc.GetString("generic-not-available-shorthand");
PersonName.Text = stationRecord.Name;
- PersonPrints.Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", stationRecord.Fingerprint ?? na));
- PersonDna.Text = Loc.GetString("general-station-record-console-record-dna", ("dna", stationRecord.DNA ?? na));
+ 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);
- StatusOptionButton.SelectId((int) criminalRecord.Status);
- if (criminalRecord.Reason is {} reason)
+ StatusOptionButton.SelectId((int)criminalRecord.Status);
+ if (criminalRecord.Reason is { } reason)
{
var message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-wanted-reason"));
+
+ if (criminalRecord.Status == SecurityStatus.Suspected)
+ {
+ message = FormattedMessage.FromMarkupOrThrow(Loc.GetString("criminal-records-console-suspected-reason"));
+ }
message.AddText($": {reason}");
+
WantedReason.SetMessage(message);
WantedReason.Visible = true;
}
_reasonDialog.OnClose += () => { _reasonDialog = null; };
}
-
+ 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"
+ };
+ }
private string GetTypeFilterLocals(StationRecordFilterType type)
{
return Loc.GetString($"criminal-records-{type.ToString().ToLower()}-filter");
}
+
+ 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;
+ }
}
using System.Diagnostics.CodeAnalysis;
using Content.Shared.IdentityManagement;
using Content.Shared.Security.Components;
+using System.Linq;
+using Content.Shared.Roles.Jobs;
namespace Content.Server.CriminalRecords.Systems;
subs.Event<CriminalRecordChangeStatus>(OnChangeStatus);
subs.Event<CriminalRecordAddHistory>(OnAddHistory);
subs.Event<CriminalRecordDeleteHistory>(OnDeleteHistory);
+ subs.Event<CriminalRecordSetStatusFilter>(OnStatusFilterPressed);
});
}
ent.Comp.ActiveKey = msg.SelectedKey;
UpdateUserInterface(ent);
}
+ private void OnStatusFilterPressed(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordSetStatusFilter msg)
+ {
+ ent.Comp.FilterStatus = msg.FilterStatus;
+ UpdateUserInterface(ent);
+ }
private void OnFiltersChanged(Entity<CriminalRecordsConsoleComponent> ent, ref SetStationRecordFilter msg)
{
}
// will probably never fail given the checks above
+ name = _records.RecordName(key.Value);
+ officer = Loc.GetString("criminal-records-console-unknown-officer");
+ var jobName = "Unknown";
+
+ _records.TryGetRecord<GeneralStationRecord>(key.Value, out var entry);
+ if (entry != null)
+ jobName = entry.JobTitle;
+
+ var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, mob.Value);
+ RaiseLocalEvent(tryGetIdentityShortInfoEvent);
+ if (tryGetIdentityShortInfoEvent.Title != null)
+ officer = tryGetIdentityShortInfoEvent.Title;
+
_criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason, officer);
(string, object)[] args;
if (reason != null)
- args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason) };
+ args = new (string, object)[] { ("name", name), ("officer", officer), ("reason", reason), ("job", jobName) };
else
- args = new (string, object)[] { ("name", name), ("officer", officer) };
+ args = new (string, object)[] { ("name", name), ("officer", officer), ("job", jobName) };
// figure out which radio message to send depending on transition
var statusString = (oldStatus, msg.Status) switch
return;
}
+ // get the listing of records to display
var listing = _records.BuildListing((owningStation.Value, stationRecords), console.Filter);
+ // filter the listing by the selected criminal record status
+ //if NONE, dont filter by status, just show all crew
+ if (console.FilterStatus != SecurityStatus.None)
+ {
+ listing = listing
+ .Where(x => _records.TryGetRecord<CriminalRecord>(new StationRecordKey(x.Key, owningStation.Value), out var record) && record.Status == console.FilterStatus)
+ .ToDictionary(x => x.Key, x => x.Value);
+ }
+
var state = new CriminalRecordsConsoleState(listing, console.Filter);
if (console.ActiveKey is { } id)
{
state.SelectedKey = id;
}
+ // Set the Current Tab aka the filter status type for the records list
+ state.FilterStatus = console.FilterStatus;
+
_ui.SetUiState(uid, CriminalRecordsConsoleKey.Key, state);
}
using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.CriminalRecords.Components;
+using Content.Shared.CriminalRecords;
using Content.Shared.Radio;
using Content.Shared.StationRecords;
using Robust.Shared.Prototypes;
+using Content.Shared.Security;
namespace Content.Shared.CriminalRecords.Components;
[DataField]
public StationRecordsFilter? Filter;
+ /// <summary>
+ /// Current seleced security status for the filter by criminal status dropdown.
+ /// </summary>
+ [DataField]
+ public SecurityStatus FilterStatus;
+
/// <summary>
/// Channel to send messages to when someone's status gets changed.
/// </summary>
/// Currently selected crewmember record key.
/// </summary>
public uint? SelectedKey = null;
-
public CriminalRecord? CriminalRecord = null;
public GeneralStationRecord? StationRecord = null;
+ public SecurityStatus FilterStatus = SecurityStatus.None;
public readonly Dictionary<uint, string>? RecordListing;
public readonly StationRecordsFilter? Filter;
Index = index;
}
}
+
+/// <summary>
+/// Used to set what status to filter by index.
+///
+/// </summary>
+///
+[Serializable, NetSerializable]
+
+public sealed class CriminalRecordSetStatusFilter : BoundUserInterfaceMessage
+{
+ public readonly SecurityStatus FilterStatus;
+ public CriminalRecordSetStatusFilter(SecurityStatus newFilterStatus)
+ {
+ FilterStatus = newFilterStatus;
+ }
+}
+
criminal-records-console-select-record-info = Select a record.
criminal-records-console-no-records = No records found!
criminal-records-console-no-record-found = No record was found for the selected person.
+criminal-records-console-flavor-left = Arrest first! Ask questions later.
+criminal-records-console-flavor-right = v2.1
+criminal-records-console-show-all = All
## Status
criminal-records-status-discharged = Discharged
criminal-records-status-paroled = Paroled
-criminal-records-console-wanted-reason = [color=gray]Wanted Reason[/color]
-criminal-records-console-suspected-reason = [color=gray]Suspected Reason[/color]
+criminal-records-console-wanted-reason = Wanted Reason
+criminal-records-console-suspected-reason = Suspected Reason
criminal-records-console-reason = Reason
criminal-records-console-reason-placeholder = For example: {$placeholder}
## Security channel notifications
-criminal-records-console-wanted = {$name} was made wanted by {$officer} for: {$reason}.
-criminal-records-console-suspected = {$officer} marked {$name} as suspicious because of: {$reason}
-criminal-records-console-not-suspected = {$name} has been cleared of suspicion by {$officer}.
-criminal-records-console-detained = {$name} has been detained by {$officer}.
-criminal-records-console-released = {$name} has been released by {$officer}.
-criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name}.
-criminal-records-console-paroled = {$name} has been released on parole by {$officer}.
-criminal-records-console-not-parole = {$officer} cleared the parole status of {$name}.
+criminal-records-console-wanted = {$name} ({$job}) was made wanted by {$officer} for: {$reason}.
+criminal-records-console-suspected = {$officer} marked {$name} ({$job}) as suspicious because of: {$reason}
+criminal-records-console-not-suspected = {$name} ({$job}) has been cleared of suspicion by {$officer}.
+criminal-records-console-detained = {$name} ({$job}) has been detained by {$officer}.
+criminal-records-console-released = {$name} ({$job}) has been released by {$officer}.
+criminal-records-console-not-wanted = {$officer} cleared the wanted status of {$name} ($job).
+criminal-records-console-paroled = {$name} ({$job}) has been released on parole by {$officer}.
+criminal-records-console-not-parole = {$officer} cleared the parole status of {$name} ({$job}).
criminal-records-console-unknown-officer = <unknown>
## Filters
- The criminal records themselves
+ - The filter button below the crew list can be used to show only wanted, detained, or paroled crew.
In the record section you can:
- See security-related information about a crewmember like their name, fingerprints and DNA.