From 1468cbdb8a59beb2dfc9188a3108157496549a57 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=D0=AD=D0=B4=D1=83=D0=B0=D1=80=D0=B4?= <36124833+Ertanic@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:22:02 +0300 Subject: [PATCH] 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 --- .../Cartridges/WantedListUi.cs | 30 +++ .../Cartridges/WantedListUiFragment.cs | 240 ++++++++++++++++++ .../Cartridges/WantedListUiFragment.xaml | 50 ++++ .../CartridgeLoader/CartridgeLoaderSystem.cs | 6 + .../Cartridges/WantedListCartridge.cs | 8 + .../Systems/CriminalRecordsConsoleSystem.cs | 34 +-- .../Systems/CriminalRecordsSystem.cs | 90 ++++++- .../Systems/StealConditionSystem.cs | 6 + .../Cartridges/WantedListUiState.cs | 11 + .../CriminalRecords/CriminalRecord.cs | 8 +- .../Systems/SharedCriminalRecordsSystem.cs | 21 ++ .../en-US/cartridge-loader/cartridges.ftl | 29 +++ .../criminal-records/criminal-records.ftl | 2 +- .../conditions/steal-target-groups.ftl | 1 + .../Catalog/Fills/Lockers/heads.yml | 1 + .../Entities/Objects/Devices/cartridges.yml | 23 ++ .../Entities/Objects/Devices/pda.yml | 31 ++- .../Prototypes/Objectives/objectiveGroups.yml | 1 + .../Objectives/stealTargetGroups.yml | 7 + Resources/Prototypes/Objectives/thief.yml | 9 + .../Devices/cartridge.rsi/cart-sec.png | Bin 0 -> 314 bytes .../Objects/Devices/cartridge.rsi/meta.json | 5 +- 22 files changed, 579 insertions(+), 34 deletions(-) create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs create mode 100644 Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml create mode 100644 Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs create mode 100644 Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs create mode 100644 Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs new file mode 100644 index 0000000000..3c97b8b37d --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs @@ -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; + } + } +} diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs new file mode 100644 index 0000000000..4137f6c2af --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.cs @@ -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 _wantedRecords = new(); + + public WantedListUiFragment() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _spriteSystem = _entitySystem.GetEntitySystem(); + + 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 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()); + + 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(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; diff --git a/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml new file mode 100644 index 0000000000..7b5d116ad7 --- /dev/null +++ b/Content.Client/CartridgeLoader/Cartridges/WantedListUiFragment.xaml @@ -0,0 +1,50 @@ + + + + + + + diff --git a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs index cd422328c3..7caec6150e 100644 --- a/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs +++ b/Content.Server/CartridgeLoader/CartridgeLoaderSystem.cs @@ -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); diff --git a/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs new file mode 100644 index 0000000000..08eef62379 --- /dev/null +++ b/Content.Server/CartridgeLoader/Cartridges/WantedListCartridge.cs @@ -0,0 +1,8 @@ +using Content.Shared.Security; + +namespace Content.Server.CartridgeLoader.Cartridges; + +[RegisterComponent] +public sealed partial class WantedListCartridgeComponent : Component +{ +} diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index c5f1d159f3..ca1d45e644 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -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 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 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 diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs index a65fb0be9e..7c65ce8c24 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsSystem.cs @@ -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(OnGeneralRecordCreated); + SubscribeLocalEvent(OnRecordChanged); + SubscribeLocalEvent(OnCartridgeUiReady); + SubscribeLocalEvent(OnHistoryAdded); + SubscribeLocalEvent(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. /// /// True if the status is changed, false if not - 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(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 /// /// Sets the status without checking previous status or reason nullability. /// - 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(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } } /// @@ -76,15 +95,23 @@ public sealed class CriminalRecordsSystem : SharedCriminalRecordsSystem return false; record.History.Add(entry); + + var args = new CriminalHistoryAddedEvent(entry); + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } /// /// Creates and tries to add a history entry using the current time. /// - 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(); + while (query.MoveNext(out var readerUid, out _)) + { + RaiseLocalEvent(readerUid, ref args); + } + return true; } + + private void OnRecordChanged(Entity ent, ref CriminalRecordChangedEvent args) => + StateChanged(ent); + + private void OnHistoryAdded(Entity ent, ref CriminalHistoryAddedEvent args) => + StateChanged(ent); + + private void OnHistoryRemoved(Entity ent, ref CriminalHistoryRemovedEvent args) => + StateChanged(ent); + + private void StateChanged(Entity ent) + { + if (Comp(ent).LoaderUid is not { } loaderUid) + return; + + UpdateReaderUi(ent, loaderUid); + } + + private void OnCartridgeUiReady(Entity ent, ref CartridgeUiReadyEvent args) + { + UpdateReaderUi(ent, args.Loader); + } + + private void UpdateReaderUi(Entity ent, EntityUid loaderUid) + { + if (_station.GetOwningStation(ent) is not { } station) + return; + + var records = _records.GetRecordsOfType(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); + } } diff --git a/Content.Server/Objectives/Systems/StealConditionSystem.cs b/Content.Server/Objectives/Systems/StealConditionSystem.cs index be34a80fe3..e2d81e011c 100644 --- a/Content.Server/Objectives/Systems/StealConditionSystem.cs +++ b/Content.Server/Objectives/Systems/StealConditionSystem.cs @@ -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(entity, out var cartridge) && + cartridge.InstallationStatus is not InstallationStatus.Cartridge) + return 0; + // check if needed target alive if (condition.CheckAlive) { diff --git a/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs new file mode 100644 index 0000000000..9d55e0c163 --- /dev/null +++ b/Content.Shared/CartridgeLoader/Cartridges/WantedListUiState.cs @@ -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 records) : BoundUserInterfaceState +{ + public List Records = records; +} diff --git a/Content.Shared/CriminalRecords/CriminalRecord.cs b/Content.Shared/CriminalRecords/CriminalRecord.cs index 0fe23d4395..5a023a9188 100644 --- a/Content.Shared/CriminalRecords/CriminalRecord.cs +++ b/Content.Shared/CriminalRecords/CriminalRecord.cs @@ -23,6 +23,12 @@ public sealed record CriminalRecord [DataField] public string? Reason; + /// + /// The name of the person who changed the status. + /// + [DataField] + public string? InitiatorName; + /// /// 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. /// [Serializable, NetSerializable] -public record struct CrimeHistory(TimeSpan AddTime, string Crime); +public record struct CrimeHistory(TimeSpan AddTime, string Crime, string? InitiatorName); diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs index 96b33ab91b..d665d32f1e 100644 --- a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs +++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsSystem.cs @@ -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 history) +{ + public GeneralStationRecord TargetInfo = targetInfo; + public SecurityStatus Status = status; + public string? Reason = reason; + public string? Initiator = initiator; + public List History = history; +}; + +[ByRefEvent] +public record struct CriminalRecordChangedEvent(CriminalRecord Record); + +[ByRefEvent] +public record struct CriminalHistoryAddedEvent(CrimeHistory History); + +[ByRefEvent] +public record struct CriminalHistoryRemovedEvent(CrimeHistory History); diff --git a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl index f5cda2f2a1..2db27f5be0 100644 --- a/Resources/Locale/en-US/cartridge-loader/cartridges.ftl +++ b/Resources/Locale/en-US/cartridge-loader/cartridges.ftl @@ -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 diff --git a/Resources/Locale/en-US/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/criminal-records/criminal-records.ftl index 6d6a97300c..2a7c09912f 100644 --- a/Resources/Locale/en-US/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/criminal-records/criminal-records.ftl @@ -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 = +criminal-records-console-unknown-officer = ## Filters diff --git a/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl b/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl index 91b3c92b1c..689e2e7808 100644 --- a/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl +++ b/Resources/Locale/en-US/objectives/conditions/steal-target-groups.ftl @@ -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 diff --git a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml index d318963016..31ebad6183 100644 --- a/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml +++ b/Resources/Prototypes/Catalog/Fills/Lockers/heads.yml @@ -325,6 +325,7 @@ - id: RubberStampHos - id: SecurityTechFabCircuitboard - id: WeaponDisabler + - id: WantedListCartridge # Hardsuit table, used for suit storage as well - type: entityTable diff --git a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml index f9581149e2..91493f48cd 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/cartridges.yml @@ -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 diff --git a/Resources/Prototypes/Entities/Objects/Devices/pda.yml b/Resources/Prototypes/Entities/Objects/Devices/pda.yml index 40f6f77e12..48e7a28deb 100644 --- a/Resources/Prototypes/Entities/Objects/Devices/pda.yml +++ b/Resources/Prototypes/Entities/Objects/Devices/pda.yml @@ -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. diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 481ca0f93d..e72de0d94a 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -73,6 +73,7 @@ ForensicScannerStealObjective: 1 #sec FlippoEngravedLighterStealObjective: 0.5 ClothingHeadHatWardenStealObjective: 1 + WantedListCartridgeStealObjective: 1 ClothingOuterHardsuitVoidParamedStealObjective: 1 #med MedicalTechFabCircuitboardStealObjective: 1 ClothingHeadsetAltMedicalStealObjective: 1 diff --git a/Resources/Prototypes/Objectives/stealTargetGroups.yml b/Resources/Prototypes/Objectives/stealTargetGroups.yml index 48f56e2bfc..09619bf986 100644 --- a/Resources/Prototypes/Objectives/stealTargetGroups.yml +++ b/Resources/Prototypes/Objectives/stealTargetGroups.yml @@ -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 diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index f8e44d831e..7a46d0f5e9 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -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 diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png b/Resources/Textures/Objects/Devices/cartridge.rsi/cart-sec.png new file mode 100644 index 0000000000000000000000000000000000000000..6a3197004cbf0460f517049aae94986a820100c8 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}w>(`OLn2z= zPBG+bHsEosU!^CfG-YaI@P@Arw!!>Ij%i9n2!48CD9e?Vk(r#al<({8fG=PF9}i=a zXiNNK?We4?Xvgswmreg783m87T6N&e>_dCMzu2W0{b+?@a@w5Va_S66OTvzKT-&+) zy`Tg0gP>`v7O_v|ifB21#c)yI>9-=2!@74lNZGY-JEyyB!?LprqYi4z{jcMH#o42s z{*tBWRzJ5%XQ9=%64~O4%?le|Nwo=>Mck8YduV;#K$H3X-)LTAXO}DUR*80WXvyrb zb#Pgt_wn=c8wNawcQEPn`j%LP9bO0YC0}ZUXPU1sgBFm@0mLA1DR?r7@^tlcS?83{ F1ONqldhh@M literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json index f3b02a2b2e..d5fad56006 100644 --- a/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json +++ b/Resources/Textures/Objects/Devices/cartridge.rsi/meta.json @@ -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" } -- 2.52.0