Criminal record hud icons (#25192)

* Security hud shows icon based on criminal record status

* Criminal status now linked to name instead of identity

* parole loc

* Test fix

* review changes

* Check station records instead of storing names on criminal record consoles.

* cleanup

* more cleanup

* review changes

* change outdated comments

* rename

* review changes

* remove event subscription

* replaced event with trycomp

* default value
This commit is contained in:
Arendian
2024-03-11 04:12:52 +01:00
committed by GitHub
parent 244e91d8eb
commit 60b9d89e4d
19 changed files with 230 additions and 40 deletions

View File

@@ -37,8 +37,8 @@ public sealed class CriminalRecordsConsoleBoundUserInterface : BoundUserInterfac
SendMessage(new SetStationRecordFilter(type, filterValue)); SendMessage(new SetStationRecordFilter(type, filterValue));
_window.OnStatusSelected += status => _window.OnStatusSelected += status =>
SendMessage(new CriminalRecordChangeStatus(status, null)); SendMessage(new CriminalRecordChangeStatus(status, null));
_window.OnDialogConfirmed += (_, reason) => _window.OnDialogConfirmed += (status, reason) =>
SendMessage(new CriminalRecordChangeStatus(SecurityStatus.Wanted, reason)); SendMessage(new CriminalRecordChangeStatus(status, reason));
_window.OnHistoryUpdated += UpdateHistory; _window.OnHistoryUpdated += UpdateHistory;
_window.OnHistoryClosed += () => _historyWindow?.Close(); _window.OnHistoryClosed += () => _historyWindow?.Close();
_window.OnClose += Close; _window.OnClose += Close;

View File

@@ -219,16 +219,16 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
private void SetStatus(SecurityStatus status) private void SetStatus(SecurityStatus status)
{ {
if (status == SecurityStatus.Wanted) if (status == SecurityStatus.Wanted || status == SecurityStatus.Suspected)
{ {
GetWantedReason(); GetReason(status);
return; return;
} }
OnStatusSelected?.Invoke(status); OnStatusSelected?.Invoke(status);
} }
private void GetWantedReason() private void GetReason(SecurityStatus status)
{ {
if (_reasonDialog != null) if (_reasonDialog != null)
{ {
@@ -237,7 +237,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
} }
var field = "reason"; 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<DatasetPrototype>(ReasonPlaceholders); var placeholders = _proto.Index<DatasetPrototype>(ReasonPlaceholders);
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used 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"); var prompt = Loc.GetString("criminal-records-console-reason");
@@ -251,7 +251,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
if (reason.Length < 1 || reason.Length > _maxLength) if (reason.Length < 1 || reason.Length > _maxLength)
return; return;
OnDialogConfirmed?.Invoke(SecurityStatus.Wanted, reason); OnDialogConfirmed?.Invoke(status, reason);
}; };
_reasonDialog.OnClose += () => { _reasonDialog = null; }; _reasonDialog.OnClose += () => { _reasonDialog = null; };

View File

@@ -3,6 +3,7 @@ using Content.Shared.Access.Systems;
using Content.Shared.Mindshield.Components; using Content.Shared.Mindshield.Components;
using Content.Shared.Overlays; using Content.Shared.Overlays;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Security.Components;
using Content.Shared.StatusIcon; using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components; using Content.Shared.StatusIcon.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -74,7 +75,11 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIco
result.Add(icon); result.Add(icon);
} }
// Add arrest icons here, WYCI. if (TryComp<CriminalRecordComponent>(uid, out var record))
{
if(_prototypeMan.TryIndex<StatusIconPrototype>(record.StatusIcon.Id, out var criminalIcon))
result.Add(criminalIcon);
}
return result; return result;
} }

View File

