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!;