--- /dev/null
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+public sealed partial class WantedListUi : UIFragment
+{
+ private WantedListUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new WantedListUiFragment();
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ switch (state)
+ {
+ case WantedListUiState cast:
+ _fragment?.UpdateState(cast.Records);
+ break;
+ }
+ }
+}
--- /dev/null
+using System.Linq;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.Security;
+using Content.Shared.StatusIcon;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.ResourceManagement;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class WantedListUiFragment : BoxContainer
+{
+ [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ private readonly SpriteSystem _spriteSystem;
+
+ private string? _selectedTargetName;
+ private List<WantedRecord> _wantedRecords = new();
+
+ public WantedListUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
+
+ SearchBar.OnTextChanged += OnSearchBarTextChanged;
+ }
+
+ private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
+ {
+ var found = !String.IsNullOrWhiteSpace(args.Text)
+ ? _wantedRecords.FindAll(r =>
+ r.TargetInfo.Name.Contains(args.Text) ||
+ r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase))
+ : _wantedRecords;
+
+ UpdateState(found, false);
+ }
+
+ public void UpdateState(List<WantedRecord> records, bool refresh = true)
+ {
+ if (records.Count == 0)
+ {
+ NoRecords.Visible = true;
+ RecordsList.Visible = false;
+ RecordUnselected.Visible = false;
+ PersonContainer.Visible = false;
+
+ _selectedTargetName = null;
+ if (refresh)
+ _wantedRecords.Clear();
+
+ RecordsList.PopulateList(new List<ListData>());
+
+ return;
+ }
+
+ NoRecords.Visible = false;
+ RecordsList.Visible = true;
+ RecordUnselected.Visible = true;
+ PersonContainer.Visible = false;
+
+ var dataList = records.Select(r => new StatusListData(r)).ToList();
+
+ RecordsList.GenerateItem = GenerateItem;
+ RecordsList.ItemPressed = OnItemSelected;
+ RecordsList.PopulateList(dataList);
+
+ if (refresh)
+ _wantedRecords = records;
+ }
+
+ private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data)
+ {
+ if (data is not StatusListData(var record))
+ return;
+
+ FormattedMessage GetLoc(string fluentId, params (string,object)[] args)
+ {
+ var msg = new FormattedMessage();
+ var fluent = Loc.GetString(fluentId, args);
+ msg.AddMarkupPermissive(fluent);
+ return msg;
+ }
+
+ // Set personal info
+ PersonName.Text = record.TargetInfo.Name;
+ TargetAge.SetMessage(GetLoc(
+ "wanted-list-age-label",
+ ("age", record.TargetInfo.Age)
+ ));
+ TargetJob.SetMessage(GetLoc(
+ "wanted-list-job-label",
+ ("job", record.TargetInfo.JobTitle.ToLower())
+ ));
+ TargetSpecies.SetMessage(GetLoc(
+ "wanted-list-species-label",
+ ("species", record.TargetInfo.Species.ToLower())
+ ));
+ TargetGender.SetMessage(GetLoc(
+ "wanted-list-gender-label",
+ ("gender", record.TargetInfo.Gender)
+ ));
+
+ // Set reason
+ WantedReason.SetMessage(GetLoc(
+ "wanted-list-reason-label",
+ ("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label"))
+ ));
+
+ // Set status
+ PersonState.SetMessage(GetLoc(
+ "wanted-list-status-label",
+ ("status", record.Status.ToString().ToLower())
+ ));
+
+ // Set initiator
+ InitiatorName.SetMessage(GetLoc(
+ "wanted-list-initiator-label",
+ ("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label"))
+ ));
+
+ // History table
+ // Clear table if it exists
+ HistoryTable.RemoveAllChildren();
+
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-time-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ });
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-reason-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ HorizontalExpand = true,
+ });
+
+ HistoryTable.AddChild(new Label()
+ {
+ Text = Loc.GetString("wanted-list-history-table-initiator-col"),
+ StyleClasses = { "LabelSmall" },
+ HorizontalAlignment = HAlignment.Center,
+ });
+
+ if (record.History.Count > 0)
+ {
+ HistoryTable.Visible = true;
+
+ foreach (var history in record.History.OrderByDescending(h => h.AddTime))
+ {
+ HistoryTable.AddChild(new Label()
+ {
+ Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}",
+ StyleClasses = { "LabelSmall" },
+ VerticalAlignment = VAlignment.Top,
+ });
+
+ HistoryTable.AddChild(new RichTextLabel()
+ {
+ Text = $"[color=white]{history.Crime}[/color]",
+ HorizontalExpand = true,
+ VerticalAlignment = VAlignment.Top,
+ StyleClasses = { "LabelSubText" },
+ Margin = new(10f, 0f),
+ });
+
+ HistoryTable.AddChild(new RichTextLabel()
+ {
+ Text = $"[color=white]{history.InitiatorName}[/color]",
+ StyleClasses = { "LabelSubText" },
+ VerticalAlignment = VAlignment.Top,
+ });
+ }
+ }
+
+ RecordUnselected.Visible = false;
+ PersonContainer.Visible = true;
+
+ // Save selected item
+ _selectedTargetName = record.TargetInfo.Name;
+ }
+
+ private void GenerateItem(ListData data, ListContainerButton button)
+ {
+ if (data is not StatusListData(var record))
+ return;
+
+ var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true };
+ var label = new Label() { Text = record.TargetInfo.Name };
+ var rect = new TextureRect()
+ {
+ TextureScale = new(2.2f),
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Margin = new(0f, 0f, 6f, 0f),
+ };
+
+ if (record.Status is not SecurityStatus.None)
+ {
+ var proto = "SecurityIcon" + record.Status switch
+ {
+ SecurityStatus.Detained => "Incarcerated",
+ _ => record.Status.ToString(),
+ };
+
+ if (_prototypeManager.TryIndex<SecurityIconPrototype>(proto, out var prototype))
+ {
+ rect.Texture = _spriteSystem.Frame0(prototype.Icon);
+ }
+ }
+
+ box.AddChild(rect);
+ box.AddChild(label);
+ button.AddChild(box);
+ button.AddStyleClass(ListContainer.StyleClassListContainerButton);
+
+ if (record.TargetInfo.Name.Equals(_selectedTargetName))
+ {
+ button.Pressed = true;
+ // For some reason the event is not called when `Pressed` changed, call it manually.
+ OnItemSelected(
+ new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())),
+ data);
+ }
+ }
+}
+
+internal record StatusListData(WantedRecord Record) : ListData;
--- /dev/null
+<cartridges:WantedListUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
+ xmlns="https://spacestation14.io"
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ Orientation="Vertical"
+ VerticalExpand="True"
+ HorizontalExpand="True">
+
+ <LineEdit Name="SearchBar" PlaceHolder="{Loc 'wanted-list-search-placeholder'}"/>
+
+ <BoxContainer Name="MainContainer" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
+ <Label Name="NoRecords" Text="{Loc 'wanted-list-label-no-records'}" Align="Center" VAlign="Center" HorizontalExpand="True" FontColorOverride="DarkGray"/>
+
+ <!-- Any attempts to set dimensions for ListContainer breaks the renderer, I have to roughly set sizes and margins in other controllers. -->
+ <controls:ListContainer
+ Name="RecordsList"
+ HorizontalAlignment="Left"
+ VerticalExpand="True"
+ Visible="False"
+ Toggle="True"
+ Group="True"
+ SetWidth="192" />
+
+ <Label Name="RecordUnselected"
+ Text="{Loc 'criminal-records-console-select-record-info'}"
+ Align="Center"
+ FontColorOverride="DarkGray"
+ Visible="False"
+ HorizontalExpand="True" />
+ <BoxContainer Name="PersonContainer" Orientation="Vertical" HorizontalExpand="True" SetWidth="334" Margin="5 0 77 0">
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+ <Label Name="PersonName" StyleClasses="LabelBig" />
+ <RichTextLabel Name="PersonState" HorizontalAlignment="Right" HorizontalExpand="True" />
+ </BoxContainer>
+ <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
+ <ScrollContainer VerticalExpand="True" HScrollEnabled="False">
+ <BoxContainer Name="DataContainer" Orientation="Vertical">
+ <RichTextLabel Name="TargetAge" />
+ <RichTextLabel Name="TargetJob" />
+ <RichTextLabel Name="TargetSpecies" />
+ <RichTextLabel Name="TargetGender" />
+ <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
+ <RichTextLabel Name="InitiatorName" VerticalAlignment="Stretch"/>
+ <RichTextLabel Name="WantedReason" VerticalAlignment="Stretch"/>
+ <PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
+ <controls:TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
+ </BoxContainer>
+ </ScrollContainer>
+ </BoxContainer>
+ </BoxContainer>
+</cartridges:WantedListUiFragment>
if (args.Container.ID != InstalledContainerId && args.Container.ID != loader.CartridgeSlot.ID)
return;
+ if (TryComp(args.Entity, out CartridgeComponent? cartridge))
+ cartridge.LoaderUid = uid;
+
RaiseLocalEvent(args.Entity, new CartridgeAddedEvent(uid));
base.OnItemInserted(uid, loader, args);
}
if (deactivate)
RaiseLocalEvent(args.Entity, new CartridgeDeactivatedEvent(uid));
+ if (TryComp(args.Entity, out CartridgeComponent? cartridge))
+ cartridge.LoaderUid = null;
+
RaiseLocalEvent(args.Entity, new CartridgeRemovedEvent(uid));
base.OnItemRemoved(uid, loader, args);
--- /dev/null
+using Content.Shared.Security;
+
+namespace Content.Server.CartridgeLoader.Cartridges;
+
+[RegisterComponent]
+public sealed partial class WantedListCartridgeComponent : Component
+{
+}
}
}
+ private void GetOfficer(EntityUid uid, out string officer)
+ {
+ var tryGetIdentityShortInfoEvent = new TryGetIdentityShortInfoEvent(null, uid);
+ RaiseLocalEvent(tryGetIdentityShortInfoEvent);
+ officer = tryGetIdentityShortInfoEvent.Title ?? Loc.GetString("criminal-records-console-unknown-officer");
+ }
+
private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordChangeStatus msg)
{
// prevent malf client violating wanted/reason nullability
return;
}
+ var oldStatus = record.Status;
+
+ var name = _records.RecordName(key.Value);
+ GetOfficer(mob.Value, out var officer);
+
// when arresting someone add it to history automatically
// fallback exists if the player was not set to wanted beforehand
if (msg.Status == SecurityStatus.Detained)
{
var oldReason = record.Reason ?? Loc.GetString("criminal-records-console-unspecified-reason");
var history = Loc.GetString("criminal-records-console-auto-history", ("reason", oldReason));
- _criminalRecords.TryAddHistory(key.Value, history);
+ _criminalRecords.TryAddHistory(key.Value, history, officer);
}
- var oldStatus = record.Status;
-
// will probably never fail given the checks above
- _criminalRecords.TryChangeStatus(key.Value, msg.Status, msg.Reason);
-
- var name = _records.RecordName(key.Value);
- var officer = Loc.GetString("criminal-records-console-unknown-officer");
-
- 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)
private void OnAddHistory(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordAddHistory msg)
{
- if (!CheckSelected(ent, msg.Actor, out _, out var key))
+ if (!CheckSelected(ent, msg.Actor, out var mob, out var key))
return;
var line = msg.Line.Trim();
if (line.Length < 1 || line.Length > ent.Comp.MaxStringLength)
return;
- if (!_criminalRecords.TryAddHistory(key.Value, line))
+ GetOfficer(mob.Value, out var officer);
+
+ if (!_criminalRecords.TryAddHistory(key.Value, line, officer))
return;
// no radio message since its not crucial to officers patrolling
-using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.CartridgeLoader;
+using Content.Server.CartridgeLoader.Cartridges;
using Content.Server.StationRecords.Systems;
using Content.Shared.CriminalRecords;
using Content.Shared.CriminalRecords.Systems;
using Content.Shared.Security;
using Content.Shared.StationRecords;
using Content.Server.GameTicking;
+using Content.Server.Station.Systems;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
namespace Content.Server.CriminalRecords.Systems;
{
[Dependency] private readonly GameTicker _ticker = default!;
[Dependency] private readonly StationRecordsSystem _records = default!;
+ [Dependency] private readonly StationSystem _station = default!;
+ [Dependency] private readonly CartridgeLoaderSystem _cartridge = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AfterGeneralRecordCreatedEvent>(OnGeneralRecordCreated);
+ SubscribeLocalEvent<WantedListCartridgeComponent, CriminalRecordChangedEvent>(OnRecordChanged);
+ SubscribeLocalEvent<WantedListCartridgeComponent, CartridgeUiReadyEvent>(OnCartridgeUiReady);
+ SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryAddedEvent>(OnHistoryAdded);
+ SubscribeLocalEvent<WantedListCartridgeComponent, CriminalHistoryRemovedEvent>(OnHistoryRemoved);
}
private void OnGeneralRecordCreated(AfterGeneralRecordCreatedEvent ev)
/// Reason should only be passed if status is Wanted, nullability isn't checked.
/// </summary>
/// <returns>True if the status is changed, false if not</returns>
- public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason)
+ public bool TryChangeStatus(StationRecordKey key, SecurityStatus status, string? reason, string? initiatorName = null)
{
// don't do anything if its the same status
if (!_records.TryGetRecord<CriminalRecord>(key, out var record)
|| status == record.Status)
return false;
- OverwriteStatus(key, record, status, reason);
+ OverwriteStatus(key, record, status, reason, initiatorName);
return true;
}
/// <summary>
/// Sets the status without checking previous status or reason nullability.
/// </summary>
- public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason)
+ public void OverwriteStatus(StationRecordKey key, CriminalRecord record, SecurityStatus status, string? reason, string? initiatorName = null)
{
record.Status = status;
record.Reason = reason;
+ record.InitiatorName = initiatorName;
var name = _records.RecordName(key);
if (name != string.Empty)
UpdateCriminalIdentity(name, status);
_records.Synchronize(key);
+
+ var args = new CriminalRecordChangedEvent(record);
+ var query = EntityQueryEnumerator<WantedListCartridgeComponent>();
+ while (query.MoveNext(out var readerUid, out _))
+ {
+ RaiseLocalEvent(readerUid, ref args);
+ }
}
/// <summary>
return false;
record.History.Add(entry);
+
+ var args = new CriminalHistoryAddedEvent(entry);
+ var query = EntityQueryEnumerator<WantedListCartridgeComponent>();
+ while (query.MoveNext(out var readerUid, out _))
+ {
+ RaiseLocalEvent(readerUid, ref args);
+ }
+
return true;
}
/// <summary>
/// Creates and tries to add a history entry using the current time.
/// </summary>
- public bool TryAddHistory(StationRecordKey key, string line)
+ public bool TryAddHistory(StationRecordKey key, string line, string? initiatorName = null)
{
- var entry = new CrimeHistory(_ticker.RoundDuration(), line);
+ var entry = new CrimeHistory(_ticker.RoundDuration(), line, initiatorName);
return TryAddHistory(key, entry);
}
if (index >= record.History.Count)
return false;
+ var history = record.History[(int)index];
record.History.RemoveAt((int) index);
+
+ var args = new CriminalHistoryRemovedEvent(history);
+ var query = EntityQueryEnumerator<WantedListCartridgeComponent>();
+ while (query.MoveNext(out var readerUid, out _))
+ {
+ RaiseLocalEvent(readerUid, ref args);
+ }
+
return true;
}
+
+ private void OnRecordChanged(Entity<WantedListCartridgeComponent> ent, ref CriminalRecordChangedEvent args) =>
+ StateChanged(ent);
+
+ private void OnHistoryAdded(Entity<WantedListCartridgeComponent> ent, ref CriminalHistoryAddedEvent args) =>
+ StateChanged(ent);
+
+ private void OnHistoryRemoved(Entity<WantedListCartridgeComponent> ent, ref CriminalHistoryRemovedEvent args) =>
+ StateChanged(ent);
+
+ private void StateChanged(Entity<WantedListCartridgeComponent> ent)
+ {
+ if (Comp<CartridgeComponent>(ent).LoaderUid is not { } loaderUid)
+ return;
+
+ UpdateReaderUi(ent, loaderUid);
+ }
+
+ private void OnCartridgeUiReady(Entity<WantedListCartridgeComponent> ent, ref CartridgeUiReadyEvent args)
+ {
+ UpdateReaderUi(ent, args.Loader);
+ }
+
+ private void UpdateReaderUi(Entity<WantedListCartridgeComponent> ent, EntityUid loaderUid)
+ {
+ if (_station.GetOwningStation(ent) is not { } station)
+ return;
+
+ var records = _records.GetRecordsOfType<CriminalRecord>(station)
+ .Where(cr => cr.Item2.Status is not SecurityStatus.None || cr.Item2.History.Count > 0)
+ .Select(cr =>
+ {
+ var (i, r) = cr;
+ var key = new StationRecordKey(i, station);
+ // Hopefully it will work smoothly.....
+ _records.TryGetRecord(key, out GeneralStationRecord? generalRecord);
+ return new WantedRecord(generalRecord!, r.Status, r.Reason, r.InitiatorName, r.History);
+ });
+ var state = new WantedListUiState(records.ToList());
+
+ _cartridge.UpdateCartridgeUiState(loaderUid, state);
+ }
}
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Components.Targets;
+using Content.Shared.CartridgeLoader;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
if (target.StealGroup != condition.StealGroup)
return 0;
+ // check if cartridge is installed
+ if (TryComp<CartridgeComponent>(entity, out var cartridge) &&
+ cartridge.InstallationStatus is not InstallationStatus.Cartridge)
+ return 0;
+
// check if needed target alive
if (condition.CheckAlive)
{
--- /dev/null
+using Content.Shared.CriminalRecords;
+using Content.Shared.CriminalRecords.Systems;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.CartridgeLoader.Cartridges;
+
+[Serializable, NetSerializable]
+public sealed class WantedListUiState(List<WantedRecord> records) : BoundUserInterfaceState
+{
+ public List<WantedRecord> Records = records;
+}
[DataField]
public string? Reason;
+ /// <summary>
+ /// The name of the person who changed the status.
+ /// </summary>
+ [DataField]
+ public string? InitiatorName;
+
/// <summary>
/// Criminal history of the person.
/// This should have charges and time served added after someone is detained.
/// A line of criminal activity and the time it was added at.
/// </summary>
[Serializable, NetSerializable]
-public record struct CrimeHistory(TimeSpan AddTime, string Crime);
+public record struct CrimeHistory(TimeSpan AddTime, string Crime, string? InitiatorName);
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Security;
using Content.Shared.Security.Components;
+using Content.Shared.StationRecords;
+using Robust.Shared.Serialization;
namespace Content.Shared.CriminalRecords.Systems;
Dirty(characterUid, record);
}
}
+
+[Serializable, NetSerializable]
+public struct WantedRecord(GeneralStationRecord targetInfo, SecurityStatus status, string? reason, string? initiator, List<CrimeHistory> history)
+{
+ public GeneralStationRecord TargetInfo = targetInfo;
+ public SecurityStatus Status = status;
+ public string? Reason = reason;
+ public string? Initiator = initiator;
+ public List<CrimeHistory> History = history;
+};
+
+[ByRefEvent]
+public record struct CriminalRecordChangedEvent(CriminalRecord Record);
+
+[ByRefEvent]
+public record struct CriminalHistoryAddedEvent(CrimeHistory History);
+
+[ByRefEvent]
+public record struct CriminalHistoryRemovedEvent(CrimeHistory History);
log-probe-label-time = Time
log-probe-label-accessor = Accessed by
log-probe-label-number = #
+
+# Wanted list cartridge
+wanted-list-program-name = Wanted list
+wanted-list-label-no-records = It's all right, cowboy
+wanted-list-search-placeholder = Search by name and status
+
+wanted-list-age-label = [color=darkgray]Age:[/color] [color=white]{$age}[/color]
+wanted-list-job-label = [color=darkgray]Job:[/color] [color=white]{$job}[/color]
+wanted-list-species-label = [color=darkgray]Species:[/color] [color=white]{$species}[/color]
+wanted-list-gender-label = [color=darkgray]Gender:[/color] [color=white]{$gender}[/color]
+
+wanted-list-reason-label = [color=darkgray]Reason:[/color] [color=white]{$reason}[/color]
+wanted-list-unknown-reason-label = unknown reason
+
+wanted-list-initiator-label = [color=darkgray]Initiator:[/color] [color=white]{$initiator}[/color]
+wanted-list-unknown-initiator-label = unknown initiator
+
+wanted-list-status-label = [color=darkgray]status:[/color] {$status ->
+ [suspected] [color=yellow]suspected[/color]
+ [wanted] [color=red]wanted[/color]
+ [detained] [color=#b18644]detained[/color]
+ [paroled] [color=green]paroled[/color]
+ [discharged] [color=green]discharged[/color]
+ *[other] none
+ }
+
+wanted-list-history-table-time-col = Time
+wanted-list-history-table-reason-col = Crime
+wanted-list-history-table-initiator-col = Initiator
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-unknown-officer = <unknown officer>
+criminal-records-console-unknown-officer = <unknown>
## Filters
steal-target-groups-bible = bible
steal-target-groups-clothing-neck-goldmedal = gold medal of crewmanship
steal-target-groups-clothing-neck-clownmedal = clown medal
+steal-target-groups-wanted-list-cartridge = wanted list cartridge
# Thief structures
steal-target-groups-teg = teg generator part
- id: RubberStampHos
- id: SecurityTechFabCircuitboard
- id: WeaponDisabler
+ - id: WantedListCartridge
# Hardsuit table, used for suit storage as well
- type: entityTable
- type: GuideHelp
guides:
- Forensics
+
+- type: entity
+ parent: BaseItem
+ id: WantedListCartridge
+ name: Wanted list cartridge
+ description: A program to get a list of wanted persons.
+ components:
+ - type: Sprite
+ sprite: Objects/Devices/cartridge.rsi
+ state: cart-sec
+ - type: Icon
+ sprite: Objects/Devices/cartridge.rsi
+ state: cart-sec
+ - type: UIFragment
+ ui: !type:WantedListUi
+ - type: Cartridge
+ programName: wanted-list-program-name
+ icon:
+ sprite: Objects/Misc/books.rsi
+ state: icon_magnifier
+ - type: WantedListCartridge
+ - type: StealTarget
+ stealGroup: WantedListCartridge
- type: Speech
speechVerb: Robotic
+- type: entity
+ id: BaseSecurityPDA
+ abstract: true
+ components:
+ - type: CartridgeLoader
+ uiKey: enum.PdaUiKey.Key
+ preinstalled:
+ - CrewManifestCartridge
+ - NotekeeperCartridge
+ - NewsReaderCartridge
+ - WantedListCartridge
+
- type: entity
parent: BasePDA
id: BaseMedicalPDA
state: pda-library
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: LawyerPDA
name: lawyer PDA
description: For lawyers to poach dubious clients.
state: pda-janitor
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: CaptainPDA
name: captain PDA
description: Surprisingly no different from your PDA.
state: pda-science
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: HoSPDA
name: head of security PDA
description: Whosoever bears this PDA is the law.
state: pda-hos
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: WardenPDA
name: warden PDA
description: The OS appears to have been jailbroken.
state: pda-warden
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: SecurityPDA
name: security PDA
description: Red to hide the stains of passenger blood.
state: pda-security
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: CentcomPDA
name: CentComm PDA
description: Light green sign of walking bureaucracy.
- NotekeeperCartridge
- NewsReaderCartridge
- LogProbeCartridge
+ - WantedListCartridge
- type: entity
parent: CentcomPDA
- Cartridge
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: ERTLeaderPDA
name: ERT Leader PDA
suffix: Leader
state: pda-boxer
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: DetectivePDA
name: detective PDA
description: Smells like rain... pouring down the rooftops...
state: pda-seniorphysician
- type: entity
- parent: BasePDA
+ parent: [BaseSecurityPDA, BasePDA]
id: SeniorOfficerPDA
name: senior officer PDA
description: Beaten, battered and broken, but just barely useable.
ForensicScannerStealObjective: 1 #sec
FlippoEngravedLighterStealObjective: 0.5
ClothingHeadHatWardenStealObjective: 1
+ WantedListCartridgeStealObjective: 1
ClothingOuterHardsuitVoidParamedStealObjective: 1 #med
MedicalTechFabCircuitboardStealObjective: 1
ClothingHeadsetAltMedicalStealObjective: 1
sprite: Clothing/Neck/Medals/clownmedal.rsi
state: icon
+- type: stealTargetGroup
+ id: WantedListCartridge
+ name: steal-target-groups-wanted-list-cartridge
+ sprite:
+ sprite: Objects/Devices/cartridge.rsi
+ state: cart-sec
+
#Thief structures
- type: stealTargetGroup
- type: Objective
difficulty: 1.2
+- type: entity
+ parent: BaseThiefStealObjective
+ id: WantedListCartridgeStealObjective
+ components:
+ - type: StealCondition
+ stealGroup: WantedListCartridge
+ - type: Objective
+ difficulty: 1
+
- type: entity #Medical subgroup
parent: BaseThiefStealObjective
id: ClothingOuterHardsuitVoidParamedStealObjective
{
"version": 1,
"license": "CC-BY-SA-3.0",
- "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github)",
+ "copyright": "Taken from vgstation at https://github.com/vgstation-coders/vgstation13/commit/1cdfb0230cc96d0ba751fa002d04f8aa2f25ad7d and tgstation at tgstation at https://github.com/tgstation/tgstation/commit/0c15d9dbcf0f2beb230eba5d9d889ef2d1945bb8, cart-log made by Skarletto (github), cart-sec made by dieselmohawk (discord)",
"size": {
"x": 32,
"y": 32
{
"name": "cart-y"
},
+ {
+ "name": "cart-sec"
+ },
{
"name": "insert_overlay"
}