@@ -134,7 +134,7 @@ namespace Content.Server.Administration.Systems
return value ?? null; return value ?? null;
} }
private void OnIdentityChanged(IdentityChangedEvent ev) private void OnIdentityChanged(ref IdentityChangedEvent ev)
{ {
if (!TryComp<ActorComponent>(ev.CharacterEntity, out var actor)) if (!TryComp<ActorComponent>(ev.CharacterEntity, out var actor))
return; return;

View File

@@ -12,6 +12,8 @@ using Content.Shared.StationRecords;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player; using Robust.Shared.Player;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.IdentityManagement;
using Content.Shared.Security.Components;
namespace Content.Server.CriminalRecords.Systems; namespace Content.Server.CriminalRecords.Systems;
@@ -71,7 +73,8 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordChangeStatus msg) private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordChangeStatus msg)
{ {
// prevent malf client violating wanted/reason nullability // 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; return;
if (!CheckSelected(ent, msg.Session, out var mob, out var key)) if (!CheckSelected(ent, msg.Session, out var mob, out var key))
@@ -117,20 +120,32 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
// figure out which radio message to send depending on transition // figure out which radio message to send depending on transition
var statusString = (oldStatus, msg.Status) switch var statusString = (oldStatus, msg.Status) switch
{ {
// going from wanted or detained on the spot // person has been detained
(_, SecurityStatus.Detained) => "detained", (_, SecurityStatus.Detained) => "detained",
// person did something sus
(_, SecurityStatus.Suspected) => "suspected",
// released on parole
(_, SecurityStatus.Paroled) => "paroled",
// prisoner did their time // prisoner did their time
(SecurityStatus.Detained, SecurityStatus.None) => "released", (_, SecurityStatus.Discharged) => "released",
// going from wanted to none, must have been a mistake // going from any other state to wanted, AOS or prisonbreak / lazy secoff never set them to released and they reoffended
(_, SecurityStatus.None) => "not-wanted",
// going from none or detained, AOS or prisonbreak / lazy secoff never set them to released and they reoffended
(_, SecurityStatus.Wanted) => "wanted", (_, 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 // this is impossible
_ => "not-wanted" _ => "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); UpdateUserInterface(ent);
UpdateCriminalIdentity(name, msg.Status);
} }
private void OnAddHistory(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordAddHistory msg) private void OnAddHistory(Entity<CriminalRecordsConsoleComponent> ent, ref CriminalRecordAddHistory msg)
@@ -229,4 +244,29 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
return record.Name; return record.Name;
} }
/// <summary>
/// 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.
/// </summary>
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<CriminalRecord>(new StationRecordKey(id, station.Value),
out var record))
{
if (record.Status != SecurityStatus.None)
{
SetCriminalIcon(name, record.Status, uid);
return;
}
}
}
RemComp<CriminalRecordComponent>(uid);
}
} }

View File

@@ -1,5 +1,6 @@
using Content.Server.Access.Systems; using Content.Server.Access.Systems;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.CriminalRecords.Systems;
using Content.Server.Humanoid; using Content.Server.Humanoid;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.Database; using Content.Shared.Database;
@@ -25,6 +26,7 @@ public class IdentitySystem : SharedIdentitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedContainerSystem _container = default!; [Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
private HashSet<EntityUid> _queuedIdentityUpdates = new(); private HashSet<EntityUid> _queuedIdentityUpdates = new();
@@ -107,7 +109,9 @@ public class IdentitySystem : SharedIdentitySystem
_metaData.SetEntityName(ident, name); _metaData.SetEntityName(ident, name);
_adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {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) private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
@@ -118,6 +122,16 @@ public class IdentitySystem : SharedIdentitySystem
return representation.ToStringKnown(!ev.Cancelled); return representation.ToStringKnown(!ev.Cancelled);
} }
/// <summary>
/// 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.
/// </summary>
private void SetIdentityCriminalIcon(EntityUid uid)
{
_criminalRecordsConsole.CheckNewIdentity(uid);
}
/// <summary> /// <summary>
/// Gets an 'identity representation' of an entity, with their true name being the entity name /// 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. /// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one.
@@ -159,15 +173,3 @@ public class IdentitySystem : SharedIdentitySystem
#endregion #endregion
} }
public sealed class IdentityChangedEvent : EntityEventArgs
{
public EntityUid CharacterEntity;
public EntityUid IdentityEntity;
public IdentityChangedEvent(EntityUid characterEntity, EntityUid identityEntity)
{
CharacterEntity = characterEntity;
IdentityEntity = identityEntity;
}
}

