1
0

IdentitySystem.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. using Content.Server.Access.Systems;
  2. using Content.Server.Administration.Logs;
  3. using Content.Server.CriminalRecords.Systems;
  4. using Content.Server.Humanoid;
  5. using Content.Shared.Clothing;
  6. using Content.Shared.Database;
  7. using Content.Shared.Hands;
  8. using Content.Shared.Humanoid;
  9. using Content.Shared.IdentityManagement;
  10. using Content.Shared.IdentityManagement.Components;
  11. using Content.Shared.Inventory;
  12. using Content.Shared.Inventory.Events;
  13. using Robust.Shared.Containers;
  14. using Robust.Shared.Enums;
  15. using Robust.Shared.GameObjects.Components.Localization;
  16. namespace Content.Server.IdentityManagement;
  17. /// <summary>
  18. /// Responsible for updating the identity of an entity on init or clothing equip/unequip.
  19. /// </summary>
  20. public sealed class IdentitySystem : SharedIdentitySystem
  21. {
  22. [Dependency] private readonly IdCardSystem _idCard = default!;
  23. [Dependency] private readonly IAdminLogManager _adminLog = default!;
  24. [Dependency] private readonly MetaDataSystem _metaData = default!;
  25. [Dependency] private readonly SharedContainerSystem _container = default!;
  26. [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
  27. [Dependency] private readonly CriminalRecordsConsoleSystem _criminalRecordsConsole = default!;
  28. private HashSet<EntityUid> _queuedIdentityUpdates = new();
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. SubscribeLocalEvent<IdentityComponent, DidEquipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  33. SubscribeLocalEvent<IdentityComponent, DidEquipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  34. SubscribeLocalEvent<IdentityComponent, DidUnequipEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  35. SubscribeLocalEvent<IdentityComponent, DidUnequipHandEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  36. SubscribeLocalEvent<IdentityComponent, WearerMaskToggledEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  37. SubscribeLocalEvent<IdentityComponent, EntityRenamedEvent>((uid, _, _) => QueueIdentityUpdate(uid));
  38. SubscribeLocalEvent<IdentityComponent, MapInitEvent>(OnMapInit);
  39. }
  40. public override void Update(float frameTime)
  41. {
  42. base.Update(frameTime);
  43. foreach (var ent in _queuedIdentityUpdates)
  44. {
  45. if (!TryComp<IdentityComponent>(ent, out var identity))
  46. continue;
  47. UpdateIdentityInfo(ent, identity);
  48. }
  49. _queuedIdentityUpdates.Clear();
  50. }
  51. // This is where the magic happens
  52. private void OnMapInit(EntityUid uid, IdentityComponent component, MapInitEvent args)
  53. {
  54. var ident = Spawn(null, Transform(uid).Coordinates);
  55. _metaData.SetEntityName(ident, "identity");
  56. QueueIdentityUpdate(uid);
  57. _container.Insert(ident, component.IdentityEntitySlot);
  58. }
  59. /// <summary>
  60. /// Queues an identity update to the start of the next tick.
  61. /// </summary>
  62. public void QueueIdentityUpdate(EntityUid uid)
  63. {
  64. _queuedIdentityUpdates.Add(uid);
  65. }
  66. #region Private API
  67. /// <summary>
  68. /// Updates the metadata name for the id(entity) from the current state of the character.
  69. /// </summary>
  70. private void UpdateIdentityInfo(EntityUid uid, IdentityComponent identity)
  71. {
  72. if (identity.IdentityEntitySlot.ContainedEntity is not { } ident)
  73. return;
  74. var representation = GetIdentityRepresentation(uid);
  75. var name = GetIdentityName(uid, representation);
  76. // Clone the old entity's grammar to the identity entity, for loc purposes.
  77. if (TryComp<GrammarComponent>(uid, out var grammar))
  78. {
  79. var identityGrammar = EnsureComp<GrammarComponent>(ident);
  80. identityGrammar.Attributes.Clear();
  81. foreach (var (k, v) in grammar.Attributes)
  82. {
  83. identityGrammar.Attributes.Add(k, v);
  84. }
  85. // If presumed name is null and we're using that, we set proper noun to be false ("the old woman")
  86. if (name != representation.TrueName && representation.PresumedName == null)
  87. identityGrammar.ProperNoun = false;
  88. Dirty(ident, identityGrammar);
  89. }
  90. if (name == Name(ident))
  91. return;
  92. _metaData.SetEntityName(ident, name);
  93. _adminLog.Add(LogType.Identity, LogImpact.Medium, $"{ToPrettyString(uid)} changed identity to {name}");
  94. var identityChangedEvent = new IdentityChangedEvent(uid, ident);
  95. RaiseLocalEvent(uid, ref identityChangedEvent);
  96. SetIdentityCriminalIcon(uid);
  97. }
  98. private string GetIdentityName(EntityUid target, IdentityRepresentation representation)
  99. {
  100. var ev = new SeeIdentityAttemptEvent();
  101. RaiseLocalEvent(target, ev);
  102. return representation.ToStringKnown(!ev.Cancelled);
  103. }
  104. /// <summary>
  105. /// When the identity of a person is changed, searches the criminal records to see if the name of the new identity
  106. /// has a record. If the new name has a criminal status attached to it, the person will get the criminal status
  107. /// until they change identity again.
  108. /// </summary>
  109. private void SetIdentityCriminalIcon(EntityUid uid)
  110. {
  111. _criminalRecordsConsole.CheckNewIdentity(uid);
  112. }
  113. /// <summary>
  114. /// Gets an 'identity representation' of an entity, with their true name being the entity name
  115. /// and their 'presumed name' and 'presumed job' being the name/job on their ID card, if they have one.
  116. /// </summary>
  117. private IdentityRepresentation GetIdentityRepresentation(EntityUid target,
  118. InventoryComponent? inventory=null,
  119. HumanoidAppearanceComponent? appearance=null)
  120. {
  121. int age = 18;
  122. Gender gender = Gender.Epicene;
  123. string species = SharedHumanoidAppearanceSystem.DefaultSpecies;
  124. // Always use their actual age and gender, since that can't really be changed by an ID.
  125. if (Resolve(target, ref appearance, false))
  126. {
  127. gender = appearance.Gender;
  128. age = appearance.Age;
  129. species = appearance.Species;
  130. }
  131. var ageString = _humanoid.GetAgeRepresentation(species, age);
  132. var trueName = Name(target);
  133. if (!Resolve(target, ref inventory, false))
  134. return new(trueName, gender, ageString, string.Empty);
  135. string? presumedJob = null;
  136. string? presumedName = null;
  137. // Get their name and job from their ID for their presumed name.
  138. if (_idCard.TryFindIdCard(target, out var id))
  139. {
  140. presumedName = string.IsNullOrWhiteSpace(id.Comp.FullName) ? null : id.Comp.FullName;
  141. presumedJob = id.Comp.LocalizedJobTitle?.ToLowerInvariant();
  142. }
  143. // If it didn't find a job, that's fine.
  144. return new(trueName, gender, ageString, presumedName, presumedJob);
  145. }
  146. #endregion
  147. }