diff --git a/Content.Client/IdentityManagement/IdentitySystem.cs b/Content.Client/IdentityManagement/IdentitySystem.cs
deleted file mode 100644
index 15d4ee20e9..0000000000
--- a/Content.Client/IdentityManagement/IdentitySystem.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.IdentityManagement;
-
-namespace Content.Client.IdentityManagement;
-
-public sealed class IdentitySystem : SharedIdentitySystem
-{
-}
diff --git a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs
index 0d281d7075..f734d3eb3e 100644
--- a/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs
+++ b/Content.Server/Clothing/Systems/ChameleonClothingSystem.cs
@@ -1,9 +1,9 @@
using System.Linq;
using Content.Server.Emp;
-using Content.Server.IdentityManagement;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Emp;
+using Content.Shared.IdentityManagement;
using Content.Shared.IdentityManagement.Components;
using Content.Shared.Inventory;
using Content.Shared.Prototypes;
diff --git a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
index 72d66c7638..1b7e50c651 100644
--- a/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
+++ b/Content.Server/CriminalRecords/Systems/CriminalRecordsConsoleSystem.cs
@@ -269,31 +269,4 @@ public sealed class CriminalRecordsConsoleSystem : SharedCriminalRecordsConsoleS
mob = user;
return true;
}
-
- ///
- /// 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);
-
- // TODO use the entity's station? Not the station of the map that it happens to currently be on?
- var station = _station.GetStationInMap(xform.MapID);
-
- if (station != null && _records.GetRecordByName(station.Value, name) is { } id)
- {
- if (_records.TryGetRecord(new StationRecordKey(id, station.Value),
- out var record))
- {
- if (record.Status != SecurityStatus.None)
- {
- _criminalRecords.SetCriminalIcon(name, record.Status, uid);
- return;
- }
- }
- }
- RemComp(uid);
- }
}
diff --git a/Content.Server/Delivery/DeliverySystem.Spawning.cs b/Content.Server/Delivery/DeliverySystem.Spawning.cs
index a7496a343b..14662e58c6 100644
--- a/Content.Server/Delivery/DeliverySystem.Spawning.cs
+++ b/Content.Server/Delivery/DeliverySystem.Spawning.cs
@@ -1,7 +1,7 @@
using Content.Shared.Delivery;
using Content.Shared.Power.EntitySystems;
-using Content.Server.StationRecords;
using Content.Shared.EntityTable;
+using Content.Shared.StationRecords;
using Robust.Shared.Random;
using Robust.Shared.Timing;
diff --git a/Content.Server/IdentityManagement/IdentitySystem.cs b/Content.Server/IdentityManagement/IdentitySystem.cs
deleted file mode 100644
index 131544e569..0000000000
--- a/Content.Server/IdentityManagement/IdentitySystem.cs
+++ /dev/null
@@ -1,180 +0,0 @@
-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;
-using Content.Shared.Hands;
-using Content.Shared.Humanoid;
-using Content.Shared.IdentityManagement;
-using Content.Shared.IdentityManagement.Components;
-using Content.Shared.Inventory;
-using Content.Shared.Inventory.Events;
-using Robust.Shared.Containers;
-using Robust.Shared.Enums;
-using Robust.Shared.GameObjects.Components.Localization;
-
-namespace Content.Server.IdentityManagement;
-
-///
-/// Responsible for updating the identity of an entity on init or clothing equip/unequip.
-///
-public sealed class IdentitySystem : SharedIdentitySystem
-{
- [Dependency] private readonly IdCardSystem _idCard = default!;
- [Dependency] private readonly IAdminLogManager _adminLog = default!;
- [Dependency] private readonly MetaDataSystem _metaData = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
- [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
- [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
- [Dependency] private readonly GrammarSystem _grammarSystem = default!;
-
- private HashSet _queuedIdentityUpdates = new();
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
- SubscribeLocalEvent(OnMapInit);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- foreach (var ent in _queuedIdentityUpdates)
- {
- if (!TryComp(ent, out var identity))
- continue;
-
- UpdateIdentityInfo(ent, identity);
- }
-
- _queuedIdentityUpdates.Clear();
- }
-
- // This is where the magic happens
- private void OnMapInit(EntityUid uid, IdentityComponent component, MapInitEvent args)
- {
- var ident = Spawn(null, Transform(uid).Coordinates);
-
- _metaData.SetEntityName(ident, "identity");
- QueueIdentityUpdate(uid);
- _container.Insert(ident, component.IdentityEntitySlot);
- }
-
- ///
- /// Queues an identity update to the start of the next tick.
- ///
- public override void QueueIdentityUpdate(EntityUid uid)
- {
- _queuedIdentityUpdates.Add(uid);
- }
-
- #region Private API
-
- ///
- /// Updates the metadata name for the id(entity) from the current state of the character.
- ///
- private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity)
- {
- if (identity.IdentityEntitySlot.ContainedEntity is not { } ident)
- return;
-
- var representation = GetIdentityRepresentation(uid);
- var name = GetIdentityName(uid, representation);
-
- // Clone the old entity's grammar to the identity entity, for loc purposes.
- if (TryComp(uid, out var grammar))
- {
- var identityGrammar = EnsureComp(ident);
- identityGrammar.Attributes.Clear();
-
- foreach (var (k, v) in grammar.Attributes)
- {
- identityGrammar.Attributes.Add(k, v);
- }
-
- // If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
- if (name != representation.TrueName && representation.PresumedName == null)
- _grammarSystem.SetProperNoun((ident, identityGrammar), false);
-
- Dirty(ident, identityGrammar);
- }
-
- if (name == Name(ident))
- return;
-
- _metaData.SetEntityName(ident, name);
-
- _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}");
- var identityChangedEvent = new IdentityChangedEvent(uid, ident);
- RaiseLocalEvent(uid, ref identityChangedEvent);
- SetIdentityCriminalIcon(uid);
- }
-
- private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
- {
- var ev = new SeeIdentityAttemptEvent();
-
- RaiseLocalEvent(target, ev);
- 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.
- ///
- private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
- InventoryComponent? inventory=null,
- HumanoidAppearanceComponent? appearance=null)
- {
- int age = 18;
- Gender gender = Gender.Epicene;
- string species = SharedHumanoidAppearanceSystem.DefaultSpecies;
-
- // Always use their actual age and gender, since that can't really be changed by an ID.
- if (Resolve(target, ref appearance, false))
- {
- gender = appearance.Gender;
- age = appearance.Age;
- species = appearance.Species;
- }
-
- var ageString = _humanoid.GetAgeRepresentation(species, age);
- var trueName = Name(target);
- if (!Resolve(target, ref inventory, false))
- return new(trueName, gender, ageString, string.Empty);
-
- string? presumedJob = null;
- string? presumedName = null;
-
- // Get their name and job from their ID for their presumed name.
- if (_idCard.TryFindIdCard(target, out var id))
- {
- presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
- presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
- }
-
- // If it didn't find a job, that's fine.
- return new(trueName, gender, ageString, presumedName, presumedJob);
- }
-
- #endregion
-}
diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs
index 3967e320a8..ba9487b031 100644
--- a/Content.Server/Station/Systems/StationSpawningSystem.cs
+++ b/Content.Server/Station/Systems/StationSpawningSystem.cs
@@ -1,6 +1,5 @@
using Content.Server.Access.Systems;
using Content.Server.Humanoid;
-using Content.Server.IdentityManagement;
using Content.Server.Mind;
using Content.Server.PDA;
using Content.Server.Station.Components;
@@ -11,6 +10,7 @@ using Content.Shared.Clothing;
using Content.Shared.DetailExaminable;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.IdentityManagement;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
diff --git a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
index a3d0b49970..10eedd7562 100644
--- a/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
+++ b/Content.Server/StationRecords/Systems/StationRecordsSystem.cs
@@ -215,26 +215,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
return false;
}
- ///
- /// Try to get a record from this station's record entries,
- /// from the provided station record key. Will always return
- /// null if the key does not match the station.
- ///
- /// Station and key to try and index from the record set.
- /// The resulting entry.
- /// Station record component.
- /// Type to get from the record set.
- /// True if the record was obtained, false otherwise.
- public bool TryGetRecord(StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
- {
- entry = default;
-
- if (!Resolve(key.OriginStation, ref records))
- return false;
-
- return records.Records.TryGetRecordEntry(key.Id, out entry);
- }
-
///
/// Gets a random record from the station's record entries.
///
@@ -257,26 +237,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
return ent.Comp.Records.TryGetRecordEntry(key, out entry);
}
- ///
- /// Returns an id if a record with the same name exists.
- ///
- ///
- /// Linear search so O(n) time complexity.
- ///
- public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null)
- {
- if (!Resolve(station, ref records, false))
- return null;
-
- foreach (var (id, record) in GetRecordsOfType(station, records))
- {
- if (record.Name == name)
- return id;
- }
-
- return null;
- }
-
///
/// Get the name for a record, or an empty string if it has no record.
///
@@ -288,21 +248,6 @@ public sealed class StationRecordsSystem : SharedStationRecordsSystem
return record.Name;
}
- ///
- /// Gets all records of a specific type from a station.
- ///
- /// The station to get the records from.
- /// Station records component.
- /// Type of record to fetch
- /// Enumerable of pairs with a station record key, and the entry in question of type T.
- public IEnumerable<(uint, T)> GetRecordsOfType(EntityUid station, StationRecordsComponent? records = null)
- {
- if (!Resolve(station, ref records))
- return Array.Empty<(uint, T)>();
-
- return records.Records.GetRecordsOfType();
- }
-
///
/// Adds a new record entry to a station's record set.
///
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index 8b5db4561c..7cdcec78c2 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -6,7 +6,6 @@ using Content.Server.Chat.Managers;
using Content.Server.Ghost;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Humanoid;
-using Content.Server.IdentityManagement;
using Content.Server.Inventory;
using Content.Server.Mind;
using Content.Server.NPC;
@@ -39,6 +38,7 @@ using Content.Shared.Prying.Components;
using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio.Systems;
using Content.Shared.Ghost.Roles.Components;
+using Content.Shared.IdentityManagement;
using Content.Shared.Tag;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
diff --git a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs
index d3d366ecf9..7b894f0087 100644
--- a/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs
+++ b/Content.Shared/CriminalRecords/Systems/SharedCriminalRecordsConsoleSystem.cs
@@ -1,6 +1,44 @@
+using Content.Shared.IdentityManagement;
+using Content.Shared.Security;
+using Content.Shared.Security.Components;
+using Content.Shared.Station;
+using Content.Shared.StationRecords;
+
namespace Content.Shared.CriminalRecords.Systems;
///
/// Station records aren't predicted, just exists for access.
///
-public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem;
+public abstract class SharedCriminalRecordsConsoleSystem : EntitySystem
+{
+ [Dependency] private readonly SharedCriminalRecordsSystem _criminalRecords = default!;
+ [Dependency] private readonly SharedStationRecordsSystem _records = default!;
+ [Dependency] private readonly SharedStationSystem _station = default!;
+
+ ///
+ /// 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);
+
+ // TODO use the entity's station? Not the station of the map that it happens to currently be on?
+ var station = _station.GetStationInMap(xform.MapID);
+
+ if (station != null && _records.GetRecordByName(station.Value, name) is { } id)
+ {
+ if (_records.TryGetRecord(new StationRecordKey(id, station.Value),
+ out var record))
+ {
+ if (record.Status != SecurityStatus.None)
+ {
+ _criminalRecords.SetCriminalIcon(name, record.Status, uid);
+ return;
+ }
+ }
+ }
+ RemComp(uid);
+ }
+}
diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
index e88b99b593..401ba0404f 100644
--- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
+++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs
@@ -40,7 +40,7 @@ public abstract class SharedHumanoidAppearanceSystem : EntitySystem
[Dependency] private readonly ISerializationManager _serManager = default!;
[Dependency] private readonly MarkingManager _markingManager = default!;
[Dependency] private readonly GrammarSystem _grammarSystem = default!;
- [Dependency] private readonly SharedIdentitySystem _identity = default!;
+ [Dependency] private readonly IdentitySystem _identity = default!;
public static readonly ProtoId DefaultSpecies = "Human";
diff --git a/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs b/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs
index 308d9c0bf7..cc92a4c078 100644
--- a/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs
+++ b/Content.Shared/IdentityManagement/Components/IdentityBlockerComponent.cs
@@ -1,12 +1,13 @@
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
namespace Content.Shared.IdentityManagement.Components;
-[RegisterComponent, NetworkedComponent]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class IdentityBlockerComponent : Component
{
- [DataField]
+ [DataField, AutoNetworkedField]
public bool Enabled = true;
///
@@ -16,6 +17,8 @@ public sealed partial class IdentityBlockerComponent : Component
public IdentityBlockerCoverage Coverage = IdentityBlockerCoverage.FULL;
}
+[Flags]
+[Serializable, NetSerializable]
public enum IdentityBlockerCoverage
{
NONE = 0,
diff --git a/Content.Shared/IdentityManagement/Components/IdentityComponent.cs b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs
index 5e4c4531c1..86c07b307f 100644
--- a/Content.Shared/IdentityManagement/Components/IdentityComponent.cs
+++ b/Content.Shared/IdentityManagement/Components/IdentityComponent.cs
@@ -1,5 +1,6 @@
using Robust.Shared.Containers;
using Robust.Shared.Enums;
+using Robust.Shared.GameStates;
namespace Content.Shared.IdentityManagement.Components;
@@ -10,7 +11,7 @@ namespace Content.Shared.IdentityManagement.Components;
///
/// This is a and not just a datum entity because we do sort of care that it gets deleted and sent with the user.
///
-[RegisterComponent]
+[RegisterComponent, NetworkedComponent]
public sealed partial class IdentityComponent : Component
{
[ViewVariables]
diff --git a/Content.Shared/IdentityManagement/IdentitySystem.cs b/Content.Shared/IdentityManagement/IdentitySystem.cs
new file mode 100644
index 0000000000..7c559df629
--- /dev/null
+++ b/Content.Shared/IdentityManagement/IdentitySystem.cs
@@ -0,0 +1,241 @@
+using Content.Shared.Access.Systems;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Clothing;
+using Content.Shared.CriminalRecords.Systems;
+using Content.Shared.Database;
+using Content.Shared.Hands;
+using Content.Shared.Humanoid;
+using Content.Shared.IdentityManagement.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Inventory.Events;
+using Robust.Shared.Containers;
+using Robust.Shared.Enums;
+using Robust.Shared.GameObjects.Components.Localization;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.IdentityManagement;
+
+///
+/// Responsible for updating the identity of an entity on init or clothing equip/unequip.
+///
+public sealed class IdentitySystem : EntitySystem
+{
+ [Dependency] private readonly GrammarSystem _grammarSystem = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLog = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedCriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
+ [Dependency] private readonly SharedHumanoidAppearanceSystem _humanoid = default!;
+ [Dependency] private readonly SharedIdCardSystem _idCard = default!;
+
+ // The name of the container holding the identity entity
+ private const string SlotName = "identity";
+
+ // Recycled hashset for tracking identities each tick that need to update
+ private readonly HashSet _queuedIdentityUpdates = new();
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSeeIdentity);
+ SubscribeLocalEvent>(OnRelaySeeIdentity);
+ SubscribeLocalEvent(OnMaskToggled);
+
+ SubscribeLocalEvent(OnMapInit);
+ SubscribeLocalEvent(OnComponentInit);
+
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ SubscribeLocalEvent((uid, _, _) => QueueIdentityUpdate(uid));
+ }
+
+ ///
+ /// Iterates through all identities that need to be updated.
+ ///
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ foreach (var ent in _queuedIdentityUpdates)
+ {
+ if (!TryComp(ent, out var identity))
+ continue;
+
+ UpdateIdentityInfo((ent, identity));
+ }
+
+ _queuedIdentityUpdates.Clear();
+ }
+
+ #region Event Handlers
+
+ // Creates an identity entity, and store it in the identity container
+ private void OnMapInit(Entity ent, ref MapInitEvent args)
+ {
+ var ident = Spawn(null, Transform(ent).Coordinates);
+
+ _metaData.SetEntityName(ident, "identity");
+ QueueIdentityUpdate(ent);
+ _container.Insert(ident, ent.Comp.IdentityEntitySlot);
+ }
+
+ private void OnComponentInit(Entity ent, ref ComponentInit args)
+ {
+ ent.Comp.IdentityEntitySlot = _container.EnsureContainer(ent, SlotName);
+ }
+
+ // Adds an identity blocker's coverage, and cancels the event if coverage is complete.
+ private void OnSeeIdentity(Entity ent, ref SeeIdentityAttemptEvent args)
+ {
+ if (ent.Comp.Enabled)
+ {
+ args.TotalCoverage |= ent.Comp.Coverage;
+ if (args.TotalCoverage == IdentityBlockerCoverage.FULL)
+ args.Cancel();
+ }
+ }
+
+ private void OnRelaySeeIdentity(Entity ent, ref InventoryRelayedEvent args)
+ {
+ OnSeeIdentity(ent, ref args.Args);
+ }
+
+ // Toggles if a mask is hiding the identity.
+ private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args)
+ {
+ ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
+ Dirty(ent);
+ }
+
+ #endregion
+
+ ///
+ /// Queues an identity update to the start of the next tick.
+ ///
+ public void QueueIdentityUpdate(EntityUid uid)
+ {
+ if (_timing.ApplyingState)
+ return;
+
+ _queuedIdentityUpdates.Add(uid);
+ }
+ #region Private API
+
+ ///
+ /// Updates the metadata name for the id(entity) from the current state of the character.
+ ///
+ private void UpdateIdentityInfo(Entity ent)
+ {
+ if (ent.Comp.IdentityEntitySlot.ContainedEntity is not { } ident)
+ return;
+
+ var representation = GetIdentityRepresentation(ent.Owner);
+ var name = GetIdentityName(ent, representation);
+
+ // Clone the old entity's grammar to the identity entity, for loc purposes.
+ if (TryComp(ent, out var grammar))
+ {
+ var identityGrammar = EnsureComp(ident);
+ identityGrammar.Attributes.Clear();
+
+ foreach (var (k, v) in grammar.Attributes)
+ {
+ identityGrammar.Attributes.Add(k, v);
+ }
+
+ // If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
+ if (name != representation.TrueName && representation.PresumedName == null)
+ _grammarSystem.SetProperNoun((ident, identityGrammar), false);
+
+ Dirty(ident, identityGrammar);
+ }
+
+ if (name == Name(ident))
+ return;
+
+ _metaData.SetEntityName(ident, name);
+
+ _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(ent)} changed identity to {name}");
+ var identityChangedEvent = new IdentityChangedEvent(ent, ident);
+ RaiseLocalEvent(ent, ref identityChangedEvent);
+ SetIdentityCriminalIcon(ent);
+ }
+
+ ///
+ /// 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);
+ }
+
+ ///
+ /// Attempts to get an entity's name. Cancelled if the entity has full coverage from .
+ ///
+ /// The entity being targeted.
+ /// The data structure containing an entity's identities.
+ ///
+ /// An entity's real name if isn't cancelled,
+ /// or a hidden identity such as a fake ID or fully hidden identity like "middle-aged man".
+ ///
+ private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
+ {
+ var ev = new SeeIdentityAttemptEvent();
+
+ RaiseLocalEvent(target, ev);
+ return representation.ToStringKnown(!ev.Cancelled);
+ }
+
+ ///
+ /// 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.
+ ///
+ private IdentityRepresentation GetIdentityRepresentation(Entity target)
+ {
+ var age = 18;
+ var gender = Gender.Epicene;
+ var species = SharedHumanoidAppearanceSystem.DefaultSpecies;
+
+ // Always use their actual age and gender, since that can't really be changed by an ID.
+ if (Resolve(target, ref target.Comp2, false))
+ {
+ gender = target.Comp2.Gender;
+ age = target.Comp2.Age;
+ species = target.Comp2.Species;
+ }
+
+ var ageString = _humanoid.GetAgeRepresentation(species, age);
+ var trueName = Name(target);
+ if (!Resolve(target, ref target.Comp1, false))
+ return new(trueName, gender, ageString, string.Empty);
+
+ string? presumedJob = null;
+ string? presumedName = null;
+
+ // Get their name and job from their ID for their presumed name.
+ if (_idCard.TryFindIdCard(target, out var id))
+ {
+ presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
+ presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
+ }
+
+ // If it didn't find a job, that's fine.
+ return new(trueName, gender, ageString, presumedName, presumedJob);
+ }
+
+ #endregion
+}
+
+///
+/// Gets called whenever an entity changes their identity.
+///
+[ByRefEvent]
+public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);
diff --git a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs b/Content.Shared/IdentityManagement/SharedIdentitySystem.cs
deleted file mode 100644
index 6b03dc3850..0000000000
--- a/Content.Shared/IdentityManagement/SharedIdentitySystem.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Content.Shared.Clothing;
-using Content.Shared.IdentityManagement.Components;
-using Content.Shared.Inventory;
-using Robust.Shared.Containers;
-
-namespace Content.Shared.IdentityManagement;
-
-public abstract class SharedIdentitySystem : EntitySystem
-{
- [Dependency] private readonly SharedContainerSystem _container = default!;
- private static string SlotName = "identity";
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnComponentInit);
- SubscribeLocalEvent(OnSeeIdentity);
- SubscribeLocalEvent>((e, c, ev) => OnSeeIdentity(e, c, ev.Args));
- SubscribeLocalEvent(OnMaskToggled);
- }
-
- private void OnSeeIdentity(EntityUid uid, IdentityBlockerComponent component, SeeIdentityAttemptEvent args)
- {
- if (component.Enabled)
- {
- args.TotalCoverage |= component.Coverage;
- if(args.TotalCoverage == IdentityBlockerCoverage.FULL)
- args.Cancel();
- }
- }
-
- protected virtual void OnComponentInit(EntityUid uid, IdentityComponent component, ComponentInit args)
- {
- component.IdentityEntitySlot = _container.EnsureContainer(uid, SlotName);
- }
-
- private void OnMaskToggled(Entity ent, ref ItemMaskToggledEvent args)
- {
- ent.Comp.Enabled = !args.Mask.Comp.IsToggled;
- }
-
- ///
- /// Queues an identity update to the start of the next tick.
- ///
- public virtual void QueueIdentityUpdate(EntityUid uid) { }
-}
-///
-/// Gets called whenever an entity changes their identity.
-///
-[ByRefEvent]
-public record struct IdentityChangedEvent(EntityUid CharacterEntity, EntityUid IdentityEntity);
diff --git a/Content.Shared/StationRecords/SharedStationRecordsSystem.cs b/Content.Shared/StationRecords/SharedStationRecordsSystem.cs
index c2cc418f54..e04de09d65 100644
--- a/Content.Shared/StationRecords/SharedStationRecordsSystem.cs
+++ b/Content.Shared/StationRecords/SharedStationRecordsSystem.cs
@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+
namespace Content.Shared.StationRecords;
public abstract class SharedStationRecordsSystem : EntitySystem
@@ -40,4 +42,60 @@ public abstract class SharedStationRecordsSystem : EntitySystem
}
return result;
}
+
+ ///
+ /// Try to get a record from this station's record entries,
+ /// from the provided station record key. Will always return
+ /// null if the key does not match the station.
+ ///
+ /// Station and key to try and index from the record set.
+ /// The resulting entry.
+ /// Station record component.
+ /// Type to get from the record set.
+ /// True if the record was obtained, false otherwise. Always false on client.
+ public bool TryGetRecord(StationRecordKey key, [NotNullWhen(true)] out T? entry, StationRecordsComponent? records = null)
+ {
+ entry = default;
+
+ if (!Resolve(key.OriginStation, ref records))
+ return false;
+
+ return records.Records.TryGetRecordEntry(key.Id, out entry);
+ }
+
+ ///
+ /// Gets all records of a specific type from a station.
+ ///
+ /// The station to get the records from.
+ /// Station records component.
+ /// Type of record to fetch
+ /// Enumerable of pairs with a station record key, and the entry in question of type T. Always empty on client.
+ public IEnumerable<(uint, T)> GetRecordsOfType(EntityUid station, StationRecordsComponent? records = null)
+ {
+ if (!Resolve(station, ref records))
+ return Array.Empty<(uint, T)>();
+
+ return records.Records.GetRecordsOfType();
+ }
+
+ ///
+ /// Returns an id if a record with the same name exists.
+ ///
+ ///
+ /// Linear search so O(n) time complexity.
+ ///
+ /// Returns a station record id. Always null on client.
+ public uint? GetRecordByName(EntityUid station, string name, StationRecordsComponent? records = null)
+ {
+ if (!Resolve(station, ref records, false))
+ return null;
+
+ foreach (var (id, record) in GetRecordsOfType(station, records))
+ {
+ if (record.Name == name)
+ return id;
+ }
+
+ return null;
+ }
}
diff --git a/Content.Server/StationRecords/StationRecordSet.cs b/Content.Shared/StationRecords/StationRecordSet.cs
similarity index 98%
rename from Content.Server/StationRecords/StationRecordSet.cs
rename to Content.Shared/StationRecords/StationRecordSet.cs
index b5a4501cea..169e5843d2 100644
--- a/Content.Server/StationRecords/StationRecordSet.cs
+++ b/Content.Shared/StationRecords/StationRecordSet.cs
@@ -1,9 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using Content.Shared.StationRecords;
using Robust.Shared.Utility;
-namespace Content.Server.StationRecords;
+namespace Content.Shared.StationRecords;
///
/// Set of station records for a single station. StationRecordsComponent stores these.
diff --git a/Content.Server/StationRecords/Components/StationRecordsComponent.cs b/Content.Shared/StationRecords/StationRecordsComponent.cs
similarity index 70%
rename from Content.Server/StationRecords/Components/StationRecordsComponent.cs
rename to Content.Shared/StationRecords/StationRecordsComponent.cs
index 4ea65522f4..66c60dddc6 100644
--- a/Content.Server/StationRecords/Components/StationRecordsComponent.cs
+++ b/Content.Shared/StationRecords/StationRecordsComponent.cs
@@ -1,8 +1,6 @@
-using Content.Server.StationRecords.Systems;
+namespace Content.Shared.StationRecords;
-namespace Content.Server.StationRecords;
-
-[Access(typeof(StationRecordsSystem))]
+[Access(typeof(SharedStationRecordsSystem))]
[RegisterComponent]
public sealed partial class StationRecordsComponent : Component
{
diff --git a/Content.Shared/Trigger/Systems/DnaScrambleOnTriggerSystem.cs b/Content.Shared/Trigger/Systems/DnaScrambleOnTriggerSystem.cs
index 246c6a8c7a..db27bd7f74 100644
--- a/Content.Shared/Trigger/Systems/DnaScrambleOnTriggerSystem.cs
+++ b/Content.Shared/Trigger/Systems/DnaScrambleOnTriggerSystem.cs
@@ -13,7 +13,7 @@ public sealed class DnaScrambleOnTriggerSystem : EntitySystem
{
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly SharedHumanoidAppearanceSystem _humanoidAppearance = default!;
- [Dependency] private readonly SharedIdentitySystem _identity = default!;
+ [Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly SharedForensicsSystem _forensics = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly INetManager _net = default!;