View File

@@ -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; namespace Content.Shared.CriminalRecords.Systems;
/// <summary>
/// Nothing is predicted just exists for access.
/// </summary>
public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem
{ {
/// <summary>
/// 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.
/// </summary>
public void UpdateCriminalIdentity(string name, SecurityStatus status)
{
var query = EntityQueryEnumerator<IdentityComponent>();
while (query.MoveNext(out var uid, out var identity))
{
if (!Identity.Name(uid, EntityManager).Equals(name))
continue;
if (status == SecurityStatus.None)
RemComp<CriminalRecordComponent>(uid);
else
SetCriminalIcon(name, status, uid);
}
}
/// <summary>
/// Decides the icon that should be displayed on the entity based on the security status
/// </summary>
public void SetCriminalIcon(string name, SecurityStatus status, EntityUid characterUid)
{
EnsureComp<CriminalRecordComponent>(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);
}
} }

View File

@@ -1,5 +1,4 @@
using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Containers;
using Robust.Shared.Containers;
using Robust.Shared.Enums; using Robust.Shared.Enums;
namespace Content.Shared.IdentityManagement.Components; namespace Content.Shared.IdentityManagement.Components;

View File

@@ -40,3 +40,8 @@ public abstract class SharedIdentitySystem : EntitySystem
ent.Comp.Enabled = !args.IsToggled; ent.Comp.Enabled = !args.IsToggled;
} }
} }
/// <summary>
/// Gets called whenever an entity changes their identity.
/// </summary>
[ByRefEvent]
public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);

View File

@@ -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
{
/// <summary>
/// The icon that should be displayed based on the criminal status of the entity.
/// </summary>
[DataField, AutoNetworkedField]
public ProtoId<StatusIconPrototype> StatusIcon = "SecurityIconWanted";
}

View File

@@ -4,12 +4,18 @@
/// Status used in Criminal Records. /// Status used in Criminal Records.
/// ///
/// None - the default value /// None - the default value
/// Suspected - the person is suspected of doing something illegal
/// Wanted - the person is being wanted by security /// Wanted - the person is being wanted by security
/// Detained - the person is detained by security /// Detained - the person is detained by security
/// Paroled - the person is on parole
/// Discharged - the person has been released from prison
/// </summary> /// </summary>
public enum SecurityStatus : byte public enum SecurityStatus : byte
{ {
None, None,
Suspected,
Wanted, Wanted,
Detained Detained,
Paroled,
Discharged
} }

View File

@@ -10,8 +10,12 @@ criminal-records-console-status = Status
criminal-records-status-none = None criminal-records-status-none = None
criminal-records-status-wanted = Wanted criminal-records-status-wanted = Wanted
criminal-records-status-detained = Detained 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-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 = Reason
criminal-records-console-reason-placeholder = For example: {$placeholder} criminal-records-console-reason-placeholder = For example: {$placeholder}
@@ -28,9 +32,13 @@ criminal-records-permission-denied = Permission denied
## Security channel notifications ## Security channel notifications
criminal-records-console-wanted = {$name} is wanted by {$officer} for: {$reason}. 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-detained = {$name} has been detained by {$officer}.
criminal-records-console-released = {$name} has been released 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-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 = <unknown officer> criminal-records-console-unknown-officer = <unknown officer>
## Filters ## Filters

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

View File

@@ -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"
}
]
}