From 93c7bdc1345dd3b76727a386fe7ce30e6edd28b2 Mon Sep 17 00:00:00 2001 From: Errant <35878406+Errant-4@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:48:56 +0200 Subject: [PATCH] Mind Role Entities (#31318) * Mind Role Entities wip * headrev count fix * silicon stuff, cleanup * exclusive antag config, cleanup * jobroleadd overwerite * logging stuff * MindHasRole cleanup, admin log stuff * last second cleanup * ocd * minor cleanup * remove createdTime datafield * now actually using the event replacement I made for role time tracking * weh --- .../Tests/GameRules/NukeOpsTest.cs | 21 +- .../Tests/Internals/AutoInternalsTests.cs | 6 +- .../Tests/Minds/MindTests.cs | 21 +- .../Tests/Preferences/LoadoutTests.cs | 6 +- Content.Server/Antag/AntagSelectionSystem.cs | 27 +- .../Components/AntagSelectionComponent.cs | 8 +- Content.Server/Cloning/CloningSystem.cs | 2 +- .../EffectConditions/JobCondition.cs | 44 +- .../GameTicking/GameTicker.RoundFlow.cs | 6 +- .../GameTicking/GameTicker.Spawning.cs | 13 +- .../GameTicking/Rules/NukeopsRuleSystem.cs | 37 +- .../Rules/RevolutionaryRuleSystem.cs | 37 +- .../GameTicking/Rules/ThiefRuleSystem.cs | 35 +- .../GameTicking/Rules/TraitorRuleSystem.cs | 31 +- .../GameTicking/Rules/ZombieRuleSystem.cs | 29 +- .../Roles/GhostRoleMarkerRoleComponent.cs | 6 +- Content.Server/Ghost/Roles/GhostRoleSystem.cs | 17 +- .../Mind/Commands/MindInfoCommand.cs | 2 +- .../Systems/KillPersonConditionSystem.cs | 2 +- .../Systems/NinjaConditionsSystem.cs | 8 +- .../Systems/NotCommandRequirementSystem.cs | 2 +- .../Systems/NotJobRequirementSystem.cs | 9 +- .../Systems/RoleRequirementSystem.cs | 2 - .../PlayTimeTrackingSystem.cs | 14 +- .../Components/CommandStaffComponent.cs | 2 + Content.Server/Roles/DragonRoleComponent.cs | 6 +- .../Roles/InitialInfectedRoleComponent.cs | 7 +- Content.Server/Roles/Jobs/JobSystem.cs | 4 +- Content.Server/Roles/NinjaRoleComponent.cs | 7 +- Content.Server/Roles/NukeopsRoleComponent.cs | 6 +- Content.Server/Roles/RemoveRoleCommand.cs | 2 +- .../Roles/RevolutionaryRoleComponent.cs | 6 +- Content.Server/Roles/RoleSystem.cs | 50 +- .../Roles/SubvertedSiliconRoleComponent.cs | 5 +- Content.Server/Roles/ThiefRoleComponent.cs | 5 +- Content.Server/Roles/TraitorRoleComponent.cs | 7 +- Content.Server/Roles/ZombieRoleComponent.cs | 7 +- .../Silicons/Laws/SiliconLawSystem.cs | 12 +- .../ContainerSpawnPointSystem.cs | 10 +- .../EntitySystems/SpawnPointSystem.cs | 2 +- .../Station/Systems/StationSpawningSystem.cs | 30 +- .../Store/Conditions/BuyerAntagCondition.cs | 11 +- .../Conditions/BuyerDepartmentCondition.cs | 10 +- .../Store/Conditions/BuyerJobCondition.cs | 6 +- .../Zombies/ZombieSystem.Transform.cs | 17 +- .../Mind/Components/MindContainerComponent.cs | 2 - Content.Shared/Mind/MindComponent.cs | 13 +- .../Roles/AntagonistRoleComponent.cs | 20 - Content.Shared/Roles/Jobs/JobComponent.cs | 14 - Content.Shared/Roles/Jobs/JobRoleComponent.cs | 12 + Content.Shared/Roles/Jobs/SharedJobSystem.cs | 46 +- ...lesEvent.cs => MindGetAllRoleInfoEvent.cs} | 4 +- Content.Shared/Roles/MindRoleComponent.cs | 48 ++ Content.Shared/Roles/SharedRoleSystem.cs | 482 +++++++++++++----- Content.Shared/Whitelist/EntityWhitelist.cs | 6 + .../Whitelist/EntityWhitelistSystem.cs | 46 +- .../game-presets/preset-nukeops.ftl | 1 + Resources/Prototypes/GameRules/events.yml | 26 +- Resources/Prototypes/GameRules/midround.yml | 5 +- Resources/Prototypes/GameRules/roundstart.yml | 30 +- Resources/Prototypes/Objectives/dragon.yml | 2 +- Resources/Prototypes/Objectives/ninja.yml | 2 +- Resources/Prototypes/Objectives/thief.yml | 2 +- Resources/Prototypes/Objectives/traitor.yml | 2 +- .../Prototypes/Roles/MindRoles/mind_roles.yml | 180 +++++++ 65 files changed, 1033 insertions(+), 507 deletions(-) delete mode 100644 Content.Shared/Roles/AntagonistRoleComponent.cs delete mode 100644 Content.Shared/Roles/Jobs/JobComponent.cs create mode 100644 Content.Shared/Roles/Jobs/JobRoleComponent.cs rename Content.Shared/Roles/{MindGetAllRolesEvent.cs => MindGetAllRoleInfoEvent.cs} (79%) create mode 100644 Content.Shared/Roles/MindRoleComponent.cs create mode 100644 Resources/Prototypes/Roles/MindRoles/mind_roles.yml diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs index a4563aa37e..039c0c7b18 100644 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -1,4 +1,5 @@ #nullable enable +using System.Collections.Generic; using System.Linq; using Content.Server.Body.Components; using Content.Server.GameTicking; @@ -120,8 +121,8 @@ public sealed class NukeOpsTest Assert.That(roleSys.MindHasRole(mind)); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); - var roles = roleSys.MindGetAllRoles(mind); - var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); + var roles = roleSys.MindGetAllRoleInfo(mind); + var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander"); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); // The second dummy player should be a medic @@ -131,8 +132,8 @@ public sealed class NukeOpsTest Assert.That(roleSys.MindHasRole(dummyMind)); Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); - roles = roleSys.MindGetAllRoles(dummyMind); - cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); + roles = roleSys.MindGetAllRoleInfo(dummyMind); + cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic"); Assert.That(cmdRoles.Count(), Is.EqualTo(1)); // The other two players should have just spawned in as normal. @@ -141,13 +142,14 @@ public sealed class NukeOpsTest void CheckDummy(int i) { var ent = dummyEnts[i]; - var mind = mindSys.GetMind(ent)!.Value; + var mindCrew = mindSys.GetMind(ent)!.Value; Assert.That(entMan.HasComponent(ent), Is.False); - Assert.That(roleSys.MindIsAntagonist(mind), Is.False); - Assert.That(roleSys.MindHasRole(mind), Is.False); + Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False); + Assert.That(roleSys.MindHasRole(mindCrew), Is.False); Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); - Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False); + var nukeroles = new List() { "Nukeops", "NukeopsMedic", "NukeopsCommander" }; + Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False); } // The game rule exists, and all the stations/shuttles/maps are properly initialized @@ -238,7 +240,8 @@ public sealed class NukeOpsTest for (var i = 0; i < nukies.Length - 1; i++) { entMan.DeleteEntity(nukies[i]); - Assert.That(roundEndSys.IsRoundEndRequested, Is.False, + Assert.That(roundEndSys.IsRoundEndRequested, + Is.False, $"The round ended, but {nukies.Length - i - 1} nukies are still alive!"); } // Delete the last nukie and make sure the round ends. diff --git a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs index dbd612c710..d153536873 100644 --- a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs +++ b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs @@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Body.Systems; using Content.Server.Station.Systems; using Content.Shared.Preferences; -using Content.Shared.Roles.Jobs; namespace Content.IntegrationTests.Tests.Internals; @@ -25,10 +24,7 @@ public sealed class AutoInternalsTests await server.WaitAssertion(() => { var profile = new HumanoidCharacterProfile(); - var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent() - { - Prototype = "TestInternalsDummy" - }, profile, station: null); + var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null); Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!"); Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!"); diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs index 3b7ecf0135..d4d551f4e0 100644 --- a/Content.IntegrationTests/Tests/Minds/MindTests.cs +++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs @@ -1,10 +1,8 @@ #nullable enable using System.Linq; -using Content.Server.Ghost; using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles.Components; using Content.Server.Mind.Commands; -using Content.Server.Players; using Content.Server.Roles; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; @@ -18,7 +16,6 @@ using Robust.Server.Console; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Prototypes; @@ -287,27 +284,27 @@ public sealed partial class MindTests Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); - var traitorRole = new TraitorRoleComponent(); + var traitorRole = "MindRoleTraitor"; roleSystem.MindAddRole(mindId, traitorRole); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId)); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); - var jobRole = new JobComponent(); + var jobRole = ""; - roleSystem.MindAddRole(mindId, jobRole); + roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId)); - Assert.That(roleSystem.MindHasRole(mindId)); + Assert.That(roleSystem.MindHasRole(mindId)); }); roleSystem.MindRemoveRole(mindId); @@ -315,15 +312,15 @@ public sealed partial class MindTests Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId)); + Assert.That(roleSystem.MindHasRole(mindId)); }); - roleSystem.MindRemoveRole(mindId); + roleSystem.MindRemoveRole(mindId); Assert.Multiple(() => { Assert.That(roleSystem.MindHasRole(mindId), Is.False); - Assert.That(roleSystem.MindHasRole(mindId), Is.False); + Assert.That(roleSystem.MindHasRole(mindId), Is.False); }); }); diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs index 6746d6d5a9..267b3637e0 100644 --- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs +++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs @@ -3,7 +3,6 @@ using Content.Server.Station.Systems; using Content.Shared.Inventory; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Roles.Jobs; using Robust.Shared.GameObjects; using Robust.Shared.Prototypes; @@ -68,10 +67,7 @@ public sealed class LoadoutTests profile.SetLoadout(new RoleLoadout("LoadoutTester")); - var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent() - { - Prototype = "LoadoutTester" - }, profile, station: null); + var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null); var slotQuery = inventorySystem.GetSlotEnumerator(tester); var checkedCount = 0; diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 3f33d01116..224629ff2e 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -11,7 +11,6 @@ using Content.Server.Preferences.Managers; using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Shuttles.Components; -using Content.Server.Station.Systems; using Content.Shared.Antag; using Content.Shared.Clothing; using Content.Shared.GameTicking; @@ -20,7 +19,6 @@ using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Players; -using Content.Shared.Preferences.Loadouts; using Content.Shared.Roles; using Content.Shared.Whitelist; using Robust.Server.Audio; @@ -37,14 +35,14 @@ namespace Content.Server.Antag; public sealed partial class AntagSelectionSystem : GameRuleSystem { - [Dependency] private readonly IChatManager _chat = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IServerPreferencesManager _pref = default!; [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly IChatManager _chat = default!; [Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly JobSystem _jobs = default!; [Dependency] private readonly LoadoutSystem _loadout = default!; [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerPreferencesManager _pref = default!; [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; @@ -193,6 +191,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Chooses antagonists from the given selection of players /// + /// The antagonist rule entity + /// The players to choose from + /// Disable picking players for pre-spawn antags in the middle of a round public void ChooseAntags(Entity ent, IList pool, bool midround = false) { if (ent.Comp.SelectionsComplete) @@ -209,8 +210,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Chooses antagonists from the given selection of players for the given antag definition. /// + /// The antagonist rule entity + /// The players to choose from + /// The antagonist selection parameters and criteria /// Disable picking players for pre-spawn antags in the middle of a round - public void ChooseAntags(Entity ent, IList pool, AntagSelectionDefinition def, bool midround = false) + public void ChooseAntags(Entity ent, + IList pool, + AntagSelectionDefinition def, + bool midround = false) { var playerPool = GetPlayerPool(ent, pool, def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); @@ -331,7 +338,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem>? gear = new(); + List> gear = new(); if (def.StartingGear is not null) gear.Add(def.StartingGear.Value); @@ -340,8 +347,8 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem(curMind.Value, out var mindComp) || mindComp.OwnedEntity != antagEnt) { @@ -350,7 +357,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem /// Components added to the player's mind. + /// Do NOT use this to add role-type components. Add those as MindRoles instead /// [DataField] public ComponentRegistry MindComponents = new(); + /// + /// List of Mind Role Prototypes to be added to the player's mind. + /// + [DataField] + public List>? MindRoles; + /// /// A set of starting gear that's equipped to the player. /// diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 3893f31d25..65f9274001 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -231,7 +231,7 @@ namespace Content.Server.Cloning // TODO: Ideally, components like this should be components on the mind entity so this isn't necessary. // Add on special job components to the mob. - if (_jobs.MindTryGetJob(mindEnt, out _, out var prototype)) + if (_jobs.MindTryGetJob(mindEnt, out var prototype)) { foreach (var special in prototype.Special) { diff --git a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs index 20d67d6de0..9c7bda839e 100644 --- a/Content.Server/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Server/EntityEffects/EffectConditions/JobCondition.cs @@ -1,49 +1,45 @@ using System.Linq; using Content.Shared.EntityEffects; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Localizations; -using Robust.Shared.Prototypes; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; -using Content.Shared.Station; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; -using Robust.Shared.IoC; +using Robust.Shared.Prototypes; namespace Content.Server.EntityEffects.EffectConditions; public sealed partial class JobCondition : EntityEffectCondition { [DataField(required: true)] public List> Job; - + public override bool Condition(EntityEffectBaseArgs args) - { + { args.EntityManager.TryGetComponent(args.TargetEntity, out var mindContainer); - if (mindContainer != null && mindContainer.Mind != null) + + if ( mindContainer is null + || !args.EntityManager.TryGetComponent(mindContainer.Mind, out var mind)) + return false; + + foreach (var roleId in mind.MindRoles) { - var prototypeManager = IoCManager.Resolve(); - if (args.EntityManager.TryGetComponent(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype)) - { - foreach (var jobId in Job) - { - if (prototype.ID == jobId) - { - return true; - } - } - } + if(!args.EntityManager.HasComponent(roleId)) + continue; + + if(!args.EntityManager.TryGetComponent(roleId, out var mindRole) + || mindRole.JobPrototype is null) + continue; + + if (Job.Contains(mindRole.JobPrototype.Value)) + return true; } - + return false; } - + public override string GuidebookExplanation(IPrototypeManager prototype) { var localizedNames = Job.Select(jobId => prototype.Index(jobId).LocalizedName).ToList(); return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames))); } } - - diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs index 0c7668d354..683061d8ed 100644 --- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs +++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs @@ -4,6 +4,7 @@ using Content.Server.Discord; using Content.Server.GameTicking.Events; using Content.Server.Ghost; using Content.Server.Maps; +using Content.Server.Roles; using Content.Shared.CCVar; using Content.Shared.Database; using Content.Shared.GameTicking; @@ -26,6 +27,7 @@ namespace Content.Server.GameTicking public sealed partial class GameTicker { [Dependency] private readonly DiscordWebhook _discord = default!; + [Dependency] private readonly RoleSystem _role = default!; [Dependency] private readonly ITaskManager _taskManager = default!; private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( @@ -373,7 +375,7 @@ namespace Content.Server.GameTicking var userId = mind.UserId ?? mind.OriginalOwnerUserId; var connected = false; - var observer = HasComp(mindId); + var observer = _role.MindHasRole(mindId); // Continuing if (userId != null && _playerManager.ValidSessionId(userId.Value)) { @@ -400,7 +402,7 @@ namespace Content.Server.GameTicking _pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true); } - var roles = _roles.MindGetAllRoles(mindId); + var roles = _roles.MindGetAllRoleInfo(mindId); var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo() { diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index 53dc712dae..9c8b04770f 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Numerics; using Content.Server.Administration.Managers; using Content.Server.GameTicking.Events; -using Content.Server.Ghost; -using Content.Server.Shuttles.Components; using Content.Server.Spawners.Components; using Content.Server.Speech.Components; using Content.Server.Station.Components; @@ -224,13 +222,12 @@ namespace Content.Server.GameTicking _mind.SetUserId(newMind, data.UserId); var jobPrototype = _prototypeManager.Index(jobId); - var job = new JobComponent {Prototype = jobId}; - _roles.MindAddRole(newMind, job, silent: silent); + _roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId); var jobName = _jobs.MindTryGetJobName(newMind); _playTimeTrackings.PlayerRolesChanged(player); - var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character); + var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character); DebugTools.AssertNotNull(mobMaybe); var mob = mobMaybe!.Value; @@ -269,13 +266,17 @@ namespace Content.Server.GameTicking _stationJobs.TryAssignJob(station, jobPrototype, player.UserId); if (lateJoin) + { _adminLogger.Add(LogType.LateJoin, LogImpact.Medium, $"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + } else + { _adminLogger.Add(LogType.RoundStartJoin, LogImpact.Medium, $"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); + } // Make sure they're aware of extended access. if (Comp(station).ExtendedAccess @@ -361,7 +362,7 @@ namespace Content.Server.GameTicking var (mindId, mindComp) = _mind.CreateMind(player.UserId, name); mind = (mindId, mindComp); _mind.SetUserId(mind.Value, player.UserId); - _roles.MindAddRole(mind.Value, new ObserverRoleComponent()); + _roles.MindAddRole(mind.Value, "MindRoleObserver"); } var ghost = _ghost.SpawnGhost(mind.Value); diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 57239ee8c1..ca6548301a 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -9,7 +9,6 @@ using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; -using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Shared.GameTicking.Components; using Content.Shared.Mobs; @@ -31,9 +30,9 @@ namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; [Dependency] private readonly StoreSystem _store = default!; @@ -57,6 +56,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnOperativeZombified); + SubscribeLocalEvent(OnGetBriefing); + SubscribeLocalEvent(OnShuttleFTLAttempt); SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); @@ -65,7 +66,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem SubscribeLocalEvent(OnRuleLoadedGrids); } - protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + protected override void Started(EntityUid uid, + NukeopsRuleComponent component, + GameRuleComponent gameRule, GameRuleStartedEvent args) { var eligible = new List>(); @@ -85,7 +88,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem } #region Event Handlers - protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + protected override void AppendRoundEndText(EntityUid uid, + NukeopsRuleComponent component, + GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); @@ -227,7 +232,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem // If the disk is currently at Central Command, the crew wins - just slightly. // This also implies that some nuclear operatives have died. - SetWinType(ent, diskAtCentCom + SetWinType(ent, + diskAtCentCom ? WinType.CrewMinor : WinType.OpsMinor); ent.Comp.WinConditions.Add(diskAtCentCom @@ -456,8 +462,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem : WinCondition.AllNukiesDead); SetWinType(ent, WinType.CrewMajor, false); - _roundEndSystem.DoRoundEndBehavior( - nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); + _roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior, + nukeops.EvacShuttleTime, + nukeops.RoundEndTextSender, + nukeops.RoundEndTextShuttleCall, + nukeops.RoundEndTextAnnouncement); // prevent it called multiple times nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; @@ -465,16 +474,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - if (ent.Comp.TargetStation is not { } station) - return; + var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target"; - _antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", - ("station", station), + _antag.SendBriefing(args.Session, + Loc.GetString("nukeops-welcome", + ("station", target), ("name", Name(ent))), Color.Red, ent.Comp.GreetSoundNotification); } + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) + { + // TODO Different character screen briefing for the 3 nukie types + args.Append(Loc.GetString("nukeops-briefing")); + } + /// /// Is this method the shitty glue holding together the last of my sanity? yes. /// Do i have a better solution? not presently. diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index c5f88ab6cf..939ab87115 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -15,7 +15,6 @@ using Content.Shared.Database; using Content.Shared.GameTicking.Components; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; -using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mindshield.Components; using Content.Shared.Mobs; @@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules; public sealed class RevolutionaryRuleSystem : GameRuleSystem { [Dependency] private readonly IAdminLogManager _adminLogManager = default!; - [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; [Dependency] private readonly EuiManager _euiMan = default!; [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly MobStateSystem _mobState = default!; @@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -59,9 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(OnCommandMobStateChanged); + + SubscribeLocalEvent(OnPostFlash); SubscribeLocalEvent(OnHeadRevMobStateChanged); + SubscribeLocalEvent(OnGetBriefing); - SubscribeLocalEvent(OnPostFlash); + } protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) @@ -85,7 +87,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(mind)?.ConvertedCount ?? 0; + _role.MindHasRole(mind, out var role); + var count = CompOrNull(role)?.ConvertedCount ?? 0; + args.AddLine(Loc.GetString("rev-headrev-name-user", ("name", name), ("username", data.UserName), @@ -113,10 +119,8 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(uid, out var mind) || mind.OwnedEntity == null) - return; - - var head = HasComp(mind.OwnedEntity); + var ent = args.Mind.Comp.OwnedEntity; + var head = HasComp(ent); args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing")); } @@ -145,15 +149,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(ev.User.Value, out var headrev)) - headrev.ConvertedCount++; + if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _)) + { + if (_role.MindHasRole(revMindId, out _, out var role)) + role.Value.Comp.ConvertedCount++; + } } if (mindId == default || !_role.MindHasRole(mindId)) { - _role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId }); + _role.MindAddRole(mindId, "MindRoleRevolutionary"); } if (mind?.Session != null) diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 074b4c0df7..b00ed38636 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -1,18 +1,12 @@ using Content.Server.Antag; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Mind; -using Content.Server.Objectives; using Content.Server.Roles; using Content.Shared.Humanoid; -using Content.Shared.Mind; -using Content.Shared.Objectives.Components; -using Robust.Shared.Random; namespace Content.Server.GameTicking.Rules; public sealed class ThiefRuleSystem : GameRuleSystem { - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; public override void Initialize() @@ -24,32 +18,33 @@ public sealed class ThiefRuleSystem : GameRuleSystem SubscribeLocalEvent(OnGetBriefing); } - private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + // Greeting upon thief activation + private void AfterAntagSelected(Entity mindId, ref AfterAntagEntitySelectedEvent args) { - if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) - return; - - //Generate objectives - _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); + var ent = args.EntityUid; + _antag.SendBriefing(ent, MakeBriefing(ent), null, null); } - //Add mind briefing - private void OnGetBriefing(Entity thief, ref GetBriefingEvent args) + // Character screen briefing + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(thief.Owner, out var mind) || mind.OwnedEntity == null) - return; + var ent = args.Mind.Comp.OwnedEntity; - args.Append(MakeBriefing(mind.OwnedEntity.Value)); + if (ent is null) + return; + args.Append(MakeBriefing(ent.Value)); } - private string MakeBriefing(EntityUid thief) + private string MakeBriefing(EntityUid ent) { - var isHuman = HasComp(thief); + var isHuman = HasComp(ent); var briefing = isHuman ? Loc.GetString("thief-role-greeting-human") : Loc.GetString("thief-role-greeting-animal"); - briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; + if (isHuman) + briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; + return briefing; } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 56b652ed9a..44ad00ae17 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -10,9 +10,7 @@ using Content.Shared.Database; using Content.Shared.GameTicking.Components; using Content.Shared.Mind; using Content.Shared.NPC.Systems; -using Content.Shared.Objectives.Components; using Content.Shared.PDA; -using Content.Shared.Radio; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; using Content.Shared.Roles.RoleCodeword; @@ -28,22 +26,21 @@ public sealed class TraitorRuleSystem : GameRuleSystem private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b"); [Dependency] private readonly IAdminLogManager _adminLogger = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!; - [Dependency] private readonly UplinkSystem _uplink = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly UplinkSystem _uplink = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(AfterEntitySelected); - SubscribeLocalEvent(OnObjectivesTextPrepend); } @@ -80,7 +77,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) { - //Grab the mind if it wasnt provided + //Grab the mind if it wasn't provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) return false; @@ -92,7 +89,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem { // Calculate the amount of currency on the uplink. var startingBalance = component.StartingBalance; - if (_jobs.MindTryGetJob(mindId, out _, out var prototype)) + if (_jobs.MindTryGetJob(mindId, out var prototype)) startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0); // creadth: we need to create uplink for the antag. @@ -111,14 +108,18 @@ public sealed class TraitorRuleSystem : GameRuleSystem _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification); - component.TraitorMinds.Add(mindId); // Assign briefing - _roleSystem.MindAddRole(mindId, new RoleBriefingComponent + //Since this provides neither an antag/job prototype, nor antag status/roletype, + //and is intrinsically related to the traitor role + //it does not need to be a separate Mind Role Entity + _roleSystem.MindHasRole(mindId, out var traitorRole); + if (traitorRole is not null) { - Briefing = briefing - }, mind, true); + AddComp(traitorRole.Value.Owner); + Comp(traitorRole.Value.Owner).Briefing = briefing; + } // Send codewords to only the traitor client var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index e91931300e..bb76721340 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -13,6 +13,7 @@ using Content.Shared.Mind; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Roles; using Content.Shared.Zombies; using Robust.Shared.Player; using Robust.Shared.Timing; @@ -22,15 +23,16 @@ namespace Content.Server.GameTicking.Rules; public sealed class ZombieRuleSystem : GameRuleSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly RoundEndSystem _roundEnd = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly ZombieSystem _zombie = default!; public override void Initialize() { @@ -41,23 +43,20 @@ public sealed class ZombieRuleSystem : GameRuleSystem SubscribeLocalEvent(OnZombifySelf); } - private void OnGetBriefing(EntityUid uid, InitialInfectedRoleComponent component, ref GetBriefingEvent args) + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(uid, out var mind) || mind.OwnedEntity == null) - return; - if (HasComp(uid)) // don't show both briefings - return; - args.Append(Loc.GetString("zombie-patientzero-role-greeting")); + if (!_roles.MindHasRole(args.Mind.Owner)) + args.Append(Loc.GetString("zombie-patientzero-role-greeting")); } - private void OnGetBriefing(EntityUid uid, ZombieRoleComponent component, ref GetBriefingEvent args) + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) { - if (!TryComp(uid, out var mind) || mind.OwnedEntity == null) - return; args.Append(Loc.GetString("zombie-infection-greeting")); } - protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, + protected override void AppendRoundEndText(EntityUid uid, + ZombieRuleComponent component, + GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { base.AppendRoundEndText(uid, component, gameRule, ref args); diff --git a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs index bd276e6df7..3dfa37a6b1 100644 --- a/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs +++ b/Content.Server/Ghost/Roles/GhostRoleMarkerRoleComponent.cs @@ -1,11 +1,13 @@ -namespace Content.Server.Ghost.Roles; +using Content.Shared.Roles; + +namespace Content.Server.Ghost.Roles; /// /// This is used for round end display of ghost roles. /// It may also be used to ensure some ghost roles count as antagonists in future. /// [RegisterComponent] -public sealed partial class GhostRoleMarkerRoleComponent : Component +public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent { [DataField("name")] public string? Name; } diff --git a/Content.Server/Ghost/Roles/GhostRoleSystem.cs b/Content.Server/Ghost/Roles/GhostRoleSystem.cs index 23ce0f1539..bb95b827a7 100644 --- a/Content.Server/Ghost/Roles/GhostRoleSystem.cs +++ b/Content.Server/Ghost/Roles/GhostRoleSystem.cs @@ -71,18 +71,22 @@ public sealed class GhostRoleSystem : EntitySystem SubscribeLocalEvent(Reset); SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnMindRemoved); SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnTakeoverTakeRole); + SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnRoleStartup); SubscribeLocalEvent(OnRoleShutdown); SubscribeLocalEvent(OnPaused); SubscribeLocalEvent(OnUnpaused); + SubscribeLocalEvent(OnRaffleInit); SubscribeLocalEvent(OnRaffleShutdown); + SubscribeLocalEvent(OnSpawnerTakeRole); - SubscribeLocalEvent(OnTakeoverTakeRole); SubscribeLocalEvent>(OnVerb); SubscribeLocalEvent(OnGhostRoleRadioMessage); _playerManager.PlayerStatusChanged += PlayerStatusChanged; @@ -509,7 +513,11 @@ public sealed class GhostRoleSystem : EntitySystem var newMind = _mindSystem.CreateMind(player.UserId, EntityManager.GetComponent(mob).EntityName); - _roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName }); + + _roleSystem.MindAddRole(newMind, "MindRoleGhostMarker"); + + if(_roleSystem.MindHasRole(newMind, out _, out var markerRole)) + markerRole.Value.Comp.Name = role.RoleName; _mindSystem.SetUserId(newMind, player.UserId); _mindSystem.TransferTo(newMind, mob); @@ -602,10 +610,7 @@ public sealed class GhostRoleSystem : EntitySystem if (ghostRole.JobProto != null) { - if (HasComp(args.Mind)) - _roleSystem.MindRemoveRole(args.Mind); - - _roleSystem.MindAddRole(args.Mind, new JobComponent { Prototype = ghostRole.JobProto }); + _roleSystem.MindAddJobRole(args.Mind, args.Mind, silent:false,ghostRole.JobProto); } ghostRole.Taken = true; diff --git a/Content.Server/Mind/Commands/MindInfoCommand.cs b/Content.Server/Mind/Commands/MindInfoCommand.cs index d4961b82db..c365702a31 100644 --- a/Content.Server/Mind/Commands/MindInfoCommand.cs +++ b/Content.Server/Mind/Commands/MindInfoCommand.cs @@ -43,7 +43,7 @@ namespace Content.Server.Mind.Commands builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity); var roles = _entities.System(); - foreach (var role in roles.MindGetAllRoles(mindId)) + foreach (var role in roles.MindGetAllRoleInfo(mindId)) { builder.AppendFormat("{0} ", role.Name); } diff --git a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs index c1caa819e4..b4de15f2b9 100644 --- a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs +++ b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs @@ -89,7 +89,7 @@ public sealed class KillPersonConditionSystem : EntitySystem foreach (var mind in allHumans) { // RequireAdminNotify used as a cheap way to check for command department - if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify) + if (_job.MindTryGetJob(mind, out var prototype) && prototype.RequireAdminNotify) allHeads.Add(mind); } diff --git a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs index 47c54b937a..ee99658f66 100644 --- a/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs +++ b/Content.Server/Objectives/Systems/NinjaConditionsSystem.cs @@ -1,9 +1,10 @@ using Content.Server.Objectives.Components; +using Content.Server.Roles; using Content.Server.Warps; using Content.Shared.Objectives.Components; using Content.Shared.Ninja.Components; +using Content.Shared.Roles; using Robust.Shared.Random; -using Content.Server.Roles; namespace Content.Server.Objectives.Systems; @@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly NumberObjectiveSystem _number = default!; [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; public override void Initialize() { @@ -46,10 +48,8 @@ public sealed class NinjaConditionsSystem : EntitySystem // spider charge private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args) { - if (args.Cancelled || !HasComp(args.MindId)) - { + if (args.Cancelled || !_roles.MindHasRole(args.MindId)) return; - } // choose spider charge detonation point var warps = new List(); diff --git a/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs b/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs index e63492bb5e..50d747c1a2 100644 --- a/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/NotCommandRequirementSystem.cs @@ -21,7 +21,7 @@ public sealed class NotCommandRequirementSystem : EntitySystem return; // cheap equivalent to checking that job department is command, since all command members require admin notification when leaving - if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify) + if (_job.MindTryGetJob(args.MindId, out var prototype) && prototype.RequireAdminNotify) args.Cancelled = true; } } diff --git a/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs b/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs index 5c2b040463..ac7e579c38 100644 --- a/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/NotJobRequirementSystem.cs @@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems; /// public sealed class NotJobRequirementSystem : EntitySystem { + [Dependency] private readonly SharedJobSystem _jobs = default!; + public override void Initialize() { base.Initialize(); @@ -20,11 +22,10 @@ public sealed class NotJobRequirementSystem : EntitySystem if (args.Cancelled) return; - // if player has no job then don't care - if (!TryComp(args.MindId, out var job)) - return; + _jobs.MindTryGetJob(args.MindId, out var proto); - if (job.Prototype == comp.Job) + // if player has no job then don't care + if (proto is not null && proto.ID == comp.Job) args.Cancelled = true; } } diff --git a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs index 8421987bae..83d4c2ea4c 100644 --- a/Content.Server/Objectives/Systems/RoleRequirementSystem.cs +++ b/Content.Server/Objectives/Systems/RoleRequirementSystem.cs @@ -22,8 +22,6 @@ public sealed class RoleRequirementSystem : EntitySystem if (args.Cancelled) return; - // this whitelist trick only works because roles are components on the mind and not entities - // if that gets reworked then this will need changing if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId)) args.Cancelled = true; } diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index 4139499e9f..0896731a2e 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -30,14 +30,15 @@ namespace Content.Server.Players.PlayTimeTracking; /// public sealed class PlayTimeTrackingSystem : EntitySystem { + [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IAfkManager _afk = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly MindSystem _minds = default!; - [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; - [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; public override void Initialize() { @@ -101,10 +102,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem public IEnumerable GetTimedRoles(EntityUid mindId) { - var ev = new MindGetAllRolesEvent(new List()); - RaiseLocalEvent(mindId, ref ev); - - foreach (var role in ev.Roles) + foreach (var role in _roles.MindGetAllRoleInfo(mindId)) { if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId)) continue; diff --git a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs index 8e42f41cb3..dc16b87300 100644 --- a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs +++ b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs @@ -10,3 +10,5 @@ public sealed partial class CommandStaffComponent : Component { } + +//TODO this should probably be on a mind role, not the mob diff --git a/Content.Server/Roles/DragonRoleComponent.cs b/Content.Server/Roles/DragonRoleComponent.cs index b85fd53eb0..c47455d8f6 100644 --- a/Content.Server/Roles/DragonRoleComponent.cs +++ b/Content.Server/Roles/DragonRoleComponent.cs @@ -4,9 +4,9 @@ using Content.Shared.Roles; namespace Content.Server.Roles; /// -/// Role used to keep track of space dragons for antag purposes. +/// Added to mind role entities to tag that they are a space dragon. /// -[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist] -public sealed partial class DragonRoleComponent : AntagonistRoleComponent +[RegisterComponent, Access(typeof(DragonSystem))] +public sealed partial class DragonRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/InitialInfectedRoleComponent.cs b/Content.Server/Roles/InitialInfectedRoleComponent.cs index 52d3db4164..475cd3ba60 100644 --- a/Content.Server/Roles/InitialInfectedRoleComponent.cs +++ b/Content.Server/Roles/InitialInfectedRoleComponent.cs @@ -2,8 +2,11 @@ using Content.Shared.Roles; namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are an initial infected. +/// +[RegisterComponent] +public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/Jobs/JobSystem.cs b/Content.Server/Roles/Jobs/JobSystem.cs index 9f0dd7ae32..7e69d9d92b 100644 --- a/Content.Server/Roles/Jobs/JobSystem.cs +++ b/Content.Server/Roles/Jobs/JobSystem.cs @@ -30,7 +30,7 @@ public sealed class JobSystem : SharedJobSystem if (!_mind.TryGetSession(mindId, out var session)) return; - if (!MindTryGetJob(mindId, out _, out var prototype)) + if (!MindTryGetJob(mindId, out var prototype)) return; _chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", @@ -47,6 +47,6 @@ public sealed class JobSystem : SharedJobSystem if (MindHasJobWithId(mindId, jobPrototypeId)) return; - _roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId }); + _roles.MindAddJobRole(mindId, null, false, jobPrototypeId); } } diff --git a/Content.Server/Roles/NinjaRoleComponent.cs b/Content.Server/Roles/NinjaRoleComponent.cs index cb60e5bdf0..7bdffe67a3 100644 --- a/Content.Server/Roles/NinjaRoleComponent.cs +++ b/Content.Server/Roles/NinjaRoleComponent.cs @@ -2,7 +2,10 @@ using Content.Shared.Roles; namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class NinjaRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a space ninja. +/// +[RegisterComponent] +public sealed partial class NinjaRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/NukeopsRoleComponent.cs b/Content.Server/Roles/NukeopsRoleComponent.cs index a6ff0b71b0..41561088ea 100644 --- a/Content.Server/Roles/NukeopsRoleComponent.cs +++ b/Content.Server/Roles/NukeopsRoleComponent.cs @@ -3,9 +3,9 @@ using Content.Shared.Roles; namespace Content.Server.Roles; /// -/// Added to mind entities to tag that they are a nuke operative. +/// Added to mind role entities to tag that they are a nuke operative. /// -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent +[RegisterComponent] +public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/RemoveRoleCommand.cs b/Content.Server/Roles/RemoveRoleCommand.cs index feba63a253..fd4bb09317 100644 --- a/Content.Server/Roles/RemoveRoleCommand.cs +++ b/Content.Server/Roles/RemoveRoleCommand.cs @@ -45,7 +45,7 @@ namespace Content.Server.Roles var roles = _entityManager.System(); var jobs = _entityManager.System(); if (jobs.MindHasJobWithId(mind, args[1])) - roles.MindRemoveRole(mind.Value); + roles.MindTryRemoveRole(mind.Value); } } } diff --git a/Content.Server/Roles/RevolutionaryRoleComponent.cs b/Content.Server/Roles/RevolutionaryRoleComponent.cs index bf56b96008..dcdb131b9d 100644 --- a/Content.Server/Roles/RevolutionaryRoleComponent.cs +++ b/Content.Server/Roles/RevolutionaryRoleComponent.cs @@ -3,10 +3,10 @@ using Content.Shared.Roles; namespace Content.Server.Roles; /// -/// Added to mind entities to tag that they are a Revolutionary. +/// Added to mind role entities to tag that they are a Revolutionary. /// -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent +[RegisterComponent] +public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent { /// /// For headrevs, how many people you have converted. diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs index c53fa1cf9e..333f4f9b60 100644 --- a/Content.Server/Roles/RoleSystem.cs +++ b/Content.Server/Roles/RoleSystem.cs @@ -1,32 +1,40 @@ +using Content.Shared.Mind; using Content.Shared.Roles; namespace Content.Server.Roles; public sealed class RoleSystem : SharedRoleSystem { - public override void Initialize() - { - // TODO make roles entities - base.Initialize(); - - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - SubscribeAntagEvents(); - } - public string? MindGetBriefing(EntityUid? mindId) { if (mindId == null) + { + Log.Error($"MingGetBriefing failed for mind {mindId}"); + return null; + } + + TryComp(mindId.Value, out var mindComp); + + if (mindComp is null) + { + Log.Error($"MingGetBriefing failed for mind {mindId}"); return null; + } var ev = new GetBriefingEvent(); - RaiseLocalEvent(mindId.Value, ref ev); + + // This is on the event because while this Entity is also present on every Mind Role Entity's MindRoleComp + // getting to there from a GetBriefing event subscription can be somewhat boilerplate + // and this needs to be looked up for the event anyway so why calculate it again later + ev.Mind = (mindId.Value, mindComp); + + // Briefing is no longer raised on the mind entity itself + // because all the components that briefings subscribe to should be on Mind Role Entities + foreach(var role in mindComp.MindRoles) + { + RaiseLocalEvent(role, ref ev); + } + return ev.Briefing; } } @@ -38,8 +46,16 @@ public sealed class RoleSystem : SharedRoleSystem [ByRefEvent] public sealed class GetBriefingEvent { + /// + /// The text that will be shown on the Character Screen + /// public string? Briefing; + /// + /// The Mind to whose Mind Role Entities the briefing is sent to + /// + public Entity Mind; + public GetBriefingEvent(string? briefing = null) { Briefing = briefing; diff --git a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs index 70056fbec9..55727573b9 100644 --- a/Content.Server/Roles/SubvertedSiliconRoleComponent.cs +++ b/Content.Server/Roles/SubvertedSiliconRoleComponent.cs @@ -2,7 +2,10 @@ namespace Content.Server.Roles; +/// +/// Added to mind role entities to tag that they are a hacked borg. +/// [RegisterComponent] -public sealed partial class SubvertedSiliconRoleComponent : AntagonistRoleComponent +public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/ThiefRoleComponent.cs b/Content.Server/Roles/ThiefRoleComponent.cs index 82e350ef63..c0ddee71a4 100644 --- a/Content.Server/Roles/ThiefRoleComponent.cs +++ b/Content.Server/Roles/ThiefRoleComponent.cs @@ -2,7 +2,10 @@ using Content.Shared.Roles; namespace Content.Server.Roles; +/// +/// Added to mind role entities to tag that they are a thief. +/// [RegisterComponent] -public sealed partial class ThiefRoleComponent : AntagonistRoleComponent +public sealed partial class ThiefRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/TraitorRoleComponent.cs b/Content.Server/Roles/TraitorRoleComponent.cs index 96bfe8dd80..a8a11a8f1b 100644 --- a/Content.Server/Roles/TraitorRoleComponent.cs +++ b/Content.Server/Roles/TraitorRoleComponent.cs @@ -2,7 +2,10 @@ using Content.Shared.Roles; namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class TraitorRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a syndicate traitor. +/// +[RegisterComponent] +public sealed partial class TraitorRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Roles/ZombieRoleComponent.cs b/Content.Server/Roles/ZombieRoleComponent.cs index 2f9948022b..cff25e53e8 100644 --- a/Content.Server/Roles/ZombieRoleComponent.cs +++ b/Content.Server/Roles/ZombieRoleComponent.cs @@ -2,7 +2,10 @@ using Content.Shared.Roles; namespace Content.Server.Roles; -[RegisterComponent, ExclusiveAntagonist] -public sealed partial class ZombieRoleComponent : AntagonistRoleComponent +/// +/// Added to mind role entities to tag that they are a zombie. +/// +[RegisterComponent] +public sealed partial class ZombieRoleComponent : BaseMindRoleComponent { } diff --git a/Content.Server/Silicons/Laws/SiliconLawSystem.cs b/Content.Server/Silicons/Laws/SiliconLawSystem.cs index 6b7df52a6e..07674352ec 100644 --- a/Content.Server/Silicons/Laws/SiliconLawSystem.cs +++ b/Content.Server/Silicons/Laws/SiliconLawSystem.cs @@ -28,12 +28,12 @@ namespace Content.Server.Silicons.Laws; public sealed class SiliconLawSystem : SharedSiliconLawSystem { [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly UserInterfaceSystem _userInterface = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; + [Dependency] private readonly UserInterfaceSystem _userInterface = default!; /// public override void Initialize() @@ -178,10 +178,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) return; - if (_roles.MindHasRole(mindId)) - return; - - _roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole }); + if (!_roles.MindHasRole(mindId)) + _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon"); } public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) diff --git a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs index 69baf591bc..db82dc70a2 100644 --- a/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ContainerSpawnPointSystem.cs @@ -12,10 +12,10 @@ namespace Content.Server.Spawners.EntitySystems; public sealed class ContainerSpawnPointSystem : EntitySystem { + [Dependency] private readonly ContainerSystem _container = default!; [Dependency] private readonly GameTicker _gameTicker = default!; - [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IPrototypeManager _proto = default!; - [Dependency] private readonly ContainerSystem _container = default!; + [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; @@ -32,7 +32,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem // If it's just a spawn pref check if it's for cryo (silly). if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep && - (!_proto.TryIndex(args.Job?.Prototype, out var jobProto) || jobProto.JobEntity == null)) + (!_proto.TryIndex(args.Job, out var jobProto) || jobProto.JobEntity == null)) { return; } @@ -49,7 +49,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem if (spawnPoint.SpawnType == SpawnPointType.Unset) { // make sure we also check the job here for various reasons. - if (spawnPoint.Job == null || spawnPoint.Job == args.Job?.Prototype) + if (spawnPoint.Job == null || spawnPoint.Job == args.Job) possibleContainers.Add((uid, spawnPoint, container, xform)); continue; } @@ -61,7 +61,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job)) { possibleContainers.Add((uid, spawnPoint, container, xform)); } diff --git a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs index be555dd54a..bd905e2982 100644 --- a/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs +++ b/Content.Server/Spawners/EntitySystems/SpawnPointSystem.cs @@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem if (_gameTicker.RunLevel != GameRunLevel.InRound && spawnPoint.SpawnType == SpawnPointType.Job && - (args.Job == null || spawnPoint.Job == args.Job.Prototype)) + (args.Job == null || spawnPoint.Job == args.Job)) { possiblePositions.Add(xform.Coordinates); } diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index e39a094319..3dd4995371 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Server.Access.Systems; using Content.Server.DetailExaminable; using Content.Server.Humanoid; @@ -17,13 +16,10 @@ using Content.Shared.Humanoid.Prototypes; using Content.Shared.PDA; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Random; using Content.Shared.Random.Helpers; using Content.Shared.Roles; -using Content.Shared.Roles.Jobs; using Content.Shared.Station; -using Content.Shared.StatusIcon; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Map; @@ -41,16 +37,18 @@ namespace Content.Server.Station.Systems; [PublicAPI] public sealed class StationSpawningSystem : SharedStationSpawningSystem { - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly SharedAccessSystem _accessSystem = default!; [Dependency] private readonly ActorSystem _actors = default!; - [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; + [Dependency] private readonly ArrivalsSystem _arrivalsSystem = default!; [Dependency] private readonly IdCardSystem _cardSystem = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly ContainerSpawnPointSystem _containerSpawnPointSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidSystem = default!; [Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly PdaSystem _pdaSystem = default!; - [Dependency] private readonly SharedAccessSystem _accessSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; private bool _randomizeCharacters; @@ -73,7 +71,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem /// /// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable. /// - public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, JobComponent? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null) + public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, ProtoId? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null) { if (station != null && !Resolve(station.Value, ref stationSpawning)) throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); @@ -101,12 +99,12 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem /// The spawned entity public EntityUid SpawnPlayerMob( EntityCoordinates coordinates, - JobComponent? job, + ProtoId? job, HumanoidCharacterProfile? profile, EntityUid? station, EntityUid? entity = null) { - _prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype); + _prototypeManager.TryIndex(job ?? string.Empty, out var prototype); RoleLoadout? loadout = null; // Need to get the loadout up-front to handle names if we use an entity spawn override. @@ -200,9 +198,9 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem return entity.Value; } - private void DoJobSpecials(JobComponent? job, EntityUid entity) + private void DoJobSpecials(ProtoId? job, EntityUid entity) { - if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype)) + if (!_prototypeManager.TryIndex(job ?? string.Empty, out JobPrototype? prototype)) return; foreach (var jobSpecial in prototype.Special) @@ -269,7 +267,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs /// /// The job to use, if any. /// - public readonly JobComponent? Job; + public readonly ProtoId? Job; /// /// The profile to use, if any. /// @@ -279,7 +277,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs /// public readonly EntityUid? Station; - public PlayerSpawningEvent(JobComponent? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station) + public PlayerSpawningEvent(ProtoId? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station) { Job = job; HumanoidCharacterProfile = humanoidCharacterProfile; diff --git a/Content.Server/Store/Conditions/BuyerAntagCondition.cs b/Content.Server/Store/Conditions/BuyerAntagCondition.cs index 1edc4a3365..4b1b6013eb 100644 --- a/Content.Server/Store/Conditions/BuyerAntagCondition.cs +++ b/Content.Server/Store/Conditions/BuyerAntagCondition.cs @@ -33,16 +33,16 @@ public sealed partial class BuyerAntagCondition : ListingCondition return true; var roleSystem = ent.System(); - var roles = roleSystem.MindGetAllRoles(mindId); + var roles = roleSystem.MindGetAllRoleInfo(mindId); if (Blacklist != null) { foreach (var role in roles) { - if (role.Component is not AntagonistRoleComponent blacklistantag) + if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype)) continue; - if (blacklistantag.PrototypeId != null && Blacklist.Contains(blacklistantag.PrototypeId)) + if (Blacklist.Contains(role.Prototype)) return false; } } @@ -52,10 +52,11 @@ public sealed partial class BuyerAntagCondition : ListingCondition var found = false; foreach (var role in roles) { - if (role.Component is not AntagonistRoleComponent antag) + + if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype)) continue; - if (antag.PrototypeId != null && Whitelist.Contains(antag.PrototypeId)) + if (Whitelist.Contains(role.Prototype)) found = true; } if (!found) diff --git a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs index ea8de4a9cc..43c06efad3 100644 --- a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs +++ b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs @@ -37,13 +37,13 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition return true; var jobs = ent.System(); - jobs.MindTryGetJob(mindId, out var job, out _); + jobs.MindTryGetJob(mindId, out var job); - if (Blacklist != null && job?.Prototype != null) + if (Blacklist != null && job != null) { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) + if (department.Roles.Contains(job.ID) && Blacklist.Contains(department.ID)) return false; } } @@ -52,11 +52,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { var found = false; - if (job?.Prototype != null) + if (job != null) { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) + if (department.Roles.Contains(job.ID) && Whitelist.Contains(department.ID)) { found = true; break; diff --git a/Content.Server/Store/Conditions/BuyerJobCondition.cs b/Content.Server/Store/Conditions/BuyerJobCondition.cs index 6a53af188c..1ff4a97c33 100644 --- a/Content.Server/Store/Conditions/BuyerJobCondition.cs +++ b/Content.Server/Store/Conditions/BuyerJobCondition.cs @@ -34,17 +34,17 @@ public sealed partial class BuyerJobCondition : ListingCondition return true; var jobs = ent.System(); - jobs.MindTryGetJob(mindId, out var job, out _); + jobs.MindTryGetJob(mindId, out var job); if (Blacklist != null) { - if (job?.Prototype != null && Blacklist.Contains(job.Prototype)) + if (job is not null && Blacklist.Contains(job.ID)) return false; } if (Whitelist != null) { - if (job?.Prototype == null || !Whitelist.Contains(job.Prototype)) + if (job == null || !Whitelist.Contains(job.ID)) return false; } diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index 84ab7e2e4a..7acfe9dbbd 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -11,7 +11,6 @@ using Content.Server.Mind.Commands; using Content.Server.NPC; using Content.Server.NPC.HTN; using Content.Server.NPC.Systems; -using Content.Server.Roles; using Content.Server.Speech.Components; using Content.Server.Temperature.Components; using Content.Shared.CombatMode; @@ -47,18 +46,18 @@ namespace Content.Server.Zombies; /// public sealed partial class ZombieSystem { - [Dependency] private readonly SharedHandsSystem _hands = default!; - [Dependency] private readonly ServerInventorySystem _inventory = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly SharedCombatModeSystem _combat = default!; [Dependency] private readonly NpcFactionSystem _faction = default!; - [Dependency] private readonly NPCSystem _npc = default!; + [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; [Dependency] private readonly IdentitySystem _identity = default!; - [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; - [Dependency] private readonly SharedCombatModeSystem _combat = default!; - [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly NPCSystem _npc = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; /// /// Handles an entity turning into a zombie when they die or go into crit @@ -234,7 +233,7 @@ public sealed partial class ZombieSystem if (hasMind && _mind.TryGetSession(mindId, out var session)) { //Zombie role for player manifest - _roles.MindAddRole(mindId, new ZombieRoleComponent { PrototypeId = zombiecomp.ZombieRoleId }); + _roles.MindAddRole(mindId, "MindRoleZombie", mind: null, silent: true); //Greeting message for new bebe zombers _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); diff --git a/Content.Shared/Mind/Components/MindContainerComponent.cs b/Content.Shared/Mind/Components/MindContainerComponent.cs index 380d30ca14..760f5026fa 100644 --- a/Content.Shared/Mind/Components/MindContainerComponent.cs +++ b/Content.Shared/Mind/Components/MindContainerComponent.cs @@ -14,7 +14,6 @@ public sealed partial class MindContainerComponent : Component /// The mind controlling this mob. Can be null. /// [DataField, AutoNetworkedField] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends public EntityUid? Mind { get; set; } /// @@ -35,7 +34,6 @@ public sealed partial class MindContainerComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("ghostOnShutdown")] - [Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends public bool GhostOnShutdown { get; set; } = true; } diff --git a/Content.Shared/Mind/MindComponent.cs b/Content.Shared/Mind/MindComponent.cs index d603102682..a0812be8f7 100644 --- a/Content.Shared/Mind/MindComponent.cs +++ b/Content.Shared/Mind/MindComponent.cs @@ -1,4 +1,3 @@ -using Content.Shared.Actions; using Content.Shared.GameTicking; using Content.Shared.Mind.Components; using Robust.Shared.GameStates; @@ -87,17 +86,21 @@ public sealed partial class MindComponent : Component /// /// Prevents user from ghosting out /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventGhosting")] + [DataField] public bool PreventGhosting { get; set; } /// /// Prevents user from suiciding /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("preventSuicide")] + [DataField] public bool PreventSuicide { get; set; } + /// + /// Mind Role Entities belonging to this Mind + /// + [DataField, AutoNetworkedField] + public List MindRoles = new List(); + /// /// The session of the player owning this mind. /// Can be null, in which case the player is currently not logged in. diff --git a/Content.Shared/Roles/AntagonistRoleComponent.cs b/Content.Shared/Roles/AntagonistRoleComponent.cs deleted file mode 100644 index 748b18255a..0000000000 --- a/Content.Shared/Roles/AntagonistRoleComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -using JetBrains.Annotations; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Shared.Roles; - -public abstract partial class AntagonistRoleComponent : Component -{ - [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PrototypeId; -} - -/// -/// Mark the antagonist role component as being exclusive -/// IE by default other antagonists should refuse to select the same entity for a different antag role -/// -[AttributeUsage(AttributeTargets.Class, Inherited = false)] -[BaseTypeRequired(typeof(AntagonistRoleComponent))] -public sealed partial class ExclusiveAntagonistAttribute : Attribute -{ -} diff --git a/Content.Shared/Roles/Jobs/JobComponent.cs b/Content.Shared/Roles/Jobs/JobComponent.cs deleted file mode 100644 index 7191e8b397..0000000000 --- a/Content.Shared/Roles/Jobs/JobComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Shared.Roles.Jobs; - -/// -/// Added to mind entities to hold the data for the player's current job. -/// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] -public sealed partial class JobComponent : Component -{ - [DataField(required: true), AutoNetworkedField] - public ProtoId? Prototype; -} diff --git a/Content.Shared/Roles/Jobs/JobRoleComponent.cs b/Content.Shared/Roles/Jobs/JobRoleComponent.cs new file mode 100644 index 0000000000..dbaf12beec --- /dev/null +++ b/Content.Shared/Roles/Jobs/JobRoleComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Roles.Jobs; + +/// +/// Added to mind role entities to mark them as a job role entity. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class JobRoleComponent : BaseMindRoleComponent +{ + +} diff --git a/Content.Shared/Roles/Jobs/SharedJobSystem.cs b/Content.Shared/Roles/Jobs/SharedJobSystem.cs index 939a57baad..94447a5af4 100644 --- a/Content.Shared/Roles/Jobs/SharedJobSystem.cs +++ b/Content.Shared/Roles/Jobs/SharedJobSystem.cs @@ -13,8 +13,10 @@ namespace Content.Shared.Roles.Jobs; /// public abstract class SharedJobSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedPlayerSystem _playerSystem = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; + private readonly Dictionary _inverseTrackerLookup = new(); public override void Initialize() @@ -100,32 +102,44 @@ public abstract class SharedJobSystem : EntitySystem public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { - return CompOrNull(mindId)?.Prototype == prototypeId; + + MindRoleComponent? comp = null; + if (mindId is null) + return false; + + _roles.MindHasRole(mindId.Value, out var role); + + if (role is null) + return false; + + comp = role.Value.Comp; + + return (comp.JobPrototype == prototypeId); } public bool MindTryGetJob( [NotNullWhen(true)] EntityUid? mindId, - [NotNullWhen(true)] out JobComponent? comp, [NotNullWhen(true)] out JobPrototype? prototype) { - comp = null; prototype = null; + MindTryGetJobId(mindId, out var protoId); - return TryComp(mindId, out comp) && - comp.Prototype != null && - _prototypes.TryIndex(comp.Prototype, out prototype); + return (_prototypes.TryIndex(protoId, out prototype) || prototype is not null); } - public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId? job) + public bool MindTryGetJobId( + [NotNullWhen(true)] EntityUid? mindId, + out ProtoId? job) { - if (!TryComp(mindId, out JobComponent? comp)) - { - job = null; + job = null; + + if (mindId is null) return false; - } - job = comp.Prototype; - return true; + if (_roles.MindHasRole(mindId.Value, out var role)) + job = role.Value.Comp.JobPrototype; + + return (job is not null); } /// @@ -134,7 +148,7 @@ public abstract class SharedJobSystem : EntitySystem /// public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name) { - if (MindTryGetJob(mindId, out _, out var prototype)) + if (MindTryGetJob(mindId, out var prototype)) { name = prototype.LocalizedName; return true; @@ -161,7 +175,7 @@ public abstract class SharedJobSystem : EntitySystem if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) return true; - if (!MindTryGetJob(mindId, out _, out var prototype)) + if (!MindTryGetJob(mindId, out var prototype)) return true; return prototype.CanBeAntag; diff --git a/Content.Shared/Roles/MindGetAllRolesEvent.cs b/Content.Shared/Roles/MindGetAllRoleInfoEvent.cs similarity index 79% rename from Content.Shared/Roles/MindGetAllRolesEvent.cs rename to Content.Shared/Roles/MindGetAllRoleInfoEvent.cs index 6987873908..a2f2820b5c 100644 --- a/Content.Shared/Roles/MindGetAllRolesEvent.cs +++ b/Content.Shared/Roles/MindGetAllRoleInfoEvent.cs @@ -7,7 +7,7 @@ namespace Content.Shared.Roles; /// /// The list of roles on the player. [ByRefEvent] -public readonly record struct MindGetAllRolesEvent(List Roles); +public readonly record struct MindGetAllRoleInfoEvent(List Roles); /// /// Returned by to give some information about a player's role. @@ -17,4 +17,4 @@ public readonly record struct MindGetAllRolesEvent(List Roles); /// Whether or not this role makes this player an antagonist. /// The id associated with the role. /// The prototype ID of the role -public readonly record struct RoleInfo(Component Component, string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype); +public readonly record struct RoleInfo(string Name, bool Antagonist, string? PlayTimeTrackerId, string Prototype); diff --git a/Content.Shared/Roles/MindRoleComponent.cs b/Content.Shared/Roles/MindRoleComponent.cs new file mode 100644 index 0000000000..38b83a8b3f --- /dev/null +++ b/Content.Shared/Roles/MindRoleComponent.cs @@ -0,0 +1,48 @@ +using Content.Shared.Mind; +using JetBrains.Annotations; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Roles; + +/// +/// This holds data for, and indicates, a Mind Role entity +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class MindRoleComponent : BaseMindRoleComponent +{ + /// + /// Marks this Mind Role as Antagonist + /// A single antag Mind Role is enough to make the owner mind count as Antagonist. + /// + [DataField] + public bool Antag { get; set; } = false; + + /// + /// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True. + /// + [DataField] + public bool ExclusiveAntag { get; set; } = false; + + /// + /// The Mind that this role belongs to + /// + public Entity Mind { get; set; } + + /// + /// The Antagonist prototype of this role + /// + [DataField] + public ProtoId? AntagPrototype { get; set; } + + /// + /// The Job prototype of this role + /// + [DataField] + public ProtoId? JobPrototype { get; set; } +} + +public abstract partial class BaseMindRoleComponent : Component +{ + +} diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 81a360ebb7..cd3fe141b2 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,34 +1,31 @@ +using System.Diagnostics.CodeAnalysis; using Content.Shared.Administration.Logs; using Content.Shared.CCVar; using Content.Shared.Database; -using Content.Shared.Ghost.Roles; +using Content.Shared.GameTicking; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; +using Robust.Shared.Map; using Robust.Shared.Prototypes; -using Robust.Shared.Utility; namespace Content.Shared.Roles; public abstract class SharedRoleSystem : EntitySystem { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; - [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly SharedMindSystem _minds = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; - - // TODO please lord make role entities - private readonly HashSet _antagTypes = new(); + [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly SharedGameTicker _gameTicker = default!; + [Dependency] private readonly IPrototypeManager _prototypes = default!; private JobRequirementOverridePrototype? _requirementOverride; public override void Initialize() { - // TODO make roles entities - SubscribeLocalEvent(OnJobGetAllRoles); Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); } @@ -44,62 +41,118 @@ public abstract class SharedRoleSystem : EntitySystem Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } - private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) + /// + /// Adds multiple mind roles to a mind + /// + /// The mind entity to add the role to + /// The list of mind roles to add + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + public void MindAddRoles(EntityUid mindId, + List>? roles, + MindComponent? mind = null, + bool silent = false) { - var name = "game-ticker-unknown-role"; - var prototype = ""; - string? playTimeTracker = null; - if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job)) + if (roles is null || roles.Count == 0) + return; + + foreach (var proto in roles) { - name = job.Name; - prototype = job.ID; - playTimeTracker = job.PlayTimeTracker; + MindAddRole(mindId, proto, mind, silent); } + } - name = Loc.GetString(name); - - args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype)); + /// + /// Adds a mind role to a mind + /// + /// The mind entity to add the role to + /// The mind role to add + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + public void MindAddRole(EntityUid mindId, + ProtoId protoId, + MindComponent? mind = null, + bool silent = false) + { + if (protoId == "MindRoleJob") + MindAddJobRole(mindId, mind, silent, ""); + else + MindAddRoleDo(mindId, protoId, mind, silent); } - protected void SubscribeAntagEvents() where T : AntagonistRoleComponent + /// + /// Adds a Job mind role with the specified job prototype + /// + /// /// The mind entity to add the job role to + /// If the mind component is provided, it will be checked if it belongs to the mind entity + /// If true, no briefing will be generated upon receiving the mind role + /// The Job prototype for the new role + public void MindAddJobRole(EntityUid mindId, + MindComponent? mind = null, + bool silent = false, + string? jobPrototype = null) { - SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) => + // Can't have someone get paid for two jobs now, can we + if (MindHasRole(mindId, out var jobRole) + && jobRole.Value.Comp.JobPrototype != jobPrototype) { - var name = "game-ticker-unknown-role"; - var prototype = ""; - if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag)) + Resolve(mindId, ref mind); + if (mind is not null) { - name = antag.Name; - prototype = antag.ID; + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'"); } - name = Loc.GetString(name); - - args.Roles.Add(new RoleInfo(component, name, true, null, prototype)); - }); - SubscribeLocalEvent((EntityUid _, T _, ref MindIsAntagonistEvent args) => { args.IsAntagonist = true; args.IsExclusiveAntagonist |= typeof(T).TryGetCustomAttribute(out _); }); - _antagTypes.Add(typeof(T)); + jobRole.Value.Comp.JobPrototype = jobPrototype; + } + else + MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype); } - public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false) + /// + /// Creates a Mind Role + /// + private void MindAddRoleDo(EntityUid mindId, + ProtoId protoId, + MindComponent? mind = null, + bool silent = false, + string? jobPrototype = null) { if (!Resolve(mindId, ref mind)) + { + Log.Error($"Failed to add role {protoId} to mind {mindId} : Mind does not match provided mind component"); return; + } - EntityManager.AddComponents(mindId, components); var antagonist = false; - foreach (var compReg in components.Values) + + if (!_prototypes.TryIndex(protoId, out var protoEnt)) { - var compType = compReg.Component.GetType(); + Log.Error($"Failed to add role {protoId} to mind {mindId} : Role prototype does not exist"); + return; + } - var comp = EntityManager.ComponentFactory.GetComponent(compType); - if (IsAntagonistRole(comp.GetType())) - { - antagonist = true; - break; - } + //TODO don't let a prototype being added a second time + //If that was somehow to occur, a second mindrole for that comp would be created + //Meaning any mind role checks could return wrong results, since they just return the first match they find + + var mindRoleId = Spawn(protoId, MapCoordinates.Nullspace); + EnsureComp(mindRoleId); + var mindRoleComp = Comp(mindRoleId); + + mindRoleComp.Mind = (mindId,mind); + if (jobPrototype is not null) + { + mindRoleComp.JobPrototype = jobPrototype; + EnsureComp(mindRoleId); } + if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag) + antagonist = true; + + mind.MindRoles.Add(mindRoleId); + var mindEv = new MindRoleAddedEvent(silent); RaiseLocalEvent(mindId, ref mindEv); @@ -109,157 +162,330 @@ public abstract class SharedRoleSystem : EntitySystem RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}"); + var name = Loc.GetString(protoEnt.Name); + if (mind.OwnedEntity is not null) + { + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"{name} added to mind of {ToPrettyString(mind.OwnedEntity)}"); + } + else + { + //TODO: This is not tied to the player on the Admin Log filters. + //Probably only happens when Job Role is added on initial spawn, before the mind entity is put in a mob + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"{name} added to {ToPrettyString(mindId)}"); + } } - public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false) + /// + /// Removes all instances of a specific role from this mind. + /// + /// 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 { - if (!Resolve(mindId, ref mind)) - return; + if (!TryComp(mindId, out var mind) ) + throw new ArgumentException($"{mindId} does not exist or does not have mind component"); - if (HasComp(mindId, component.GetType())) + var found = false; + var antagonist = false; + var delete = new List(); + foreach (var role in mind.MindRoles) { - throw new ArgumentException($"We already have this role: {component}"); + if (!HasComp(role)) + continue; + + var roleComp = Comp(role); + antagonist = roleComp.Antag; + _entityManager.DeleteEntity(role); + + delete.Add(role); + found = true; + } - EntityManager.AddComponent(mindId, component); - var antagonist = IsAntagonistRole(component.GetType()); + foreach (var role in delete) + { + mind.MindRoles.Remove(role); + } - var mindEv = new MindRoleAddedEvent(silent); - RaiseLocalEvent(mindId, ref mindEv); + if (!found) + { + throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}"); + } + + var message = new RoleRemovedEvent(mindId, mind, antagonist); - var message = new RoleAddedEvent(mindId, mind, antagonist, silent); if (mind.OwnedEntity != null) { RaiseLocalEvent(mind.OwnedEntity.Value, message, true); } - - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + _adminLogger.Add(LogType.Mind, + LogImpact.Low, + $"'Role {typeof(T).Name}' removed from mind of {ToPrettyString(mind.OwnedEntity)}"); + return true; } /// - /// Gives this mind a new role. + /// Finds and removes all mind roles of a specific type /// - /// The mind to add the role to. - /// The role instance to add. - /// The role type to add. - /// Whether or not the role should be added silently - /// The instance of the role. - /// - /// Thrown if we already have a role with this type. - /// - public void MindAddRole(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : IComponent, new() + /// The mind entity + /// The type of the role to remove. + /// True if the role existed and was removed + public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent { - if (!Resolve(mindId, ref mind)) - return; - - if (HasComp(mindId)) + if (!MindHasRole(mindId)) { - throw new ArgumentException($"We already have this role: {typeof(T)}"); + Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role "); + return false; } - AddComp(mindId, component); - var antagonist = IsAntagonistRole(); + if (typeof(T) == typeof(MindRoleComponent)) + return false; - var mindEv = new MindRoleAddedEvent(silent); - RaiseLocalEvent(mindId, ref mindEv); + return MindRemoveRole(mindId); + } - var message = new RoleAddedEvent(mindId, mind, antagonist, silent); - if (mind.OwnedEntity != null) + /// + /// Finds the first mind role of a specific T type on a mind entity. + /// Outputs entity components for the mind role's MindRoleComponent and for T + /// + /// The mind entity + /// The type of the role to find. + /// 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 + { + role = null; + roleT = null; + + if (!TryComp(mindId, out var mind)) + return false; + + var found = false; + + foreach (var roleEnt in mind.MindRoles) { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + if (!HasComp(roleEnt)) + continue; + + role = (roleEnt,Comp(roleEnt)); + roleT = (roleEnt,Comp(roleEnt)); + found = true; + break; } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {typeof(T).Name}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + return found; } /// - /// Removes a role from this mind. + /// 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 to remove the role from. - /// The type of the role to remove. - /// - /// Thrown if we do not have this role. - /// - public void MindRemoveRole(EntityUid mindId) where T : IComponent + /// The mind entity + /// The Type to look for + /// The output role + /// True if the role is found + public bool MindHasRole(EntityUid mindId, + Type type, + [NotNullWhen(true)] out Entity? role) { - if (!RemComp(mindId)) + role = null; + // All MindRoles have this component, it would just return the first one. + // Order might not be what is expected. + // Better to report null + if (type == Type.GetType("MindRoleComponent")) { - throw new ArgumentException($"We do not have this role: {typeof(T)}"); + Log.Error($"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role."); + return false; } - var mind = Comp(mindId); - var antagonist = IsAntagonistRole(); - var message = new RoleRemovedEvent(mindId, mind, antagonist); + if (!TryComp(mindId, out var mind)) + return false; - if (mind.OwnedEntity != null) + var found = false; + + foreach (var roleEnt in mind.MindRoles) { - RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + if (!HasComp(roleEnt, type)) + continue; + + role = (roleEnt,Comp(roleEnt)); + found = true; + break; } - _adminLogger.Add(LogType.Mind, LogImpact.Low, - $"'Role {typeof(T).Name}' removed from mind of {_minds.MindOwnerLoggingString(mind)}"); + + return found; } - public bool MindTryRemoveRole(EntityUid mindId) where T : IComponent + /// + /// 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 { - if (!MindHasRole(mindId)) - return false; - - MindRemoveRole(mindId); - return true; + return MindHasRole(mindId, out role, out _); } + /// + /// Finds the first mind role of a specific type on a mind entity. + /// + /// The mind entity + /// The type of the role to find. + /// True if the role is found public bool MindHasRole(EntityUid mindId) where T : IComponent { - DebugTools.Assert(HasComp(mindId)); - return HasComp(mindId); + return MindHasRole(mindId, out _, out _); } - public List MindGetAllRoles(EntityUid mindId) + //TODO: Delete this later + /// + /// Returns the first mind role of a specific type + /// + /// The mind entity + /// Entity Component of the mind role + [Obsolete("Use MindHasRole's output value")] + public Entity? MindGetRole(EntityUid mindId) where T : IComponent { - DebugTools.Assert(HasComp(mindId)); - var ev = new MindGetAllRolesEvent(new List()); - RaiseLocalEvent(mindId, ref ev); - return ev.Roles; + Entity? result = null; + + var mind = Comp(mindId); + + foreach (var uid in mind.MindRoles) + { + if (HasComp(uid) && TryComp(uid, out var comp)) + result = (uid,comp); + } + return result; } + /// + /// Reads all Roles of a mind Entity and returns their data as RoleInfo + /// + /// The mind entity + /// RoleInfo list + public List MindGetAllRoleInfo(EntityUid mindId) + { + var roleInfo = new List(); + + if (!TryComp(mindId, out var mind)) + return roleInfo; + + foreach (var role in mind.MindRoles) + { + var valid = false; + var name = "game-ticker-unknown-role"; + var prototype = ""; + string? playTimeTracker = null; + + var comp = Comp(role); + if (comp.AntagPrototype is not null) + { + prototype = comp.AntagPrototype; + } + + if (comp.JobPrototype is not null && comp.AntagPrototype is null) + { + prototype = comp.JobPrototype; + if (_prototypes.TryIndex(comp.JobPrototype, out var job)) + { + playTimeTracker = job.PlayTimeTracker; + name = job.Name; + valid = true; + } + else + { + Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Job prototype: '{comp.JobPrototype}'"); + } + } + else if (comp.AntagPrototype is not null && comp.JobPrototype is null) + { + prototype = comp.AntagPrototype; + if (_prototypes.TryIndex(comp.AntagPrototype, out var antag)) + { + name = antag.Name; + valid = true; + } + else + { + Log.Error($" Mind Role Prototype '{role.Id}' contains invalid Antagonist prototype: '{comp.AntagPrototype}'"); + } + } + else if (comp.JobPrototype is not null && comp.AntagPrototype is not null) + { + Log.Error($" Mind Role Prototype '{role.Id}' contains both Job and Antagonist prototypes"); + } + + if (valid) + roleInfo.Add(new RoleInfo(name, comp.Antag || comp.ExclusiveAntag , playTimeTracker, prototype)); + } + return roleInfo; + } + + /// + /// Does this mind possess an antagonist role + /// + /// The mind entity + /// True if the mind possesses any antag roles public bool MindIsAntagonist(EntityUid? mindId) { - if (mindId == null) + if (mindId is null) + { + Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; + } - DebugTools.Assert(HasComp(mindId)); - var ev = new MindIsAntagonistEvent(); - RaiseLocalEvent(mindId.Value, ref ev); - return ev.IsAntagonist; + return CheckAntagonistStatus(mindId.Value).Item1; } /// /// Does this mind possess an exclusive antagonist role /// /// The mind entity - /// True if the mind possesses an exclusive antag role + /// True if the mind possesses any exclusive antag roles public bool MindIsExclusiveAntagonist(EntityUid? mindId) { - if (mindId == null) + if (mindId is null) + { + Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found"); return false; + } - var ev = new MindIsAntagonistEvent(); - RaiseLocalEvent(mindId.Value, ref ev); - return ev.IsExclusiveAntagonist; + return CheckAntagonistStatus(mindId.Value).Item2; } - public bool IsAntagonistRole() - { - return _antagTypes.Contains(typeof(T)); - } + private (bool, bool) CheckAntagonistStatus(EntityUid mindId) + { + if (!TryComp(mindId, out var mind)) + { + Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found"); + return (false, false); + } - public bool IsAntagonistRole(Type component) - { - return _antagTypes.Contains(component); + var antagonist = false; + var exclusiveAntag = false; + foreach (var role in mind.MindRoles) + { + var roleComp = Comp(role); + if (roleComp.Antag || exclusiveAntag) + antagonist = true; + if (roleComp.ExclusiveAntag) + exclusiveAntag = true; + } + + return (antagonist, exclusiveAntag); } /// diff --git a/Content.Shared/Whitelist/EntityWhitelist.cs b/Content.Shared/Whitelist/EntityWhitelist.cs index 3e4e2fecb2..cbe4633360 100644 --- a/Content.Shared/Whitelist/EntityWhitelist.cs +++ b/Content.Shared/Whitelist/EntityWhitelist.cs @@ -32,6 +32,12 @@ public sealed partial class EntityWhitelist [DataField] public string[]? Components; // TODO yaml validation + /// + /// Mind Role Prototype names that are allowed in the whitelist. + /// + [DataField] public string[]? MindRoles; + // TODO yaml validation + /// /// Item sizes that are allowed in the whitelist. /// diff --git a/Content.Shared/Whitelist/EntityWhitelistSystem.cs b/Content.Shared/Whitelist/EntityWhitelistSystem.cs index 57fdb523dd..7c78b410a1 100644 --- a/Content.Shared/Whitelist/EntityWhitelistSystem.cs +++ b/Content.Shared/Whitelist/EntityWhitelistSystem.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Content.Shared.Item; +using Content.Shared.Roles; using Content.Shared.Tag; namespace Content.Shared.Whitelist; @@ -7,6 +8,7 @@ namespace Content.Shared.Whitelist; public sealed class EntityWhitelistSystem : EntitySystem { [Dependency] private readonly IComponentFactory _factory = default!; + [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly TagSystem _tag = default!; private EntityQuery _itemQuery; @@ -46,9 +48,30 @@ public sealed class EntityWhitelistSystem : EntitySystem public bool IsValid(EntityWhitelist list, EntityUid uid) { if (list.Components != null) - EnsureRegistrations(list); + { + var regs = StringsToRegs(list.Components); + + list.Registrations ??= new List(); + list.Registrations.AddRange(regs); + } + + if (list.MindRoles != null) + { + var regs = StringsToRegs(list.MindRoles); + + foreach (var role in regs) + { + if ( _roles.MindHasRole(uid, role.Type, out _)) + { + if (!list.RequireAll) + return true; + } + else if (list.RequireAll) + return false; + } + } - if (list.Registrations != null) + if (list.Registrations != null && list.Registrations.Count > 0) { foreach (var reg in list.Registrations) { @@ -153,7 +176,7 @@ public sealed class EntityWhitelistSystem : EntitySystem return IsWhitelistPassOrNull(blacklist, uid); } - /// + /// /// Helper function to determine if Blacklist is either null or the entity is not on the list /// Duplicate of equivalent Whitelist function /// @@ -162,24 +185,27 @@ public sealed class EntityWhitelistSystem : EntitySystem return IsWhitelistFailOrNull(blacklist, uid); } - private void EnsureRegistrations(EntityWhitelist list) + private List StringsToRegs(string[]? input) { - if (list.Components == null) - return; + var list = new List(); - list.Registrations = new List(); - foreach (var name in list.Components) + if (input == null || input.Length == 0) + return list; + + foreach (var name in input) { var availability = _factory.GetComponentAvailability(name); if (_factory.TryGetRegistration(name, out var registration) && availability == ComponentAvailability.Available) { - list.Registrations.Add(registration); + list.Add(registration); } else if (availability == ComponentAvailability.Unknown) { - Log.Warning($"Unknown component name {name} passed to EntityWhitelist!"); + Log.Error($"StringsToRegs failed: Unknown component name {name} passed to EntityWhitelist!"); } } + + return list; } } diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl index 1a4fcafbf8..06f8aeb565 100644 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-nukeops.ftl @@ -4,6 +4,7 @@ nukeops-description = Nuclear operatives have targeted the station. Try to keep nukeops-welcome = You are a nuclear operative. Your goal is to blow up {$station}, and ensure that it is nothing but a pile of rubble. Your bosses, the Syndicate, have provided you with the tools you'll need for the task. Operation {$name} is a go ! Death to Nanotrasen! +nukeops-briefing = Your objectives are simple. Deliver the payload and make sure it detonates. Begin mission. nukeops-opsmajor = [color=crimson]Syndicate major victory![/color] nukeops-opsminor = [color=crimson]Syndicate minor victory![/color] diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index dc44915f53..e5e1192fc6 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -176,9 +176,8 @@ min: 1 max: 1 pickPlayer: false - mindComponents: - - type: DragonRole - prototype: Dragon + mindRoles: + - MindRoleDragon - type: entity parent: BaseGameRule @@ -226,9 +225,8 @@ nameSegments: - names_ninja_title - names_ninja - mindComponents: - - type: NinjaRole - prototype: SpaceNinja + mindRoles: + - MindRoleNinja - type: entity parent: BaseGameRule @@ -439,9 +437,8 @@ - type: ZombifyOnDeath - type: IncurableZombie - type: InitialInfected - mindComponents: - - type: InitialInfectedRole - prototype: InitialInfected + mindRoles: + - MindRoleInitialInfected - type: entity parent: BaseNukeopsRule @@ -466,7 +463,6 @@ startingGear: SyndicateLoneOperativeGearFull roleLoadout: - RoleSurvivalNukie - components: - type: NukeOperative - type: RandomMetadata @@ -476,9 +472,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: Nukeops + mindRoles: + - MindRoleNukeops - type: entity parent: BaseTraitorRule @@ -505,9 +500,8 @@ blacklist: components: - AntagImmune - mindComponents: - - type: TraitorRole - prototype: TraitorSleeper + mindRoles: + - MindRoleTraitorSleeper - type: entity id: MassHallucinations diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 7446995f26..6cc53a3d10 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -26,8 +26,7 @@ startingGear: ThiefGear components: - type: Pacified - mindComponents: - - type: ThiefRole - prototype: Thief + mindRoles: + - MindRoleThief briefing: sound: "/Audio/Misc/thief_greeting.ogg" diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index caf2ca7945..46d4366f68 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -114,9 +114,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: NukeopsCommander + mindRoles: + - MindRoleNukeopsCommander - prefRoles: [ NukeopsMedic ] fallbackRoles: [ Nukeops, NukeopsCommander ] spawnerPrototype: SpawnPointNukeopsMedic @@ -132,9 +131,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: NukeopsMedic + mindRoles: + - MindRoleNukeopsMedic - prefRoles: [ Nukeops ] fallbackRoles: [ NukeopsCommander, NukeopsMedic ] spawnerPrototype: SpawnPointNukeopsOperative @@ -152,9 +150,8 @@ - type: NpcFactionMember factions: - Syndicate - mindComponents: - - type: NukeopsRole - prototype: Nukeops + mindRoles: + - MindRoleNukeops - type: entity abstract: true @@ -189,9 +186,8 @@ components: - AntagImmune lateJoinAdditional: true - mindComponents: - - type: TraitorRole - prototype: Traitor + mindRoles: + - MindRoleTraitor - type: entity id: Revolutionary @@ -213,9 +209,8 @@ components: - type: Revolutionary - type: HeadRevolutionary - mindComponents: - - type: RevolutionaryRole - prototype: HeadRev + mindRoles: + - MindRoleHeadRevolutionary - type: entity id: Sandbox @@ -257,9 +252,8 @@ - type: ZombifyOnDeath - type: IncurableZombie - type: InitialInfected - mindComponents: - - type: InitialInfectedRole - prototype: InitialInfected + mindRoles: + - MindRoleInitialInfected # event schedulers diff --git a/Resources/Prototypes/Objectives/dragon.yml b/Resources/Prototypes/Objectives/dragon.yml index bbdac8faa1..9d81c62ab3 100644 --- a/Resources/Prototypes/Objectives/dragon.yml +++ b/Resources/Prototypes/Objectives/dragon.yml @@ -9,7 +9,7 @@ issuer: objective-issuer-dragon - type: RoleRequirement roles: - components: + mindRoles: - DragonRole - type: entity diff --git a/Resources/Prototypes/Objectives/ninja.yml b/Resources/Prototypes/Objectives/ninja.yml index 4d0cf6c17c..28b624519c 100644 --- a/Resources/Prototypes/Objectives/ninja.yml +++ b/Resources/Prototypes/Objectives/ninja.yml @@ -9,7 +9,7 @@ issuer: objective-issuer-spiderclan - type: RoleRequirement roles: - components: + mindRoles: - NinjaRole - type: entity diff --git a/Resources/Prototypes/Objectives/thief.yml b/Resources/Prototypes/Objectives/thief.yml index 2dbb2cc339..c36b5f3192 100644 --- a/Resources/Prototypes/Objectives/thief.yml +++ b/Resources/Prototypes/Objectives/thief.yml @@ -7,7 +7,7 @@ issuer: objective-issuer-thief - type: RoleRequirement roles: - components: + mindRoles: - ThiefRole - type: entity diff --git a/Resources/Prototypes/Objectives/traitor.yml b/Resources/Prototypes/Objectives/traitor.yml index a156061a9a..b6f31449c0 100644 --- a/Resources/Prototypes/Objectives/traitor.yml +++ b/Resources/Prototypes/Objectives/traitor.yml @@ -7,7 +7,7 @@ issuer: objective-issuer-syndicate - type: RoleRequirement roles: - components: + mindRoles: - TraitorRole - type: entity diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml new file mode 100644 index 0000000000..eb92fa51ae --- /dev/null +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -0,0 +1,180 @@ +- type: entity + id: BaseMindRole + name: Mind Role + description: Mind Role entity + abstract: true + components: + - type: MindRole + +- type: entity + parent: BaseMindRole + id: BaseMindRoleAntag + abstract: true + components: + - type: MindRole + antag: true + +#Observer +- type: entity + parent: BaseMindRole + id: MindRoleObserver + name: Observer Role + components: + - type: ObserverRole + +#Ghostrole Marker +- type: entity + parent: BaseMindRole + id: MindRoleGhostMarker + name: Ghost Role + components: + - type: GhostRoleMarkerRole + +# The Job MindRole holds the mob's Job prototype +- type: entity + parent: BaseMindRole + id: MindRoleJob + name: Job Role +# description: + # MindRoleComponent.JobPrototype is filled by SharedJobSystem + +# Subverted Silicon +- type: entity + parent: BaseMindRoleAntag + id: MindRoleSubvertedSilicon + name: Subverted Silicon Role + description: + components: + - type: SubvertedSiliconRole + +# Dragon +- type: entity + parent: BaseMindRoleAntag + id: MindRoleDragon + name: Dragon Role +# description: + components: + - type: MindRole + antagPrototype: Dragon + exclusiveAntag: true + - type: DragonRole + - type: RoleBriefing + briefing: dragon-role-briefing + +# Ninja +- type: entity + parent: BaseMindRoleAntag + id: MindRoleNinja + name: Space Ninja Role +# description: mind-role-ninja-description + components: + - type: MindRole + antagPrototype: SpaceNinja + exclusiveAntag: true + - type: NinjaRole + +# Nukies +- type: entity + parent: BaseMindRoleAntag + id: MindRoleNukeops + name: Nukeops Operative Role +# description: mind-role-nukeops-description + components: + - type: MindRole + exclusiveAntag: true + antagPrototype: Nukeops + - type: NukeopsRole + +- type: entity + parent: MindRoleNukeops + id: MindRoleNukeopsMedic + name: Nukeops Medic Role +# description: mind-role-nukeops-medic-description + components: + - type: MindRole + antagPrototype: NukeopsMedic + +- type: entity + parent: MindRoleNukeops + id: MindRoleNukeopsCommander + name: Nukeops Commander Role +# description: mind-role-nukeops-commander-description + components: + - type: MindRole + antagPrototype: NukeopsCommander + +# Revolutionaries +- type: entity + parent: BaseMindRoleAntag + id: MindRoleHeadRevolutionary + name: Head Revolutionary Role +# description: mind-role-head-revolutionary-description + components: + - type: MindRole + antagPrototype: HeadRev + exclusiveAntag: true + - type: RevolutionaryRole + +- type: entity + parent: MindRoleHeadRevolutionary + id: MindRoleRevolutionary + name: Revolutionary Role +# description: mind-role-revolutionary-description + components: + - type: MindRole + antagPrototype: Rev + +# Thief +- type: entity + parent: BaseMindRoleAntag + id: MindRoleThief + name: Thief Role +# description: mind-role-thief-description + components: + - type: MindRole + antagPrototype: Thief + - type: ThiefRole + +# Traitors +- type: entity + parent: BaseMindRoleAntag + id: MindRoleTraitor + name: Traitor Role +# description: mind-role-traitor-description + components: + - type: MindRole + antagPrototype: Traitor + exclusiveAntag: true + - type: TraitorRole + +- type: entity + parent: MindRoleTraitor + id: MindRoleTraitorSleeper + name: Sleeper Agent Role +# description: mind-role-traitor-sleeper-description + components: + - type: MindRole + antagPrototype: TraitorSleeper + +# Zombie Squad +- type: entity + parent: BaseMindRoleAntag + id: MindRoleInitialInfected + name: Initial Infected Role +# description: mind-role-initial-infected-description + components: + - type: MindRole + antagPrototype: InitialInfected + exclusiveAntag: true + - type: InitialInfectedRole + +- type: entity + parent: BaseMindRoleAntag + id: MindRoleZombie + name: Zombie Role +# description: mind-role-zombie-description + components: + - type: MindRole + antagPrototype: Zombie + exclusiveAntag: true + - type: ZombieRole -- 2.51.2