diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs index 88b9c90c2ff..9047624f49b 100644 --- a/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs +++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleBoundUserInterface.cs @@ -37,8 +37,8 @@ protected override void Open() SendMessage(new SetStationRecordFilter(type, filterValue)); _window.OnStatusSelected += status => SendMessage(new CriminalRecordChangeStatus(status, null)); - _window.OnDialogConfirmed += (_, reason) => - SendMessage(new CriminalRecordChangeStatus(SecurityStatus.Wanted, reason)); + _window.OnDialogConfirmed += (status, reason) => + SendMessage(new CriminalRecordChangeStatus(status, reason)); _window.OnHistoryUpdated += UpdateHistory; _window.OnHistoryClosed += () => _historyWindow?.Close(); _window.OnClose += Close; diff --git a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs index 61b5c383315..b259e08e723 100644 --- a/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs +++ b/Content.Client/CriminalRecords/CriminalRecordsConsoleWindow.xaml.cs @@ -219,16 +219,16 @@ private void FilterListingOfRecords(string text = "") private void SetStatus(SecurityStatus status) { - if (status == SecurityStatus.Wanted) + if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected) { - GetWantedReason(); + GetReason(status); return; } OnStatusSelected?.Invoke(status); } - private void GetWantedReason() + private void GetReason(SecurityStatus status) { if (_reasonDialog != null) { @@ -237,7 +237,7 @@ private void GetWantedReason() } var field = "reason"; - var title = Loc.GetString("criminal-records-status-wanted"); + var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower()); var placeholders = _proto.Index(ReasonPlaceholders); var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used var prompt = Loc.GetString("criminal-records-console-reason"); @@ -251,7 +251,7 @@ private void GetWantedReason() if (reason.Length < 1 || reason.Length > _maxLength) return; - OnDialogConfirmed?.Invoke(SecurityStatus.Wanted, reason); + OnDialogConfirmed?.Invoke(status, reason); }; _reasonDialog.OnClose += () => { _reasonDialog = null; }; diff --git a/Content.Client/Overlays/ShowSecurityIconsSystem.cs b/Content.Client/Overlays/ShowSecurityIconsSystem.cs index 77c14c5ef0f..7a4abd05e00 100644 --- a/Content.Client/Overlays/ShowSecurityIconsSystem.cs +++ b/Content.Client/Overlays/ShowSecurityIconsSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Mindshield.Components; using Content.Shared.Overlays; using Content.Shared.PDA; +using Content.Shared.Security.Components; using Content.Shared.StatusIcon; using Content.Shared.StatusIcon.Components; using Robust.Shared.Prototypes; @@ -74,7 +75,11 @@ private IReadOnlyList DecideSecurityIcon(EntityUid uid) result.Add(icon); } - // Add arrest icons here, WYCI. + if (TryComp(uid, out var record)) + { + if(_prototypeMan.TryIndex(record.StatusIcon.Id, out var criminalIcon)) + result.Add(criminalIcon); + } return result; } diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs index 5760f830016..53eabb1a481 100644 --- a/Content.Server/Administration/Systems/AdminSystem.cs +++ b/Content.Server/Administration/Systems/AdminSystem.cs @@ -134,7 +134,7 @@ public void UpdatePlayerList(ICommonSession player) return value ?? null; } - private void OnIdentityChanged(IdentityChangedEvent ev) + private void OnIdentityChanged(ref IdentityChangedEvent ev) { if (!TryComp(ev.CharacterEntity, out var actor)) return; diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs index 84f8ffc2e50..4290726cc40 100644 --- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs +++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs @@ -12,6 +12,8 @@ using Robust.Server.GameObjects; using Robust.Shared.Player; using System.Diagnostics.CodeAnalysis; +using Content.Shared.IdentityManagement; +using Content.Shared.Security.Components; namespace Content.Server.CriminalRecords.Systems; @@ -71,7 +73,8 @@ private void OnFiltersChanged(Entity ent, ref S private void OnChangeStatus(Entity ent, ref CriminalRecordChangeStatus msg) { // prevent malf client violating wanted/reason nullability - if ((msg.Status == SecurityStatus.Wanted) != (msg.Reason != null)) + if (msg.Status == SecurityStatus.Wanted != (msg.Reason != null) && + msg.Status == SecurityStatus.Suspected != (msg.Reason != null)) return; if (!CheckSelected(ent, msg.Session, out var mob, out var key)) @@ -105,7 +108,7 @@ private void OnChangeStatus(Entity ent, ref Cri var name = RecordName(key.Value); var officer = Loc.GetString("criminal-records-console-unknown-officer"); - if (_idCard.TryFindIdCard(mob.Value, out var id) && id.Comp.FullName is {} fullName) + if (_idCard.TryFindIdCard(mob.Value, out var id) && id.Comp.FullName is { } fullName) officer = fullName; (string, object)[] args; @@ -117,20 +120,32 @@ private void OnChangeStatus(Entity ent, ref Cri // figure out which radio message to send depending on transition var statusString = (oldStatus, msg.Status) switch { - // going from wanted or detained on the spot + // person has been detained (_, SecurityStatus.Detained) => "detained", + // person did something sus + (_, SecurityStatus.Suspected) => "suspected", + // released on parole + (_, SecurityStatus.Paroled) => "paroled", // prisoner did their time - (SecurityStatus.Detained, SecurityStatus.None) => "released", - // going from wanted to none, must have been a mistake - (_, SecurityStatus.None) => "not-wanted", - // going from none or detained, AOS or prisonbreak / lazy secoff never set them to released and they reoffended + (_, SecurityStatus.Discharged) => "released", + // going from any other state to wanted, AOS or prisonbreak / lazy secoff never set them to released and they reoffended (_, SecurityStatus.Wanted) => "wanted", + // person is no longer sus + (SecurityStatus.Suspected, SecurityStatus.None) => "not-suspected", + // going from wanted to none, must have been a mistake + (SecurityStatus.Wanted, SecurityStatus.None) => "not-wanted", + // criminal status removed + (SecurityStatus.Detained, SecurityStatus.None) => "released", + // criminal is no longer on parole + (SecurityStatus.Paroled, SecurityStatus.None) => "not-parole", // this is impossible _ => "not-wanted" }; - _radio.SendRadioMessage(ent, Loc.GetString($"criminal-records-console-{statusString}", args), ent.Comp.SecurityChannel, ent); + _radio.SendRadioMessage(ent, Loc.GetString($"criminal-records-console-{statusString}", args), + ent.Comp.SecurityChannel, ent); UpdateUserInterface(ent); + UpdateCriminalIdentity(name, msg.Status); } private void OnAddHistory(Entity ent, ref CriminalRecordAddHistory msg) @@ -177,7 +192,7 @@ private void UpdateUserInterface(Entity ent) var listing = _stationRecords.BuildListing((owningStation.Value, stationRecords), console.Filter); var state = new CriminalRecordsConsoleState(listing, console.Filter); - if (console.ActiveKey is {} id) + if (console.ActiveKey is { } id) { // get records to display when a crewmember is selected var key = new StationRecordKey(id, owningStation.Value); @@ -198,7 +213,7 @@ private bool CheckSelected(Entity ent, ICommonS { key = null; mob = null; - if (session.AttachedEntity is not {} user) + if (session.AttachedEntity is not { } user) return false; if (!_access.IsAllowed(user, ent)) @@ -207,11 +222,11 @@ private bool CheckSelected(Entity ent, ICommonS return false; } - if (ent.Comp.ActiveKey is not {} id) + if (ent.Comp.ActiveKey is not { } id) return false; // checking the console's station since the user might be off-grid using on-grid console - if (_station.GetOwningStation(ent) is not {} station) + if (_station.GetOwningStation(ent) is not { } station) return false; key = new StationRecordKey(id, station); @@ -229,4 +244,29 @@ private string RecordName(StationRecordKey key) return record.Name; } + + /// + /// Checks if the new identity's name has a criminal record attached to it, and gives the entity the icon that + /// belongs to the status if it does. + /// + public void CheckNewIdentity(EntityUid uid) + { + var name = Identity.Name(uid, EntityManager); + var xform = Transform(uid); + var station = _station.GetStationInMap(xform.MapID); + + if (station != null && _stationRecords.GetRecordByName(station.Value, name) is { } id) + { + if (_stationRecords.TryGetRecord(new StationRecordKey(id, station.Value), + out var record)) + { + if (record.Status != SecurityStatus.None) + { + SetCriminalIcon(name, record.Status, uid); + return; + } + } + } + RemComp(uid); + } } diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs index dbd34d74843..ec8412ed1ab 100644 --- a/Content.Server/IdentityManagement/IdentitySystem.cs +++ b/Content.Server/IdentityManagement/IdentitySystem.cs @@ -1,5 +1,6 @@ using Content.Server.Access.Systems; using Content.Server.Administration.Logs; +using Content.Server.CriminalRecords.Systems; using Content.Server.Humanoid; using Content.Shared.Clothing; using Content.Shared.Database; @@ -25,6 +26,7 @@ public class IdentitySystem : SharedIdentitySystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!; private HashSet _queuedIdentityUpdates = new(); @@ -107,7 +109,9 @@ private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity) _metaData.SetEntityName(ident, name); _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}"); - RaiseLocalEvent(new IdentityChangedEvent(uid, ident)); + var identityChangedEvent = new IdentityChangedEvent(uid, ident); + RaiseLocalEvent(uid, ref identityChangedEvent); + SetIdentityCriminalIcon(uid); } private string GetIdentityName(EntityUid target, IdentityRepresentation representation) @@ -118,6 +122,16 @@ private string GetIdentityName(EntityUid target, IdentityRepresentation represen return representation.ToStringKnown(!ev.Cancelled); } + /// + /// When the identity of a person is changed, searches the criminal records to see if the name of the new identity + /// has a record. If the new name has a criminal status attached to it, the person will get the criminal status + /// until they change identity again. + /// + private void SetIdentityCriminalIcon(EntityUid uid) + { + _criminalRecordsConsole.CheckNewIdentity(uid); + } + /// /// Gets an 'identity representation' of an entity, with their true name being the entity name /// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one. @@ -159,15 +173,3 @@ private IdentityRepresentation GetIdentityRepresentation(EntityUid target, #endregion } - -public sealed class IdentityChangedEvent : EntityEventArgs -{ - public EntityUid CharacterEntity; - public EntityUid IdentityEntity; - - public IdentityChangedEvent(EntityUid characterEntity, EntityUid identityEntity) - { - CharacterEntity = characterEntity; - IdentityEntity = identityEntity; - } -} diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs index f9fc67c8964..6c51cb77acc 100644 --- a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs +++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs @@ -1,8 +1,52 @@ +using Content.Shared.IdentityManagement; +using Content.Shared.IdentityManagement.Components; +using Content.Shared.Security; +using Content.Shared.Security.Components; + namespace Content.Shared.CriminalRecords.Systems; -/// -/// Nothing is predicted just exists for access. -/// public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem { + /// + /// Any entity that has a the name of the record that was just changed as their visible name will get their icon + /// updated with the new status, if the record got removed their icon will be removed too. + /// + public void UpdateCriminalIdentity(string name, SecurityStatus status) + { + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var identity)) + { + if (!Identity.Name(uid, EntityManager).Equals(name)) + continue; + + if (status == SecurityStatus.None) + RemComp(uid); + else + SetCriminalIcon(name, status, uid); + } + } + + /// + /// Decides the icon that should be displayed on the entity based on the security status + /// + public void SetCriminalIcon(string name, SecurityStatus status, EntityUid characterUid) + { + EnsureComp(characterUid, out var record); + + var previousIcon = record.StatusIcon; + + record.StatusIcon = status switch + { + SecurityStatus.Paroled => "SecurityIconParoled", + SecurityStatus.Wanted => "SecurityIconWanted", + SecurityStatus.Detained => "SecurityIconIncarcerated", + SecurityStatus.Discharged => "SecurityIconDischarged", + SecurityStatus.Suspected => "SecurityIconSuspected", + _ => record.StatusIcon + }; + + if(previousIcon != record.StatusIcon) + Dirty(characterUid, record); + } } diff --git a/Content.Shared/IdentityManagement/Components/IdentityComponent.cs b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs index 7bbdeffe907..5e4c4531c17 100644 --- a/Content.Shared/IdentityManagement/Components/IdentityComponent.cs +++ b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs @@ -1,5 +1,4 @@ -using Content.Shared.Humanoid.Prototypes; -using Robust.Shared.Containers; +using Robust.Shared.Containers; using Robust.Shared.Enums; namespace Content.Shared.IdentityManagement.Components; diff --git a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs index a02e00f0b19..ef1c50f63ce 100644 --- a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs +++ b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs @@ -40,3 +40,8 @@ private void OnMaskToggled(Entity ent, ref ItemMaskTog ent.Comp.Enabled = !args.IsToggled; } } +/// +/// Gets called whenever an entity changes their identity. +/// +[ByRefEvent] +public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity); diff --git a/Content.Shared/Security/Components/CriminalRecordComponent.cs b/Content.Shared/Security/Components/CriminalRecordComponent.cs new file mode 100644 index 00000000000..25fab3f1350 --- /dev/null +++ b/Content.Shared/Security/Components/CriminalRecordComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.StatusIcon; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Security.Components; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class CriminalRecordComponent : Component +{ + /// + /// The icon that should be displayed based on the criminal status of the entity. + /// + [DataField, AutoNetworkedField] + public ProtoId StatusIcon = "SecurityIconWanted"; +} diff --git a/Content.Shared/Security/SecurityStatus.cs b/Content.Shared/Security/SecurityStatus.cs index 95250a86459..c7fe3766f1f 100644 --- a/Content.Shared/Security/SecurityStatus.cs +++ b/Content.Shared/Security/SecurityStatus.cs @@ -4,12 +4,18 @@ /// Status used in Criminal Records. /// /// None - the default value +/// Suspected - the person is suspected of doing something illegal /// Wanted - the person is being wanted by security /// Detained - the person is detained by security +/// Paroled - the person is on parole +/// Discharged - the person has been released from prison /// public enum SecurityStatus : byte { None, + Suspected, Wanted, - Detained + Detained, + Paroled, + Discharged } diff --git a/Resources/Locale/en-US/criminal-records/criminal-records.ftl b/Resources/Locale/en-US/criminal-records/criminal-records.ftl index 8e8124edcbd..cd73883f06f 100644 --- a/Resources/Locale/en-US/criminal-records/criminal-records.ftl +++ b/Resources/Locale/en-US/criminal-records/criminal-records.ftl @@ -10,8 +10,12 @@ criminal-records-console-status = Status criminal-records-status-none = None criminal-records-status-wanted = Wanted criminal-records-status-detained = Detained +criminal-records-status-suspected = Suspect +criminal-records-status-discharged = Discharged +criminal-records-status-paroled = Paroled criminal-records-console-wanted-reason = [color=gray]Wanted Reason[/color] +criminal-records-console-suspected-reason = [color=gray]Suspected Reason[/color] criminal-records-console-reason = Reason criminal-records-console-reason-placeholder = For example: {$placeholder} @@ -28,9 +32,13 @@ criminal-records-permission-denied = Permission denied ## Security channel notifications criminal-records-console-wanted = {$name} is wanted by {$officer} for: {$reason}. +criminal-records-console-suspected = {$officer} marked {$name} as suspicious because of: {$reason} +criminal-records-console-not-suspected = {$name} is no longer a suspect. criminal-records-console-detained = {$name} has been detained by {$officer}. criminal-records-console-released = {$name} has been released by {$officer}. criminal-records-console-not-wanted = {$name} is no longer wanted. +criminal-records-console-paroled = {$name} has been released on parole by {$officer}. +criminal-records-console-not-parole = {$name} is no longer on parole. criminal-records-console-unknown-officer = ## Filters diff --git a/Resources/Prototypes/StatusEffects/security.yml b/Resources/Prototypes/StatusEffects/security.yml new file mode 100644 index 00000000000..aeb2c1e9d52 --- /dev/null +++ b/Resources/Prototypes/StatusEffects/security.yml @@ -0,0 +1,40 @@ +- type: statusIcon + id: SecurityIcon + abstract: true + priority: 1 + locationPreference: Left + +- type: statusIcon + parent: SecurityIcon + id: SecurityIconDischarged + icon: + sprite: Interface/Misc/security_icons.rsi + state: hud_discharged + +- type: statusIcon + parent: SecurityIcon + id: SecurityIconIncarcerated + icon: + sprite: Interface/Misc/security_icons.rsi + state: hud_incarcerated + +- type: statusIcon + parent: SecurityIcon + id: SecurityIconParoled + icon: + sprite: Interface/Misc/security_icons.rsi + state: hud_paroled + +- type: statusIcon + parent: SecurityIcon + id: SecurityIconSuspected + icon: + sprite: Interface/Misc/security_icons.rsi + state: hud_suspected + +- type: statusIcon + parent: SecurityIcon + id: SecurityIconWanted + icon: + sprite: Interface/Misc/security_icons.rsi + state: hud_wanted diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_discharged.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_discharged.png new file mode 100644 index 00000000000..2e012349e06 Binary files /dev/null and b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_discharged.png differ diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_incarcerated.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_incarcerated.png new file mode 100644 index 00000000000..1dc03f7b86b Binary files /dev/null and b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_incarcerated.png differ diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_paroled.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_paroled.png new file mode 100644 index 00000000000..042fb147b1f Binary files /dev/null and b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_paroled.png differ diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_suspected.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_suspected.png new file mode 100644 index 00000000000..cfb34742bfb Binary files /dev/null and b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_suspected.png differ diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/hud_wanted.png b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_wanted.png new file mode 100644 index 00000000000..2df379d1120 Binary files /dev/null and b/Resources/Textures/Interface/Misc/security_icons.rsi/hud_wanted.png differ diff --git a/Resources/Textures/Interface/Misc/security_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/security_icons.rsi/meta.json new file mode 100644 index 00000000000..042eda20c6c --- /dev/null +++ b/Resources/Textures/Interface/Misc/security_icons.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/tgstation/tgstation/blob/7f654b6e8e59021607a9e888dfeb79920401c372/icons/mob/huds/hud.dmi", + "size": { + "x": 8, + "y": 8 + }, + "states": [ + { + "name": "hud_discharged" + }, + { + "name": "hud_incarcerated" + }, + { + "name": "hud_paroled" + }, + { + "name": "hud_suspected" + }, + { + "name": "hud_wanted" + } + ] +}