mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Wanted list cartridge (#31223)
* WantedListCartridge has been added * WantedListCartridge user interface works * WantedListCartridge is added as standard in some PDAs * The CriminalRecordsSystem can now also take into account who created the record * Added offense history table * Fix of missing loaderUid for a cartridge without installing the program * Added personalized information about the target * The crime history has been finalized * Added StatusList * The officer's name has been added to the automatic history * WantedListCartridge has been added to the HOS locker * WantedListCartridge has been removed from brigmedic's preset programs * The StealConditionSystem now takes into account whether a cartridge is inserted or installed * Added target to thief on WantedListCartridge * Merge fix * Removing copypaste * Fix merge 2 * The sprite of WantedListCartridge has been changed * Update pda.yml * Fix scrollbar in the history table * Upstream localization fix * `StatusList` has been replaced by `ListContainer` with `TextureRect` * Margin fix
This commit is contained in:
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
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;
|
||||
@@ -0,0 +1,50 @@
|
||||
<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>
|
||||
@@ -340,6 +340,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
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);
|
||||
}
|
||||
@@ -360,6 +363,9 @@ public sealed class CartridgeLoaderSystem : SharedCartridgeLoaderSystem
|
||||
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);
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Security;
|
||||
|
||||
namespace Content.Server.CartridgeLoader.Cartridges;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class WantedListCartridgeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -68,6 +68,13 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -90,29 +97,22 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
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)
|
||||
@@ -152,14 +152,16 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
|
||||
|
||||
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
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
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;
|
||||
|
||||
@@ -20,12 +25,18 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
||||
{
|
||||
[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)
|
||||
@@ -39,14 +50,14 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
||||
/// 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;
|
||||
}
|
||||
@@ -54,16 +65,24 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
||||
/// <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>
|
||||
@@ -76,15 +95,23 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -100,7 +127,58 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -172,6 +173,11 @@ public sealed class StealConditionSystem : EntitySystem
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
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;
|
||||
}
|
||||
@@ -23,6 +23,12 @@ public sealed record CriminalRecord
|
||||
[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.
|
||||
@@ -35,4 +41,4 @@ public sealed record CriminalRecord
|
||||
/// 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);
|
||||
|
||||
@@ -2,6 +2,8 @@ using Content.Shared.IdentityManagement;
|
||||
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;
|
||||
|
||||
@@ -50,3 +52,22 @@ public abstract class SharedCriminalRecordsSystem : EntitySystem
|
||||
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);
|
||||
|
||||
@@ -19,3 +19,32 @@ log-probe-scan = Downloaded logs from {$device}!
|
||||
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
|
||||
|
||||
@@ -39,7 +39,7 @@ 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-unknown-officer = <unknown officer>
|
||||
criminal-records-console-unknown-officer = <unknown>
|
||||
|
||||
## Filters
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ steal-target-groups-clothing-eyes-hud-beer = beer goggles
|
||||
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
|
||||
|
||||
@@ -325,6 +325,7 @@
|
||||
- id: RubberStampHos
|
||||
- id: SecurityTechFabCircuitboard
|
||||
- id: WeaponDisabler
|
||||
- id: WantedListCartridge
|
||||
|
||||
# Hardsuit table, used for suit storage as well
|
||||
- type: entityTable
|
||||
|
||||
@@ -93,3 +93,26 @@
|
||||
- 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
|
||||
|
||||
@@ -114,6 +114,18 @@
|
||||
- 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
|
||||
@@ -433,7 +445,7 @@
|
||||
state: pda-library
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: LawyerPDA
|
||||
name: lawyer PDA
|
||||
description: For lawyers to poach dubious clients.
|
||||
@@ -476,7 +488,7 @@
|
||||
state: pda-janitor
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: CaptainPDA
|
||||
name: captain PDA
|
||||
description: Surprisingly no different from your PDA.
|
||||
@@ -651,7 +663,7 @@
|
||||
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.
|
||||
@@ -666,7 +678,7 @@
|
||||
state: pda-hos
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: WardenPDA
|
||||
name: warden PDA
|
||||
description: The OS appears to have been jailbroken.
|
||||
@@ -681,7 +693,7 @@
|
||||
state: pda-warden
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: SecurityPDA
|
||||
name: security PDA
|
||||
description: Red to hide the stains of passenger blood.
|
||||
@@ -695,7 +707,7 @@
|
||||
state: pda-security
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: CentcomPDA
|
||||
name: CentComm PDA
|
||||
description: Light green sign of walking bureaucracy.
|
||||
@@ -733,6 +745,7 @@
|
||||
- NotekeeperCartridge
|
||||
- NewsReaderCartridge
|
||||
- LogProbeCartridge
|
||||
- WantedListCartridge
|
||||
|
||||
- type: entity
|
||||
parent: CentcomPDA
|
||||
@@ -834,7 +847,7 @@
|
||||
- Cartridge
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: ERTLeaderPDA
|
||||
name: ERT Leader PDA
|
||||
suffix: Leader
|
||||
@@ -982,7 +995,7 @@
|
||||
state: pda-boxer
|
||||
|
||||
- type: entity
|
||||
parent: BasePDA
|
||||
parent: [BaseSecurityPDA, BasePDA]
|
||||
id: DetectivePDA
|
||||
name: detective PDA
|
||||
description: Smells like rain... pouring down the rooftops...
|
||||
@@ -1082,7 +1095,7 @@
|
||||
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.
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
ForensicScannerStealObjective: 1 #sec
|
||||
FlippoEngravedLighterStealObjective: 0.5
|
||||
ClothingHeadHatWardenStealObjective: 1
|
||||
WantedListCartridgeStealObjective: 1
|
||||
ClothingOuterHardsuitVoidParamedStealObjective: 1 #med
|
||||
MedicalTechFabCircuitboardStealObjective: 1
|
||||
ClothingHeadsetAltMedicalStealObjective: 1
|
||||
|
||||
@@ -263,6 +263,13 @@
|
||||
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
|
||||
|
||||
@@ -184,6 +184,15 @@
|
||||
- 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
|
||||
|
||||
BIN
Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png
Normal file
BIN
Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 314 B |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"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
|
||||
@@ -79,6 +79,9 @@
|
||||
{
|
||||
"name": "cart-y"
|
||||
},
|
||||
{
|
||||
"name": "cart-sec"
|
||||
},
|
||||
{
|
||||
"name": "insert_overlay"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user