Skip to content

Commit

Permalink
Mirror: Criminal record hud icons (#144)
Browse files Browse the repository at this point in the history
## Mirror of PR #25192: [Criminal record hud
icons](space-wizards/space-station-14#25192)
from <img src="https://avatars.githubusercontent.com/u/10567778?v=4"
alt="space-wizards" width="22"/>
[space-wizards](https://github.com/space-wizards)/[space-station-14](https://github.com/space-wizards/space-station-14)

###### `60b9d89e4dbdd8aaad4992a105628297d9480617`

PR opened by <img
src="https://avatars.githubusercontent.com/u/137322659?v=4"
width="16"/><a href="https://github.com/Arendian"> Arendian</a> at
2024-02-13 19:39:59 UTC
PR merged by <img
src="https://avatars.githubusercontent.com/u/19864447?v=4"
width="16"/><a href="https://github.com/web-flow"> web-flow</a> at
2024-03-11 03:12:52 UTC

---

PR changed 19 files with 230 additions and 40 deletions.

The PR had the following labels:
- Changes: UI
- Changes: Sprites
- Status: Needs Review


---

<details open="true"><summary><h1>Original Body</h1></summary>

> <!-- Please read these guidelines before opening your PR:
https://docs.spacestation14.io/en/getting-started/pr-guideline -->
> <!-- The text between the arrows are comments - they will not be
visible on your PR. -->
> 
> ## About the PR
> <!-- What did you change in this PR? -->
> Added three more statuses to the criminal record console: Paroled,
Suspect, Discharged.
> Entities now have a hud icon based on the criminal record linked to
the name they are currently showing.
> If "Urist McHands" is wanted, anyone that currently shows as being
"Urist McHands", even when their real name is not Urist McHands, will
show the relevant hud icon to entities wearing a security hud. If Urist
McHands is disguised as someone without a criminal record or has no
name(middle-aged captain man etc.), they won't show an icon.
> 
> ## Why / Balance
> <!-- Why was it changed? Link any discussions or issues here. Please
discuss how this would affect game balance. -->
> Icons showing on sechuds will make sechuds more usefull, it will
(hopefully) improve security coordination by allowing officers to more
easily see who they need to keep track of.
> It also gives warden something extra to do.
> 
> ## Technical details
> <!-- If this is a code change, summarize at high level how your new
code works. This makes it easier to review. -->
> 
> When a criminal record is changed, every entity with an
IdentityComponent will have their name checked to see if it equals the
name of the changed criminal record, if it's equal the person will get
the CriminalRecordComponent which holds the icon that needs to be shown
on the entity.
> If a criminal record is removed, the CriminalRecordComponent will be
removed from all entities that currently have the visible name
associated with the criminal record.
> 
> If an entity changes identity, all criminal records will be searched
to see if the name has a criminal record attached to it. If the name has
a record attached to it, the entity will get the CriminalRecordComponent
and the corresponding criminal record hud icon until they change their
identity again or the criminal record gets removed.
> 
> ## Media
> <!-- 
> PRs which make ingame changes (adding clothing, items, new features,
etc) are required to have media attached that showcase the changes.
> Small fixes/refactors are exempt.
> Any media may be used in SS14 progress reports, with clear credit
given.
> 
> If you're unsure whether your PR will require media, ask a maintainer.
> 
> Check the box below to confirm that you have in fact seen this (put an
X in the brackets, like [X]):
> -->
> 
>
https://github.com/space-wizards/space-station-14/assets/137322659/3c9422e6-897f-456f-b54b-8fdc053cb4e1
> 
> 
> - [X] I have added screenshots/videos to this PR showcasing its
changes ingame, **or** this PR does not require an ingame showcase
> 
> **Changelog**
> <!--
> Make players aware of new features and changes that could affect how
they play the game by adding a Changelog entry. Please read the
Changelog guidelines located at:
https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
> -->
> 
> 🆑 Dygon
> - add: The following criminal record statuses have been added:
Suspect, Discharged, Paroled.
> - add: Security huds now show an icon on entities that have a visible
name linked to a criminal record.
> 


</details>

Co-authored-by: Arendian <[email protected]>
  • Loading branch information
SimpleStation14 and Arendian committed May 4, 2024
1 parent 3daf76b commit 375b1c0
Show file tree
Hide file tree
Showing 19 changed files with 230 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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<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 prompt = Loc.GetString("criminal-records-console-reason");
Expand All @@ -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; };
Expand Down
7 changes: 6 additions & 1 deletion Content.Client/Overlays/ShowSecurityIconsSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -74,7 +75,11 @@ private IReadOnlyList<StatusIconPrototype> DecideSecurityIcon(EntityUid uid)
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;
}
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Administration/Systems/AdminSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ActorComponent>(ev.CharacterEntity, out var actor))
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -71,7 +73,8 @@ private void OnFiltersChanged(Entity<CriminalRecordsConsoleComponent> ent, ref S
private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> 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))
Expand Down Expand Up @@ -105,7 +108,7 @@ private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> 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;
Expand All @@ -117,20 +120,32 @@ private void OnChangeStatus(Entity<CriminalRecordsConsoleComponent> 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<CriminalRecordsConsoleComponent> ent, ref CriminalRecordAddHistory msg)
Expand Down Expand Up @@ -177,7 +192,7 @@ private void UpdateUserInterface(Entity<CriminalRecordsConsoleComponent> 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);
Expand All @@ -198,7 +213,7 @@ private bool CheckSelected(Entity<CriminalRecordsConsoleComponent> 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))
Expand All @@ -207,11 +222,11 @@ private bool CheckSelected(Entity<CriminalRecordsConsoleComponent> 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);
Expand All @@ -229,4 +244,29 @@ private string RecordName(StationRecordKey key)

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);
}
}
28 changes: 15 additions & 13 deletions Content.Server/IdentityManagement/IdentitySystem.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<EntityUid> _queuedIdentityUpdates = new();

Expand Down Expand Up @@ -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)
Expand All @@ -118,6 +122,16 @@ private string GetIdentityName(EntityUid target, IdentityRepresentation represen
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>
/// 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.
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Nothing is predicted just exists for access.
/// </summary>
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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
5 changes: 5 additions & 0 deletions Content.Shared/IdentityManagement/SharedIdentitySystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ private void OnMaskToggled(Entity<IdentityBlockerComponent> ent, ref ItemMaskTog
ent.Comp.Enabled = !args.IsToggled;
}
}
/// <summary>
/// Gets called whenever an entity changes their identity.
/// </summary>
[ByRefEvent]
public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);
15 changes: 15 additions & 0 deletions Content.Shared/Security/Components/CriminalRecordComponent.cs
Original file line number Diff line number Diff line change
@@ -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";
}
8 changes: 7 additions & 1 deletion Content.Shared/Security/SecurityStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// </summary>
public enum SecurityStatus : byte
{
None,
Suspected,
Wanted,
Detained
Detained,
Paroled,
Discharged
}
Loading

0 comments on commit 375b1c0

Please sign in to comment.