From 4e0018697fbc358e6c0bb3243e67f05b126e5f9e Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:05:25 +1300 Subject: [PATCH] Add role prototype validation tests (#32801) * Add role prototype validation test * Rejig GetPrototypesWithComponent * More tests n stuff --- .../Pair/TestPair.Helpers.cs | 32 ++- .../Tests/Minds/RoleTests.cs | 95 +++++++++ .../Tests/Sprite/ItemSpriteTest.cs | 2 +- Content.IntegrationTests/Tests/StorageTest.cs | 7 +- .../EffectConditions/JobCondition.cs | 12 +- .../Rules/RevolutionaryRuleSystem.cs | 4 +- Content.Server/Ghost/Roles/GhostRoleSystem.cs | 4 +- Content.Shared/Roles/Jobs/SharedJobSystem.cs | 11 +- Content.Shared/Roles/SharedRoleSystem.cs | 193 +++++++++--------- 9 files changed, 238 insertions(+), 122 deletions(-) create mode 100644 Content.IntegrationTests/Tests/Minds/RoleTests.cs diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs index 4604cd82966..1b4825cc9c7 100644 --- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs +++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs @@ -107,13 +107,41 @@ public async Task WaitClientCommand(string cmd, int numTicks = 10) /// /// Retrieve all entity prototypes that have some component. /// - public List GetPrototypesWithComponent( + public List<(EntityPrototype, T)> GetPrototypesWithComponent( HashSet? ignored = null, bool ignoreAbstract = true, bool ignoreTestPrototypes = true) where T : IComponent { var id = Server.ResolveDependency().GetComponentName(typeof(T)); + var list = new List<(EntityPrototype, T)>(); + foreach (var proto in Server.ProtoMan.EnumeratePrototypes()) + { + if (ignored != null && ignored.Contains(proto.ID)) + continue; + + if (ignoreAbstract && proto.Abstract) + continue; + + if (ignoreTestPrototypes && IsTestPrototype(proto)) + continue; + + if (proto.Components.TryGetComponent(id, out var cmp)) + list.Add((proto, (T)cmp)); + } + + return list; + } + + /// + /// Retrieve all entity prototypes that have some component. + /// + public List GetPrototypesWithComponent(Type type, + HashSet? ignored = null, + bool ignoreAbstract = true, + bool ignoreTestPrototypes = true) + { + var id = Server.ResolveDependency().GetComponentName(type); var list = new List(); foreach (var proto in Server.ProtoMan.EnumeratePrototypes()) { @@ -127,7 +155,7 @@ public List GetPrototypesWithComponent( continue; if (proto.Components.ContainsKey(id)) - list.Add(proto); + list.Add((proto)); } return list; diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs new file mode 100644 index 00000000000..fcfe1236cfc --- /dev/null +++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs @@ -0,0 +1,95 @@ +using System.Linq; +using Content.Server.Roles; +using Content.Shared.Roles; +using Content.Shared.Roles.Jobs; +using Robust.Shared.GameObjects; +using Robust.Shared.Reflection; + +namespace Content.IntegrationTests.Tests.Minds; + +[TestFixture] +public sealed class RoleTests +{ + /// + /// Check that any prototype with a is properly configured + /// + [Test] + public async Task ValidateRolePrototypes() + { + await using var pair = await PoolManager.GetServerClient(); + + var jobComp = pair.Server.ResolveDependency().GetComponentName(typeof(JobRoleComponent)); + + Assert.Multiple(() => + { + foreach (var (proto, comp) in pair.GetPrototypesWithComponent()) + { + Assert.That(comp.AntagPrototype == null || comp.JobPrototype == null, $"Role {proto.ID} has both a job and antag prototype."); + Assert.That(!comp.ExclusiveAntag || comp.Antag, $"Role {proto.ID} is marked as an exclusive antag, despite not being an antag."); + Assert.That(comp.Antag || comp.AntagPrototype == null, $"Role {proto.ID} has an antag prototype, despite not being an antag."); + + if (comp.JobPrototype != null) + Assert.That(proto.Components.ContainsKey(jobComp), $"Role {proto.ID} is a job, despite not having a job prototype."); + + // It is possible that this is meant to be supported? Though I would assume that it would be for + // admin / prototype uploads, and that pre-defined roles should still check this. + Assert.That(!comp.Antag || comp.AntagPrototype != null , $"Role {proto.ID} is an antag, despite not having a antag prototype."); + } + }); + + await pair.CleanReturnAsync(); + } + + /// + /// Check that any prototype with a also has a properly configured + /// + /// + [Test] + public async Task ValidateJobPrototypes() + { + await using var pair = await PoolManager.GetServerClient(); + + var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent)); + + Assert.Multiple(() => + { + foreach (var (proto, comp) in pair.GetPrototypesWithComponent()) + { + if (proto.Components.TryGetComponent(mindCompId, out var mindComp)) + Assert.That(((MindRoleComponent)mindComp).JobPrototype, Is.Not.Null); + } + }); + + await pair.CleanReturnAsync(); + } + + /// + /// Check that any prototype with a component that inherits from also has a + /// + /// + [Test] + public async Task ValidateRolesHaveMindRoleComp() + { + await using var pair = await PoolManager.GetServerClient(); + + var refMan = pair.Server.ResolveDependency(); + var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent)); + + var compTypes = refMan.GetAllChildren(typeof(BaseMindRoleComponent)) + .Append(typeof(RoleBriefingComponent)) + .Where(x => !x.IsAbstract); + + Assert.Multiple(() => + { + foreach (var comp in compTypes) + { + foreach (var proto in pair.GetPrototypesWithComponent(comp)) + { + Assert.That(proto.Components.ContainsKey(mindCompId), $"Role {proto.ID} does not have a {nameof(MindRoleComponent)} despite having a {comp.Name}"); + } + } + }); + + await pair.CleanReturnAsync(); + } +} diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs index bf75188f029..da7e1e8e9b0 100644 --- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs +++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs @@ -40,7 +40,7 @@ public async Task AllItemsHaveSpritesTest() await pair.Client.WaitPost(() => { - foreach (var proto in pair.GetPrototypesWithComponent(Ignored)) + foreach (var (proto, _) in pair.GetPrototypesWithComponent(Ignored)) { var dummy = pair.Client.EntMan.Spawn(proto.ID); pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy)); diff --git a/Content.IntegrationTests/Tests/StorageTest.cs b/Content.IntegrationTests/Tests/StorageTest.cs index 2d28534347d..983ec709362 100644 --- a/Content.IntegrationTests/Tests/StorageTest.cs +++ b/Content.IntegrationTests/Tests/StorageTest.cs @@ -94,14 +94,13 @@ public async Task TestSufficientSpaceForFill() await Assert.MultipleAsync(async () => { - foreach (var proto in pair.GetPrototypesWithComponent()) + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) { if (proto.HasComponent(compFact)) continue; StorageComponent? storage = null; ItemComponent? item = null; - StorageFillComponent fill = default!; var size = 0; await server.WaitAssertion(() => { @@ -112,7 +111,6 @@ await server.WaitAssertion(() => } proto.TryGetComponent("Item", out item); - fill = (StorageFillComponent) proto.Components[id].Component; size = GetFillSize(fill, false, protoMan, itemSys); }); @@ -179,7 +177,7 @@ public async Task TestSufficientSpaceForEntityStorageFill() var itemSys = entMan.System(); - foreach (var proto in pair.GetPrototypesWithComponent()) + foreach (var (proto, fill) in pair.GetPrototypesWithComponent()) { if (proto.HasComponent(compFact)) continue; @@ -192,7 +190,6 @@ await server.WaitAssertion(() => if (entStorage == null) return; - var fill = (StorageFillComponent) proto.Components[id].Component; var size = GetFillSize(fill, true, protoMan, itemSys); Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity), $"{proto.ID} storage fill is too large."); diff --git a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs index 9c7bda839e4..9621d6945f6 100644 --- a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs @@ -26,9 +26,17 @@ public override bool Condition(EntityEffectBaseArgs args) if(!args.EntityManager.HasComponent(roleId)) continue; - if(!args.EntityManager.TryGetComponent(roleId, out var mindRole) - || mindRole.JobPrototype is null) + if (!args.EntityManager.TryGetComponent(roleId, out var mindRole)) + { + Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(MindRoleComponent)}"); continue; + } + + if (mindRole.JobPrototype == null) + { + Logger.Error($"Encountered job mind role entity {roleId} without a {nameof(JobPrototype)}"); + continue; + } if (Job.Contains(mindRole.JobPrototype.Value)) return true; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index 939ab871153..a313b78eaf1 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -155,8 +155,8 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _)) { - if (_role.MindHasRole(revMindId, out _, out var role)) - role.Value.Comp.ConvertedCount++; + if (_role.MindHasRole(revMindId, out var role)) + role.Value.Comp2.ConvertedCount++; } } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index bb95b827a7f..cd01c964ef6 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -516,8 +516,8 @@ public void GhostRoleInternalCreateMindAndTransfer(ICommonSession player, Entity _roleSystem.MindAddRole(newMind, "MindRoleGhostMarker"); - if(_roleSystem.MindHasRole(newMind, out _, out var markerRole)) - markerRole.Value.Comp.Name = role.RoleName; + if(_roleSystem.MindHasRole(newMind!, out var markerRole)) + markerRole.Value.Comp2.Name = role.RoleName; _mindSystem.SetUserId(newMind, player.UserId); _mindSystem.TransferTo(newMind, mob); diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 94447a5af46..8a4733c8340 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -103,7 +103,6 @@ public bool TryGetPrimaryDepartment(string jobProto, [NotNullWhen(true)] out Dep public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { - MindRoleComponent? comp = null; if (mindId is null) return false; @@ -112,9 +111,7 @@ public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) if (role is null) return false; - comp = role.Value.Comp; - - return (comp.JobPrototype == prototypeId); + return role.Value.Comp1.JobPrototype == prototypeId; } public bool MindTryGetJob( @@ -124,7 +121,7 @@ public bool MindTryGetJob( prototype = null; MindTryGetJobId(mindId, out var protoId); - return (_prototypes.TryIndex(protoId, out prototype) || prototype is not null); + return _prototypes.TryIndex(protoId, out prototype) || prototype is not null; } public bool MindTryGetJobId( @@ -137,9 +134,9 @@ public bool MindTryGetJobId( return false; if (_roles.MindHasRole(mindId.Value, out var role)) - job = role.Value.Comp.JobPrototype; + job = role.Value.Comp1.JobPrototype; - return (job is not null); + return job is not null; } /// diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 925f61e7c75..00271693abe 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -10,6 +10,7 @@ using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Shared.Roles; @@ -92,19 +93,18 @@ public void MindAddJobRole(EntityUid mindId, bool silent = false, string? jobPrototype = null) { + if (!Resolve(mindId, ref mind)) + return; + // Can't have someone get paid for two jobs now, can we - if (MindHasRole(mindId, out var jobRole) - && jobRole.Value.Comp.JobPrototype != jobPrototype) + if (MindHasRole((mindId, mind), out var jobRole) + && jobRole.Value.Comp1.JobPrototype != jobPrototype) { - Resolve(mindId, ref mind); - if (mind is not null) - { - _adminLogger.Add(LogType.Mind, - LogImpact.Low, - $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'"); - } + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp1.JobPrototype}' to '{jobPrototype}'"); - jobRole.Value.Comp.JobPrototype = jobPrototype; + jobRole.Value.Comp1.JobPrototype = jobPrototype; } else MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype); @@ -146,11 +146,12 @@ private void MindAddRoleDo(EntityUid mindId, { mindRoleComp.JobPrototype = jobPrototype; EnsureComp(mindRoleId); + DebugTools.AssertNull(mindRoleComp.AntagPrototype); + DebugTools.Assert(!mindRoleComp.Antag); + DebugTools.Assert(!mindRoleComp.ExclusiveAntag); } - if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag) - antagonist = true; - + antagonist |= mindRoleComp.Antag; mind.MindRoles.Add(mindRoleId); var mindEv = new MindRoleAddedEvent(silent); @@ -182,51 +183,55 @@ private void MindAddRoleDo(EntityUid mindId, /// /// Removes all instances of a specific role from this mind. /// - /// The mind to remove the role from. + /// The mind to remove the role from. /// The type of the role to remove. - /// Thrown if the mind does not exist or does not have this role. - /// Returns False if there was something wrong with the mind or the removal. True if successful> - public bool MindRemoveRole(EntityUid mindId) where T : IComponent + /// Returns false if the role did not exist. True if successful> + public bool MindRemoveRole(Entity mind) where T : IComponent { - if (!TryComp(mindId, out var mind) ) - throw new ArgumentException($"{mindId} does not exist or does not have mind component"); + if (typeof(T) == typeof(MindRoleComponent)) + throw new InvalidOperationException(); + + if (!Resolve(mind.Owner, ref mind.Comp)) + return false; var found = false; var antagonist = false; var delete = new List(); - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { if (!HasComp(role)) continue; - var roleComp = Comp(role); - antagonist = roleComp.Antag; - _entityManager.DeleteEntity(role); + if (!TryComp(role, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}"); + continue; + } + antagonist |= roleComp.Antag | roleComp.ExclusiveAntag; + _entityManager.DeleteEntity(role); delete.Add(role); found = true; - } + if (!found) + return false; + foreach (var role in delete) { - mind.MindRoles.Remove(role); + mind.Comp.MindRoles.Remove(role); } - if (!found) + if (mind.Comp.OwnedEntity != null) { - throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}"); + var message = new RoleRemovedEvent(mind.Owner, mind.Comp, antagonist); + RaiseLocalEvent(mind.Comp.OwnedEntity.Value, message, true); } - var message = new RoleRemovedEvent(mindId, mind, antagonist); - - if (mind.OwnedEntity != null) - { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); - } _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {typeof(T).Name}' removed from mind of {ToPrettyString(mind.OwnedEntity)}"); + $"All roles of type '{typeof(T).Name}' removed from mind of {ToPrettyString(mind.Comp.OwnedEntity)}"); + return true; } @@ -238,16 +243,14 @@ public bool MindRemoveRole(EntityUid mindId) where T : IComponent /// True if the role existed and was removed public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent { - if (!MindHasRole(mindId)) - { - Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role "); - return false; - } - if (typeof(T) == typeof(MindRoleComponent)) return false; - return MindRemoveRole(mindId); + if (MindRemoveRole(mindId)) + return true; + + Log.Warning($"Failed to remove role {typeof(T)} from {ToPrettyString(mindId)} : mind does not have role "); + return false; } /// @@ -259,30 +262,29 @@ public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent /// The Mind Role entity component /// The Mind Role's entity component for T /// True if the role is found - public bool MindHasRole(EntityUid mindId, - [NotNullWhen(true)] out Entity? role, - [NotNullWhen(true)] out Entity? roleT) where T : IComponent + public bool MindHasRole(Entity mind, + [NotNullWhen(true)] out Entity? role) where T : IComponent { role = null; - roleT = null; - - if (!TryComp(mindId, out var mind)) + if (!Resolve(mind.Owner, ref mind.Comp)) return false; - var found = false; - - foreach (var roleEnt in mind.MindRoles) + foreach (var roleEnt in mind.Comp.MindRoles) { - if (!HasComp(roleEnt)) + if (!TryComp(roleEnt, out T? tcomp)) continue; - role = (roleEnt,Comp(roleEnt)); - roleT = (roleEnt,Comp(roleEnt)); - found = true; - break; + if (!TryComp(roleEnt, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}"); + continue; + } + + role = (roleEnt, roleComp, tcomp); + return true; } - return found; + return false; } /// @@ -317,7 +319,13 @@ public bool MindHasRole(EntityUid mindId, if (!HasComp(roleEnt, type)) continue; - role = (roleEnt,Comp(roleEnt)); + if (!TryComp(roleEnt, out MindRoleComponent? roleComp)) + { + Log.Error($"Encountered mind role entity {ToPrettyString(roleEnt)} without a {nameof(MindRoleComponent)}"); + continue; + } + + role = (roleEnt, roleComp); found = true; break; } @@ -325,20 +333,6 @@ public bool MindHasRole(EntityUid mindId, return found; } - /// - /// Finds the first mind role of a specific type on a mind entity. - /// Outputs an entity component for the mind role's MindRoleComponent - /// - /// The mind entity - /// The Mind Role entity component - /// The type of the role to find. - /// True if the role is found - public bool MindHasRole(EntityUid mindId, - [NotNullWhen(true)] out Entity? role) where T : IComponent - { - return MindHasRole(mindId, out role, out _); - } - /// /// Finds the first mind role of a specific type on a mind entity. /// @@ -347,7 +341,7 @@ public bool MindHasRole(EntityUid mindId, /// True if the role is found public bool MindHasRole(EntityUid mindId) where T : IComponent { - return MindHasRole(mindId, out _, out _); + return MindHasRole(mindId, out _); } //TODO: Delete this later @@ -374,28 +368,31 @@ public bool MindHasRole(EntityUid mindId) where T : IComponent /// /// Reads all Roles of a mind Entity and returns their data as RoleInfo /// - /// The mind entity + /// The mind entity /// RoleInfo list - public List MindGetAllRoleInfo(EntityUid mindId) + public List MindGetAllRoleInfo(Entity mind) { var roleInfo = new List(); - if (!TryComp(mindId, out var mind)) + if (!Resolve(mind.Owner, ref mind.Comp)) return roleInfo; - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { var valid = false; var name = "game-ticker-unknown-role"; var prototype = ""; - string? playTimeTracker = null; + string? playTimeTracker = null; - var comp = Comp(role); - if (comp.AntagPrototype is not null) + if (!TryComp(role, out MindRoleComponent? comp)) { - prototype = comp.AntagPrototype; + Log.Error($"Encountered mind role entity {ToPrettyString(role)} without a {nameof(MindRoleComponent)}"); + continue; } + if (comp.AntagPrototype is not null) + prototype = comp.AntagPrototype; + if (comp.JobPrototype is not null && comp.AntagPrototype is null) { prototype = comp.JobPrototype; @@ -429,7 +426,7 @@ public List MindGetAllRoleInfo(EntityUid mindId) } if (valid) - roleInfo.Add(new RoleInfo(name, comp.Antag || comp.ExclusiveAntag , playTimeTracker, prototype)); + roleInfo.Add(new RoleInfo(name, comp.Antag, playTimeTracker, prototype)); } return roleInfo; } @@ -442,12 +439,9 @@ public List MindGetAllRoleInfo(EntityUid mindId) public bool MindIsAntagonist(EntityUid? mindId) { if (mindId is null) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; - } - return CheckAntagonistStatus(mindId.Value).Item1; + return CheckAntagonistStatus(mindId.Value).Antag; } /// @@ -458,37 +452,28 @@ public bool MindIsAntagonist(EntityUid? mindId) public bool MindIsExclusiveAntagonist(EntityUid? mindId) { if (mindId is null) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; - } - return CheckAntagonistStatus(mindId.Value).Item2; + return CheckAntagonistStatus(mindId.Value).ExclusiveAntag; } - private (bool, bool) CheckAntagonistStatus(EntityUid mindId) + public (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity mind) { - if (!TryComp(mindId, out var mind)) - { - Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found"); + if (!Resolve(mind.Owner, ref mind.Comp)) return (false, false); - } var antagonist = false; var exclusiveAntag = false; - foreach (var role in mind.MindRoles) + foreach (var role in mind.Comp.MindRoles) { if (!TryComp(role, out var roleComp)) { - //If this ever shows up outside of an integration test, then we need to look into this further. - Log.Warning($"Mind Role Entity {role} does not have MindRoleComponent!"); + Log.Error($"Mind Role Entity {ToPrettyString(role)} does not have a MindRoleComponent, despite being listed as a role belonging to {ToPrettyString(mind)}|"); continue; } - if (roleComp.Antag || exclusiveAntag) - antagonist = true; - if (roleComp.ExclusiveAntag) - exclusiveAntag = true; + antagonist |= roleComp.Antag; + exclusiveAntag |= roleComp.ExclusiveAntag; } return (antagonist, exclusiveAntag); @@ -504,6 +489,9 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent _audio.PlayGlobal(sound, mind.Session); } + // TODO ROLES Change to readonly. + // Passing around a reference to a prototype's hashset makes me uncomfortable because it might be accidentally + // mutated. public HashSet? GetJobRequirement(JobPrototype job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) @@ -512,6 +500,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return job.Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetJobRequirement(ProtoId job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req)) @@ -520,6 +509,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return _prototypes.Index(job).Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetAntagRequirement(ProtoId antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req)) @@ -528,6 +518,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent return _prototypes.Index(antag).Requirements; } + // TODO ROLES Change to readonly. public HashSet? GetAntagRequirement(AntagPrototype antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req))