SharedRoleSystem.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Linq;
  3. using Content.Shared.Administration.Logs;
  4. using Content.Shared.CCVar;
  5. using Content.Shared.Database;
  6. using Content.Shared.GameTicking;
  7. using Content.Shared.Mind;
  8. using Content.Shared.Roles.Jobs;
  9. using Content.Shared.Silicons.Borgs.Components;
  10. using Robust.Shared.Audio;
  11. using Robust.Shared.Audio.Systems;
  12. using Robust.Shared.Configuration;
  13. using Robust.Shared.Map;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Serialization;
  16. using Robust.Shared.Utility;
  17. namespace Content.Shared.Roles;
  18. public abstract class SharedRoleSystem : EntitySystem
  19. {
  20. [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
  21. [Dependency] private readonly SharedAudioSystem _audio = default!;
  22. [Dependency] private readonly IConfigurationManager _cfg = default!;
  23. [Dependency] private readonly IEntityManager _entityManager = default!;
  24. [Dependency] private readonly SharedMindSystem _minds = default!;
  25. [Dependency] private readonly IPrototypeManager _prototypes = default!;
  26. private JobRequirementOverridePrototype? _requirementOverride;
  27. public override void Initialize()
  28. {
  29. Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true);
  30. SubscribeLocalEvent<MindRoleComponent, ComponentShutdown>(OnComponentShutdown);
  31. SubscribeLocalEvent<StartingMindRoleComponent, PlayerSpawnCompleteEvent>(OnSpawn);
  32. }
  33. private void OnSpawn(EntityUid uid, StartingMindRoleComponent component, PlayerSpawnCompleteEvent args)
  34. {
  35. if (!_minds.TryGetMind(uid, out var mindId, out var mindComp))
  36. return;
  37. MindAddRole(mindId, component.MindRole, mind: mindComp, silent: component.Silent);
  38. }
  39. private void SetRequirementOverride(string value)
  40. {
  41. if (string.IsNullOrEmpty(value))
  42. {
  43. _requirementOverride = null;
  44. return;
  45. }
  46. if (!_prototypes.TryIndex(value, out _requirementOverride ))
  47. Log.Error($"Unknown JobRequirementOverridePrototype: {value}");
  48. }
  49. /// <summary>
  50. /// Adds multiple mind roles to a mind
  51. /// </summary>
  52. /// <param name="mindId">The mind entity to add the role to</param>
  53. /// <param name="roles">The list of mind roles to add</param>
  54. /// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
  55. /// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
  56. public void MindAddRoles(EntityUid mindId,
  57. List<EntProtoId>? roles,
  58. MindComponent? mind = null,
  59. bool silent = false)
  60. {
  61. if (roles is null || roles.Count == 0)
  62. return;
  63. foreach (var proto in roles)
  64. {
  65. MindAddRole(mindId, proto, mind, silent);
  66. }
  67. }
  68. /// <summary>
  69. /// Adds a mind role to a mind
  70. /// </summary>
  71. /// <param name="mindId">The mind entity to add the role to</param>
  72. /// <param name="protoId">The mind role to add</param>
  73. /// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
  74. /// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
  75. public void MindAddRole(EntityUid mindId,
  76. EntProtoId protoId,
  77. MindComponent? mind = null,
  78. bool silent = false)
  79. {
  80. if (protoId == "MindRoleJob")
  81. MindAddJobRole(mindId, mind, silent, "");
  82. else
  83. MindAddRoleDo(mindId, protoId, mind, silent);
  84. }
  85. /// <summary>
  86. /// Adds a Job mind role with the specified job prototype
  87. /// </summary>
  88. /// /// <param name="mindId">The mind entity to add the job role to</param>
  89. /// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
  90. /// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
  91. /// <param name="jobPrototype">The Job prototype for the new role</param>
  92. public void MindAddJobRole(EntityUid mindId,
  93. MindComponent? mind = null,
  94. bool silent = false,
  95. string? jobPrototype = null)
  96. {
  97. if (!Resolve(mindId, ref mind))
  98. return;
  99. // Can't have someone get paid for two jobs now, can we
  100. if (MindHasRole<JobRoleComponent>((mindId, mind), out var jobRole)
  101. && jobRole.Value.Comp1.JobPrototype != jobPrototype)
  102. {
  103. _adminLogger.Add(LogType.Mind,
  104. LogImpact.Low,
  105. $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'");
  106. jobRole.Value.Comp1.JobPrototype = jobPrototype;
  107. }
  108. else
  109. MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype);
  110. }
  111. /// <summary>
  112. /// Creates a Mind Role
  113. /// </summary>
  114. private void MindAddRoleDo(EntityUid mindId,
  115. EntProtoId protoId,
  116. MindComponent? mind = null,
  117. bool silent = false,
  118. string? jobPrototype = null)
  119. {
  120. if (!Resolve(mindId, ref mind))
  121. {
  122. Log.Error($"Failed to add role {protoId} to {ToPrettyString(mindId)} : Mind does not match provided mind component");
  123. return;
  124. }
  125. if (!_prototypes.TryIndex(protoId, out var protoEnt))
  126. {
  127. Log.Error($"Failed to add role {protoId} to {ToPrettyString(mindId)} : Role prototype does not exist");
  128. return;
  129. }
  130. //TODO don't let a prototype being added a second time
  131. //If that was somehow to occur, a second mindrole for that comp would be created
  132. //Meaning any mind role checks could return wrong results, since they just return the first match they find
  133. var mindRoleId = Spawn(protoId, MapCoordinates.Nullspace);
  134. EnsureComp<MindRoleComponent>(mindRoleId);
  135. var mindRoleComp = Comp<MindRoleComponent>(mindRoleId);
  136. mindRoleComp.Mind = (mindId,mind);
  137. if (jobPrototype is not null)
  138. {
  139. mindRoleComp.JobPrototype = jobPrototype;
  140. EnsureComp<JobRoleComponent>(mindRoleId);
  141. DebugTools.AssertNull(mindRoleComp.AntagPrototype);
  142. DebugTools.Assert(!mindRoleComp.Antag);
  143. DebugTools.Assert(!mindRoleComp.ExclusiveAntag);
  144. }
  145. mind.MindRoles.Add(mindRoleId);
  146. var update = MindRolesUpdate((mindId, mind));
  147. // RoleType refresh, Role time tracking, Update Admin playerlist
  148. var message = new RoleAddedEvent(mindId, mind, update, silent);
  149. RaiseLocalEvent(mindId, message, true);
  150. var name = Loc.GetString(protoEnt.Name);
  151. if (mind.OwnedEntity is not null)
  152. {
  153. _adminLogger.Add(LogType.Mind,
  154. LogImpact.Low,
  155. $"{name} added to mind of {ToPrettyString(mind.OwnedEntity)}");
  156. }
  157. else
  158. {
  159. //TODO: This is not tied to the player on the Admin Log filters.
  160. //Probably only happens when Job Role is added on initial spawn, before the mind entity is put in a mob
  161. Log.Error($"{ToPrettyString(mindId)} does not have an OwnedEntity!");
  162. _adminLogger.Add(LogType.Mind,
  163. LogImpact.Low,
  164. $"{name} added to {ToPrettyString(mindId)}");
  165. }
  166. }
  167. /// <summary>
  168. /// Select the mind's currently "active" mind role entity, and update the mind's role type, if necessary
  169. /// </summary>
  170. /// <returns>
  171. /// True if this changed the mind's role type
  172. /// </returns>>
  173. private bool MindRolesUpdate(Entity<MindComponent?> ent)
  174. {
  175. if(!Resolve(ent.Owner, ref ent.Comp))
  176. return false;
  177. //get the most important/latest mind role
  178. var roleType = GetRoleTypeByTime(ent.Comp);
  179. if (ent.Comp.RoleType == roleType)
  180. return false;
  181. SetRoleType(ent.Owner, roleType);
  182. return true;
  183. }
  184. private ProtoId<RoleTypePrototype> GetRoleTypeByTime(MindComponent mind)
  185. {
  186. // If any Mind Roles specify a Role Type, return the most recent. Otherwise return Neutral
  187. var roles = new List<ProtoId<RoleTypePrototype>>();
  188. foreach (var role in mind.MindRoles)
  189. {
  190. var comp = Comp<MindRoleComponent>(role);
  191. if (comp.RoleType is not null)
  192. roles.Add(comp.RoleType.Value);
  193. }
  194. ProtoId<RoleTypePrototype> result = (roles.Count > 0) ? roles.LastOrDefault() : "Neutral";
  195. return (result);
  196. }
  197. private void SetRoleType(EntityUid mind, ProtoId<RoleTypePrototype> roleTypeId)
  198. {
  199. if (!TryComp<MindComponent>(mind, out var comp))
  200. {
  201. Log.Error($"Failed to update Role Type of mind entity {ToPrettyString(mind)} to {roleTypeId}. MindComponent not found.");
  202. return;
  203. }
  204. if (!_prototypes.HasIndex(roleTypeId))
  205. {
  206. Log.Error($"Failed to change Role Type of {_minds.MindOwnerLoggingString(comp)} to {roleTypeId}. Invalid role");
  207. return;
  208. }
  209. comp.RoleType = roleTypeId;
  210. Dirty(mind, comp);
  211. // Update player character window
  212. if (_minds.TryGetSession(mind, out var session))
  213. RaiseNetworkEvent(new MindRoleTypeChangedEvent(), session.Channel);
  214. else
  215. {
  216. var error = $"The Character Window of {_minds.MindOwnerLoggingString(comp)} potentially did not update immediately : session error";
  217. _adminLogger.Add(LogType.Mind, LogImpact.High, $"{error}");
  218. }
  219. if (comp.OwnedEntity is null)
  220. {
  221. Log.Error($"{ToPrettyString(mind)} does not have an OwnedEntity!");
  222. _adminLogger.Add(LogType.Mind,
  223. LogImpact.High,
  224. $"Role Type of {ToPrettyString(mind)} changed to {roleTypeId}");
  225. return;
  226. }
  227. _adminLogger.Add(LogType.Mind,
  228. LogImpact.High,
  229. $"Role Type of {ToPrettyString(comp.OwnedEntity)} changed to {roleTypeId}");
  230. }
  231. /// <summary>
  232. /// Removes all instances of a specific role from this mind.
  233. /// </summary>
  234. /// <param name="mind">The mind to remove the role from.</param>
  235. /// <typeparam name="T">The type of the role to remove.</typeparam>
  236. /// <returns>Returns false if the role did not exist. True if successful</returns>>
  237. public bool MindRemoveRole<T>(Entity<MindComponent?> mind) where T : IComponent
  238. {
  239. if (typeof(T) == typeof(MindRoleComponent))
  240. throw new InvalidOperationException();
  241. if (!Resolve(mind.Owner, ref mind.Comp))
  242. return false;
  243. var found = false;
  244. var delete = new List<EntityUid>();
  245. foreach (var role in mind.Comp.MindRoles)
  246. {
  247. if (!HasComp<T>(role))
  248. continue;
  249. if (!HasComp<MindRoleComponent>(role))
  250. {
  251. Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}");
  252. continue;
  253. }
  254. delete.Add(role);
  255. found = true;
  256. }
  257. if (!found)
  258. return false;
  259. foreach (var role in delete)
  260. {
  261. _entityManager.DeleteEntity(role);
  262. }
  263. var update = MindRolesUpdate(mind);
  264. var message = new RoleRemovedEvent(mind.Owner, mind.Comp, update);
  265. RaiseLocalEvent(mind, message, true);
  266. _adminLogger.Add(LogType.Mind,
  267. LogImpact.Low,
  268. $"All roles of type '{typeof(T).Name}' removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}");
  269. return true;
  270. }
  271. // Removing the mind role's reference on component shutdown
  272. // to make sure the reference gets removed even if the mind role entity was deleted by outside code
  273. private void OnComponentShutdown(Entity<MindRoleComponent> ent, ref ComponentShutdown args)
  274. {
  275. //TODO: Just ensure that the tests don't spawn unassociated mind role entities
  276. if (ent.Comp.Mind.Comp is null)
  277. return;
  278. ent.Comp.Mind.Comp.MindRoles.Remove(ent.Owner);
  279. }
  280. /// <summary>
  281. /// Finds and removes all mind roles of a specific type
  282. /// </summary>
  283. /// <param name="mindId">The mind entity</param>
  284. /// <typeparam name="T">The type of the role to remove.</typeparam>
  285. /// <returns>True if the role existed and was removed</returns>
  286. public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
  287. {
  288. if (typeof(T) == typeof(MindRoleComponent))
  289. return false;
  290. if (MindRemoveRole<T>(mindId))
  291. return true;
  292. Log.Warning($"Failed to remove role {typeof(T)} from {ToPrettyString(mindId)} : mind does not have role ");
  293. return false;
  294. }
  295. /// <summary>
  296. /// Finds the first mind role of a specific T type on a mind entity.
  297. /// Outputs entity components for the mind role's MindRoleComponent and for T
  298. /// </summary>
  299. /// <param name="mind">The mind entity</param>
  300. /// <typeparam name="T">The type of the role to find.</typeparam>
  301. /// <param name="role">The Mind Role entity component</param>
  302. /// <returns>True if the role is found</returns>
  303. public bool MindHasRole<T>(Entity<MindComponent?> mind,
  304. [NotNullWhen(true)] out Entity<MindRoleComponent, T>? role) where T : IComponent
  305. {
  306. role = null;
  307. if (!Resolve(mind.Owner, ref mind.Comp))
  308. return false;
  309. foreach (var roleEnt in mind.Comp.MindRoles)
  310. {
  311. if (!TryComp(roleEnt, out T? tcomp))
  312. continue;
  313. if (!TryComp(roleEnt, out MindRoleComponent? roleComp))
  314. {
  315. Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}");
  316. continue;
  317. }
  318. role = (roleEnt, roleComp, tcomp);
  319. return true;
  320. }
  321. return false;
  322. }
  323. /// <summary>
  324. /// Finds the first mind role of a specific type on a mind entity.
  325. /// Outputs an entity component for the mind role's MindRoleComponent
  326. /// </summary>
  327. /// <param name="mindId">The mind entity</param>
  328. /// <param name="type">The Type to look for</param>
  329. /// <param name="role">The output role</param>
  330. /// <returns>True if the role is found</returns>
  331. public bool MindHasRole(EntityUid mindId,
  332. Type type,
  333. [NotNullWhen(true)] out Entity<MindRoleComponent>? role)
  334. {
  335. role = null;
  336. // All MindRoles have this component, it would just return the first one.
  337. // Order might not be what is expected.
  338. // Better to report null
  339. if (type == Type.GetType("MindRoleComponent"))
  340. {
  341. Log.Error($"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role.");
  342. return false;
  343. }
  344. if (!TryComp<MindComponent>(mindId, out var mind))
  345. return false;
  346. var found = false;
  347. foreach (var roleEnt in mind.MindRoles)
  348. {
  349. if (!HasComp(roleEnt, type))
  350. continue;
  351. if (!TryComp(roleEnt, out MindRoleComponent? roleComp))
  352. {
  353. Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}");
  354. continue;
  355. }
  356. role = (roleEnt, roleComp);
  357. found = true;
  358. break;
  359. }
  360. return found;
  361. }
  362. /// <summary>
  363. /// Finds the first mind role of a specific type on a mind entity.
  364. /// </summary>
  365. /// <param name="mindId">The mind entity</param>
  366. /// <typeparam name="T">The type of the role to find.</typeparam>
  367. /// <returns>True if the role is found</returns>
  368. public bool MindHasRole<T>(EntityUid mindId) where T : IComponent
  369. {
  370. return MindHasRole<T>(mindId, out _);
  371. }
  372. //TODO: Delete this later
  373. /// <summary>
  374. /// Returns the first mind role of a specific type
  375. /// </summary>
  376. /// <param name="mindId">The mind entity</param>
  377. /// <returns>Entity Component of the mind role</returns>
  378. [Obsolete("Use MindHasRole's output value")]
  379. public Entity<MindRoleComponent>? MindGetRole<T>(EntityUid mindId) where T : IComponent
  380. {
  381. Entity<MindRoleComponent>? result = null;
  382. var mind = Comp<MindComponent>(mindId);
  383. foreach (var uid in mind.MindRoles)
  384. {
  385. if (HasComp<T>(uid) && TryComp<MindRoleComponent>(uid, out var comp))
  386. result = (uid,comp);
  387. }
  388. return result;
  389. }
  390. /// <summary>
  391. /// Reads all Roles of a mind Entity and returns their data as RoleInfo
  392. /// </summary>
  393. /// <param name="mind">The mind entity</param>
  394. /// <returns>RoleInfo list</returns>
  395. public List<RoleInfo> MindGetAllRoleInfo(Entity<MindComponent?> mind)
  396. {
  397. var roleInfo = new List<RoleInfo>();
  398. if (!Resolve(mind.Owner, ref mind.Comp))
  399. return roleInfo;
  400. foreach (var role in mind.Comp.MindRoles)
  401. {
  402. var valid = false;
  403. var name = "game-ticker-unknown-role";
  404. var prototype = "";
  405. string? playTimeTracker = null;
  406. if (!TryComp(role, out MindRoleComponent? comp))
  407. {
  408. Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}");
  409. continue;
  410. }
  411. if (comp.AntagPrototype is not null)
  412. prototype = comp.AntagPrototype;
  413. if (comp.JobPrototype is not null && comp.AntagPrototype is null)
  414. {
  415. prototype = comp.JobPrototype;
  416. if (_prototypes.TryIndex(comp.JobPrototype, out var job))
  417. {
  418. playTimeTracker = job.PlayTimeTracker;
  419. name = job.Name;
  420. valid = true;
  421. }
  422. else
  423. {
  424. Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Job prototype: '{comp.JobPrototype}'");
  425. }
  426. }
  427. else if (comp.AntagPrototype is not null && comp.JobPrototype is null)
  428. {
  429. prototype = comp.AntagPrototype;
  430. if (_prototypes.TryIndex(comp.AntagPrototype, out var antag))
  431. {
  432. name = antag.Name;
  433. valid = true;
  434. }
  435. else
  436. {
  437. Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Antagonist prototype: '{comp.AntagPrototype}'");
  438. }
  439. }
  440. else if (comp.JobPrototype is not null && comp.AntagPrototype is not null)
  441. {
  442. Log.Error($" Mind Role Prototype '{role.Id}' contains both Job and Antagonist prototypes");
  443. }
  444. if (valid)
  445. roleInfo.Add(new RoleInfo(name, comp.Antag, playTimeTracker, prototype));
  446. }
  447. return roleInfo;
  448. }
  449. /// <summary>
  450. /// Does this mind possess an antagonist role
  451. /// </summary>
  452. /// <param name="mindId">The mind entity</param>
  453. /// <returns>True if the mind possesses any antag roles</returns>
  454. public bool MindIsAntagonist(EntityUid? mindId)
  455. {
  456. if (mindId is null)
  457. return false;
  458. return CheckAntagonistStatus(mindId.Value).Antag;
  459. }
  460. /// <summary>
  461. /// Does this mind possess an exclusive antagonist role
  462. /// </summary>
  463. /// <param name="mindId">The mind entity</param>
  464. /// <returns>True if the mind possesses any exclusive antag roles</returns>
  465. public bool MindIsExclusiveAntagonist(EntityUid? mindId)
  466. {
  467. if (mindId is null)
  468. return false;
  469. return CheckAntagonistStatus(mindId.Value).ExclusiveAntag;
  470. }
  471. private (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity<MindComponent?> mind)
  472. {
  473. if (!Resolve(mind.Owner, ref mind.Comp))
  474. return (false, false);
  475. var antagonist = false;
  476. var exclusiveAntag = false;
  477. foreach (var role in mind.Comp.MindRoles)
  478. {
  479. if (!TryComp<MindRoleComponent>(role, out var roleComp))
  480. {
  481. Log.Error($"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|");
  482. continue;
  483. }
  484. antagonist |= roleComp.Antag;
  485. exclusiveAntag |= roleComp.ExclusiveAntag;
  486. }
  487. return (antagonist, exclusiveAntag);
  488. }
  489. /// <summary>
  490. /// Play a sound for the mind, if it has a session attached.
  491. /// Use this for role greeting sounds.
  492. /// </summary>
  493. public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent? mind = null)
  494. {
  495. if (Resolve(mindId, ref mind) && mind.Session != null)
  496. _audio.PlayGlobal(sound, mind.Session);
  497. }
  498. // TODO ROLES Change to readonly.
  499. // Passing around a reference to a prototype's hashset makes me uncomfortable because it might be accidentally
  500. // mutated.
  501. public HashSet<JobRequirement>? GetJobRequirement(JobPrototype job)
  502. {
  503. if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req))
  504. return req;
  505. return job.Requirements;
  506. }
  507. // TODO ROLES Change to readonly.
  508. public HashSet<JobRequirement>? GetJobRequirement(ProtoId<JobPrototype> job)
  509. {
  510. if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req))
  511. return req;
  512. return _prototypes.Index(job).Requirements;
  513. }
  514. // TODO ROLES Change to readonly.
  515. public HashSet<JobRequirement>? GetAntagRequirement(ProtoId<AntagPrototype> antag)
  516. {
  517. if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req))
  518. return req;
  519. return _prototypes.Index(antag).Requirements;
  520. }
  521. // TODO ROLES Change to readonly.
  522. public HashSet<JobRequirement>? GetAntagRequirement(AntagPrototype antag)
  523. {
  524. if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req))
  525. return req;
  526. return antag.Requirements;
  527. }
  528. }
  529. /// <summary>
  530. /// Raised on the client to update Role Type on the character window, in case it happened to be open.
  531. /// </summary>
  532. [Serializable, NetSerializable]
  533. public sealed class MindRoleTypeChangedEvent : EntityEventArgs
  534. {
  535. }