SharedIdCardSystem.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. using System.Globalization;
  2. using Content.Shared.Access.Components;
  3. using Content.Shared.Administration.Logs;
  4. using Content.Shared.Database;
  5. using Content.Shared.Hands.Components;
  6. using Content.Shared.IdentityManagement;
  7. using Content.Shared.Inventory;
  8. using Content.Shared.PDA;
  9. using Content.Shared.Roles;
  10. using Content.Shared.StatusIcon;
  11. using Robust.Shared.Prototypes;
  12. namespace Content.Shared.Access.Systems;
  13. public abstract class SharedIdCardSystem : EntitySystem
  14. {
  15. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  16. [Dependency] private readonly InventorySystem _inventorySystem = default!;
  17. [Dependency] private readonly MetaDataSystem _metaSystem = default!;
  18. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  19. public override void Initialize()
  20. {
  21. base.Initialize();
  22. SubscribeLocalEvent<IdCardComponent, MapInitEvent>(OnMapInit);
  23. SubscribeLocalEvent<TryGetIdentityShortInfoEvent>(OnTryGetIdentityShortInfo);
  24. SubscribeLocalEvent<EntityRenamedEvent>(OnRename);
  25. }
  26. private void OnRename(ref EntityRenamedEvent ev)
  27. {
  28. // When a player gets renamed their id card is renamed as well to match.
  29. // Unfortunately since TryFindIdCard will succeed if the entity is also a card this means that the card will
  30. // keep renaming itself unless we return early.
  31. // We also do not include the PDA itself being renamed, as that triggers the same event (e.g. for chameleon PDAs).
  32. if (HasComp<IdCardComponent>(ev.Uid) || HasComp<PdaComponent>(ev.Uid))
  33. return;
  34. if (TryFindIdCard(ev.Uid, out var idCard))
  35. TryChangeFullName(idCard, ev.NewName, idCard);
  36. }
  37. private void OnMapInit(EntityUid uid, IdCardComponent id, MapInitEvent args)
  38. {
  39. UpdateEntityName(uid, id);
  40. }
  41. private void OnTryGetIdentityShortInfo(TryGetIdentityShortInfoEvent ev)
  42. {
  43. if (ev.Handled)
  44. {
  45. return;
  46. }
  47. string? title = null;
  48. if (TryFindIdCard(ev.ForActor, out var idCard) && !(ev.RequestForAccessLogging && idCard.Comp.BypassLogging))
  49. {
  50. title = ExtractFullTitle(idCard);
  51. }
  52. ev.Title = title;
  53. ev.Handled = true;
  54. }
  55. /// <summary>
  56. /// Attempt to find an ID card on an entity. This will look in the entity itself, in the entity's hands, and
  57. /// in the entity's inventory.
  58. /// </summary>
  59. public bool TryFindIdCard(EntityUid uid, out Entity<IdCardComponent> idCard)
  60. {
  61. // check held item?
  62. if (TryComp(uid, out HandsComponent? hands) &&
  63. hands.ActiveHandEntity is EntityUid heldItem &&
  64. TryGetIdCard(heldItem, out idCard))
  65. {
  66. return true;
  67. }
  68. // check entity itself
  69. if (TryGetIdCard(uid, out idCard))
  70. return true;
  71. // check inventory slot?
  72. if (_inventorySystem.TryGetSlotEntity(uid, "id", out var idUid) && TryGetIdCard(idUid.Value, out idCard))
  73. return true;
  74. return false;
  75. }
  76. /// <summary>
  77. /// Attempt to get an id card component from an entity, either by getting it directly from the entity, or by
  78. /// getting the contained id from a <see cref="PdaComponent"/>.
  79. /// </summary>
  80. public bool TryGetIdCard(EntityUid uid, out Entity<IdCardComponent> idCard)
  81. {
  82. if (TryComp(uid, out IdCardComponent? idCardComp))
  83. {
  84. idCard = (uid, idCardComp);
  85. return true;
  86. }
  87. if (TryComp(uid, out PdaComponent? pda)
  88. && TryComp(pda.ContainedId, out idCardComp))
  89. {
  90. idCard = (pda.ContainedId.Value, idCardComp);
  91. return true;
  92. }
  93. idCard = default;
  94. return false;
  95. }
  96. /// <summary>
  97. /// Attempts to change the job title of a card.
  98. /// Returns true/false.
  99. /// </summary>
  100. /// <remarks>
  101. /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
  102. /// Actually works with the LocalizedJobTitle DataField and not with JobTitle.
  103. /// </remarks>
  104. public bool TryChangeJobTitle(EntityUid uid, string? jobTitle, IdCardComponent? id = null, EntityUid? player = null)
  105. {
  106. if (!Resolve(uid, ref id))
  107. return false;
  108. if (!string.IsNullOrWhiteSpace(jobTitle))
  109. {
  110. jobTitle = jobTitle.Trim();
  111. if (jobTitle.Length > IdCardConsoleComponent.MaxJobTitleLength)
  112. jobTitle = jobTitle[..IdCardConsoleComponent.MaxJobTitleLength];
  113. }
  114. else
  115. {
  116. jobTitle = null;
  117. }
  118. if (id.LocalizedJobTitle == jobTitle)
  119. return true;
  120. id.LocalizedJobTitle = jobTitle;
  121. Dirty(uid, id);
  122. UpdateEntityName(uid, id);
  123. if (player != null)
  124. {
  125. _adminLogger.Add(LogType.Identity, LogImpact.Low,
  126. $"{ToPrettyString(player.Value):player} has changed the job title of {ToPrettyString(uid):entity} to {jobTitle} ");
  127. }
  128. return true;
  129. }
  130. public bool TryChangeJobIcon(EntityUid uid, JobIconPrototype jobIcon, IdCardComponent? id = null, EntityUid? player = null)
  131. {
  132. if (!Resolve(uid, ref id))
  133. {
  134. return false;
  135. }
  136. if (id.JobIcon == jobIcon.ID)
  137. {
  138. return true;
  139. }
  140. id.JobIcon = jobIcon.ID;
  141. Dirty(uid, id);
  142. if (player != null)
  143. {
  144. _adminLogger.Add(LogType.Identity, LogImpact.Low,
  145. $"{ToPrettyString(player.Value):player} has changed the job icon of {ToPrettyString(uid):entity} to {jobIcon} ");
  146. }
  147. return true;
  148. }
  149. public bool TryChangeJobDepartment(EntityUid uid, JobPrototype job, IdCardComponent? id = null)
  150. {
  151. if (!Resolve(uid, ref id))
  152. return false;
  153. id.JobDepartments.Clear();
  154. foreach (var department in _prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
  155. {
  156. if (department.Roles.Contains(job.ID))
  157. id.JobDepartments.Add(department.ID);
  158. }
  159. Dirty(uid, id);
  160. return true;
  161. }
  162. /// <summary>
  163. /// Attempts to change the full name of a card.
  164. /// Returns true/false.
  165. /// </summary>
  166. /// <remarks>
  167. /// If provided with a player's EntityUid to the player parameter, adds the change to the admin logs.
  168. /// </remarks>
  169. public bool TryChangeFullName(EntityUid uid, string? fullName, IdCardComponent? id = null, EntityUid? player = null)
  170. {
  171. if (!Resolve(uid, ref id))
  172. return false;
  173. if (!string.IsNullOrWhiteSpace(fullName))
  174. {
  175. fullName = fullName.Trim();
  176. if (fullName.Length > IdCardConsoleComponent.MaxFullNameLength)
  177. fullName = fullName[..IdCardConsoleComponent.MaxFullNameLength];
  178. }
  179. else
  180. {
  181. fullName = null;
  182. }
  183. if (id.FullName == fullName)
  184. return true;
  185. id.FullName = fullName;
  186. Dirty(uid, id);
  187. UpdateEntityName(uid, id);
  188. if (player != null)
  189. {
  190. _adminLogger.Add(LogType.Identity, LogImpact.Low,
  191. $"{ToPrettyString(player.Value):player} has changed the name of {ToPrettyString(uid):entity} to {fullName} ");
  192. }
  193. return true;
  194. }
  195. /// <summary>
  196. /// Changes the name of the id's owner.
  197. /// </summary>
  198. /// <remarks>
  199. /// If either <see cref="FullName"/> or <see cref="JobTitle"/> is empty, it's replaced by placeholders.
  200. /// If both are empty, the original entity's name is restored.
  201. /// </remarks>
  202. private void UpdateEntityName(EntityUid uid, IdCardComponent? id = null)
  203. {
  204. if (!Resolve(uid, ref id))
  205. return;
  206. var jobSuffix = string.IsNullOrWhiteSpace(id.LocalizedJobTitle) ? string.Empty : $" ({id.LocalizedJobTitle})";
  207. var val = string.IsNullOrWhiteSpace(id.FullName)
  208. ? Loc.GetString(id.NameLocId,
  209. ("jobSuffix", jobSuffix))
  210. : Loc.GetString(id.FullNameLocId,
  211. ("fullName", id.FullName),
  212. ("jobSuffix", jobSuffix));
  213. _metaSystem.SetEntityName(uid, val);
  214. }
  215. private static string ExtractFullTitle(IdCardComponent idCardComponent)
  216. {
  217. return $"{idCardComponent.FullName} ({CultureInfo.CurrentCulture.TextInfo.ToTitleCase(idCardComponent.LocalizedJobTitle ?? string.Empty)})"
  218. .Trim();
  219. }
  220. }