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
This commit is contained in:
Errant
2024-10-10 10:48:56 +02:00
committed by GitHub
parent 3e078ab3e0
commit 93c7bdc134
65 changed files with 1082 additions and 556 deletions

View File

@@ -1,4 +1,5 @@
#nullable enable #nullable enable
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Body.Components; using Content.Server.Body.Components;
using Content.Server.GameTicking; using Content.Server.GameTicking;
@@ -120,8 +121,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind)); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
var roles = roleSys.MindGetAllRoles(mind); var roles = roleSys.MindGetAllRoleInfo(mind);
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
Assert.That(cmdRoles.Count(), Is.EqualTo(1)); Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The second dummy player should be a medic // The second dummy player should be a medic
@@ -131,8 +132,8 @@ public sealed class NukeOpsTest
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind)); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True); Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False); Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
roles = roleSys.MindGetAllRoles(dummyMind); roles = roleSys.MindGetAllRoleInfo(dummyMind);
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent); cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
Assert.That(cmdRoles.Count(), Is.EqualTo(1)); Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The other two players should have just spawned in as normal. // The other two players should have just spawned in as normal.
@@ -141,13 +142,14 @@ public sealed class NukeOpsTest
void CheckDummy(int i) void CheckDummy(int i)
{ {
var ent = dummyEnts[i]; var ent = dummyEnts[i];
var mind = mindSys.GetMind(ent)!.Value; var mindCrew = mindSys.GetMind(ent)!.Value;
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False); Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
Assert.That(roleSys.MindIsAntagonist(mind), Is.False); Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False); Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False); Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True); 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<string>() { "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 // 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++) for (var i = 0; i < nukies.Length - 1; i++)
{ {
entMan.DeleteEntity(nukies[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!"); $"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
} }
// Delete the last nukie and make sure the round ends. // Delete the last nukie and make sure the round ends.

View File

@@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Roles.Jobs;
namespace Content.IntegrationTests.Tests.Internals; namespace Content.IntegrationTests.Tests.Internals;
@@ -25,10 +24,7 @@ public sealed class AutoInternalsTests
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
var profile = new HumanoidCharacterProfile(); var profile = new HumanoidCharacterProfile();
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent() var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null);
{
Prototype = "TestInternalsDummy"
}, profile, station: null);
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!"); 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!"); Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");

View File

@@ -1,10 +1,8 @@
#nullable enable #nullable enable
using System.Linq; using System.Linq;
using Content.Server.Ghost;
using Content.Server.Ghost.Roles; using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components; using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Commands; using Content.Server.Mind.Commands;
using Content.Server.Players;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
@@ -18,7 +16,6 @@ using Robust.Server.Console;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -287,27 +284,27 @@ public sealed partial class MindTests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
}); });
var traitorRole = new TraitorRoleComponent(); var traitorRole = "MindRoleTraitor";
roleSystem.MindAddRole(mindId, traitorRole); roleSystem.MindAddRole(mindId, traitorRole);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
}); });
var jobRole = new JobComponent(); var jobRole = "";
roleSystem.MindAddRole(mindId, jobRole); roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId)); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId)); Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
}); });
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId); roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
@@ -315,15 +312,15 @@ public sealed partial class MindTests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId)); Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
}); });
roleSystem.MindRemoveRole<JobComponent>(mindId); roleSystem.MindRemoveRole<JobRoleComponent>(mindId);
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False); Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
}); });
}); });

View File

@@ -3,7 +3,6 @@ using Content.Server.Station.Systems;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -68,10 +67,7 @@ public sealed class LoadoutTests
profile.SetLoadout(new RoleLoadout("LoadoutTester")); profile.SetLoadout(new RoleLoadout("LoadoutTester"));
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent() var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null);
{
Prototype = "LoadoutTester"
}, profile, station: null);
var slotQuery = inventorySystem.GetSlotEnumerator(tester); var slotQuery = inventorySystem.GetSlotEnumerator(tester);
var checkedCount = 0; var checkedCount = 0;

View File

@@ -11,7 +11,6 @@ using Content.Server.Preferences.Managers;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Server.Roles.Jobs; using Content.Server.Roles.Jobs;
using Content.Server.Shuttles.Components; using Content.Server.Shuttles.Components;
using Content.Server.Station.Systems;
using Content.Shared.Antag; using Content.Shared.Antag;
using Content.Shared.Clothing; using Content.Shared.Clothing;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
@@ -20,7 +19,6 @@ using Content.Shared.Ghost;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Players; using Content.Shared.Players;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Server.Audio; using Robust.Server.Audio;
@@ -37,14 +35,14 @@ namespace Content.Server.Antag;
public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent> public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelectionComponent>
{ {
[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 AudioSystem _audio = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly GhostRoleSystem _ghostRole = default!;
[Dependency] private readonly JobSystem _jobs = default!; [Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly LoadoutSystem _loadout = default!; [Dependency] private readonly LoadoutSystem _loadout = default!;
[Dependency] private readonly MindSystem _mind = 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 RoleSystem _role = default!;
[Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
@@ -193,6 +191,9 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary> /// <summary>
/// Chooses antagonists from the given selection of players /// Chooses antagonists from the given selection of players
/// </summary> /// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false) public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, bool midround = false)
{ {
if (ent.Comp.SelectionsComplete) if (ent.Comp.SelectionsComplete)
@@ -209,8 +210,14 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
/// <summary> /// <summary>
/// Chooses antagonists from the given selection of players for the given antag definition. /// Chooses antagonists from the given selection of players for the given antag definition.
/// </summary> /// </summary>
/// <param name="ent">The antagonist rule entity</param>
/// <param name="pool">The players to choose from</param>
/// <param name="def">The antagonist selection parameters and criteria</param>
/// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param> /// <param name="midround">Disable picking players for pre-spawn antags in the middle of a round</param>
public void ChooseAntags(Entity<AntagSelectionComponent> ent, IList<ICommonSession> pool, AntagSelectionDefinition def, bool midround = false) public void ChooseAntags(Entity<AntagSelectionComponent> ent,
IList<ICommonSession> pool,
AntagSelectionDefinition def,
bool midround = false)
{ {
var playerPool = GetPlayerPool(ent, pool, def); var playerPool = GetPlayerPool(ent, pool, def);
var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def); var count = GetTargetAntagCount(ent, GetTotalPlayerCount(pool), def);
@@ -331,7 +338,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
EntityManager.AddComponents(player, def.Components); EntityManager.AddComponents(player, def.Components);
// Equip the entity's RoleLoadout and LoadoutGroup // Equip the entity's RoleLoadout and LoadoutGroup
List<ProtoId<StartingGearPrototype>>? gear = new(); List<ProtoId<StartingGearPrototype>> gear = new();
if (def.StartingGear is not null) if (def.StartingGear is not null)
gear.Add(def.StartingGear.Value); gear.Add(def.StartingGear.Value);
@@ -350,7 +357,7 @@ public sealed partial class AntagSelectionSystem : GameRuleSystem<AntagSelection
} }
_mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true);
_role.MindAddRoles(curMind.Value, def.MindComponents, null, true); _role.MindAddRoles(curMind.Value, def.MindRoles, null, true);
ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); ent.Comp.SelectedMinds.Add((curMind.Value, Name(player)));
SendBriefing(session, def.Briefing); SendBriefing(session, def.Briefing);
} }

View File

@@ -3,7 +3,6 @@ using Content.Shared.Antag;
using Content.Shared.Destructible.Thresholds; using Content.Shared.Destructible.Thresholds;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Storage;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -145,10 +144,17 @@ public partial struct AntagSelectionDefinition()
/// <summary> /// <summary>
/// Components added to the player's mind. /// Components added to the player's mind.
/// Do NOT use this to add role-type components. Add those as MindRoles instead
/// </summary> /// </summary>
[DataField] [DataField]
public ComponentRegistry MindComponents = new(); public ComponentRegistry MindComponents = new();
/// <summary>
/// List of Mind Role Prototypes to be added to the player's mind.
/// </summary>
[DataField]
public List<ProtoId<EntityPrototype>>? MindRoles;
/// <summary> /// <summary>
/// A set of starting gear that's equipped to the player. /// A set of starting gear that's equipped to the player.
/// </summary> /// </summary>

View File

@@ -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. // 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. // 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) foreach (var special in prototype.Special)
{ {

View File

@@ -1,16 +1,11 @@
using System.Linq; using System.Linq;
using Content.Shared.EntityEffects; using Content.Shared.EntityEffects;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Localizations; using Content.Shared.Localizations;
using Robust.Shared.Prototypes;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Roles.Jobs; using Content.Shared.Roles.Jobs;
using Content.Shared.Station; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.IoC;
namespace Content.Server.EntityEffects.EffectConditions; namespace Content.Server.EntityEffects.EffectConditions;
@@ -21,20 +16,23 @@ public sealed partial class JobCondition : EntityEffectCondition
public override bool Condition(EntityEffectBaseArgs args) public override bool Condition(EntityEffectBaseArgs args)
{ {
args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer); args.EntityManager.TryGetComponent<MindContainerComponent>(args.TargetEntity, out var mindContainer);
if (mindContainer != null && mindContainer.Mind != null)
{ if ( mindContainer is null
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); || !args.EntityManager.TryGetComponent<MindComponent>(mindContainer.Mind, out var mind))
if (args.EntityManager.TryGetComponent<JobComponent>(mindContainer?.Mind, out var comp) && prototypeManager.TryIndex(comp.Prototype, out var prototype)) return false;
{
foreach (var jobId in Job) foreach (var roleId in mind.MindRoles)
{
if (prototype.ID == jobId)
{ {
if(!args.EntityManager.HasComponent<JobRoleComponent>(roleId))
continue;
if(!args.EntityManager.TryGetComponent<MindRoleComponent>(roleId, out var mindRole)
|| mindRole.JobPrototype is null)
continue;
if (Job.Contains(mindRole.JobPrototype.Value))
return true; return true;
} }
}
}
}
return false; return false;
} }
@@ -45,5 +43,3 @@ public sealed partial class JobCondition : EntityEffectCondition
return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames))); return Loc.GetString("reagent-effect-condition-guidebook-job-condition", ("job", ContentLocalizationManager.FormatListToOr(localizedNames)));
} }
} }

View File

@@ -4,6 +4,7 @@ using Content.Server.Discord;
using Content.Server.GameTicking.Events; using Content.Server.GameTicking.Events;
using Content.Server.Ghost; using Content.Server.Ghost;
using Content.Server.Maps; using Content.Server.Maps;
using Content.Server.Roles;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
@@ -26,6 +27,7 @@ namespace Content.Server.GameTicking
public sealed partial class GameTicker public sealed partial class GameTicker
{ {
[Dependency] private readonly DiscordWebhook _discord = default!; [Dependency] private readonly DiscordWebhook _discord = default!;
[Dependency] private readonly RoleSystem _role = default!;
[Dependency] private readonly ITaskManager _taskManager = default!; [Dependency] private readonly ITaskManager _taskManager = default!;
private static readonly Counter RoundNumberMetric = Metrics.CreateCounter( private static readonly Counter RoundNumberMetric = Metrics.CreateCounter(
@@ -373,7 +375,7 @@ namespace Content.Server.GameTicking
var userId = mind.UserId ?? mind.OriginalOwnerUserId; var userId = mind.UserId ?? mind.OriginalOwnerUserId;
var connected = false; var connected = false;
var observer = HasComp<ObserverRoleComponent>(mindId); var observer = _role.MindHasRole<ObserverRoleComponent>(mindId);
// Continuing // Continuing
if (userId != null && _playerManager.ValidSessionId(userId.Value)) if (userId != null && _playerManager.ValidSessionId(userId.Value))
{ {
@@ -400,7 +402,7 @@ namespace Content.Server.GameTicking
_pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true); _pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true);
} }
var roles = _roles.MindGetAllRoles(mindId); var roles = _roles.MindGetAllRoleInfo(mindId);
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo() var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
{ {

View File

@@ -3,8 +3,6 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Content.Server.Administration.Managers; using Content.Server.Administration.Managers;
using Content.Server.GameTicking.Events; using Content.Server.GameTicking.Events;
using Content.Server.Ghost;
using Content.Server.Shuttles.Components;
using Content.Server.Spawners.Components; using Content.Server.Spawners.Components;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Server.Station.Components; using Content.Server.Station.Components;
@@ -224,13 +222,12 @@ namespace Content.Server.GameTicking
_mind.SetUserId(newMind, data.UserId); _mind.SetUserId(newMind, data.UserId);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId); var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
var job = new JobComponent {Prototype = jobId}; _roles.MindAddJobRole(newMind, silent: silent, jobPrototype:jobId);
_roles.MindAddRole(newMind, job, silent: silent);
var jobName = _jobs.MindTryGetJobName(newMind); var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player); _playTimeTrackings.PlayerRolesChanged(player);
var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, job, character); var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(station, jobId, character);
DebugTools.AssertNotNull(mobMaybe); DebugTools.AssertNotNull(mobMaybe);
var mob = mobMaybe!.Value; var mob = mobMaybe!.Value;
@@ -269,13 +266,17 @@ namespace Content.Server.GameTicking
_stationJobs.TryAssignJob(station, jobPrototype, player.UserId); _stationJobs.TryAssignJob(station, jobPrototype, player.UserId);
if (lateJoin) if (lateJoin)
{
_adminLogger.Add(LogType.LateJoin, _adminLogger.Add(LogType.LateJoin,
LogImpact.Medium, LogImpact.Medium,
$"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); $"Player {player.Name} late joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}.");
}
else else
{
_adminLogger.Add(LogType.RoundStartJoin, _adminLogger.Add(LogType.RoundStartJoin,
LogImpact.Medium, LogImpact.Medium,
$"Player {player.Name} joined as {character.Name:characterName} on station {Name(station):stationName} with {ToPrettyString(mob):entity} as a {jobName:jobName}."); $"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. // Make sure they're aware of extended access.
if (Comp<StationJobsComponent>(station).ExtendedAccess if (Comp<StationJobsComponent>(station).ExtendedAccess
@@ -361,7 +362,7 @@ namespace Content.Server.GameTicking
var (mindId, mindComp) = _mind.CreateMind(player.UserId, name); var (mindId, mindComp) = _mind.CreateMind(player.UserId, name);
mind = (mindId, mindComp); mind = (mindId, mindComp);
_mind.SetUserId(mind.Value, player.UserId); _mind.SetUserId(mind.Value, player.UserId);
_roles.MindAddRole(mind.Value, new ObserverRoleComponent()); _roles.MindAddRole(mind.Value, "MindRoleObserver");
} }
var ghost = _ghost.SpawnGhost(mind.Value); var ghost = _ghost.SpawnGhost(mind.Value);

View File

@@ -9,7 +9,6 @@ using Content.Server.RoundEnd;
using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Events;
using Content.Server.Shuttles.Systems; using Content.Server.Shuttles.Systems;
using Content.Server.Station.Components; using Content.Server.Station.Components;
using Content.Server.Store.Components;
using Content.Server.Store.Systems; using Content.Server.Store.Systems;
using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs; using Content.Shared.Mobs;
@@ -31,9 +30,9 @@ namespace Content.Server.GameTicking.Rules;
public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent> public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
{ {
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!;
[Dependency] private readonly NpcFactionSystem _npcFaction = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly StoreSystem _store = default!;
@@ -57,6 +56,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeOperativeComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<NukeOperativeComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified); SubscribeLocalEvent<NukeOperativeComponent, EntityZombifiedEvent>(OnOperativeZombified);
SubscribeLocalEvent<NukeopsRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<ConsoleFTLAttemptEvent>(OnShuttleFTLAttempt); SubscribeLocalEvent<ConsoleFTLAttemptEvent>(OnShuttleFTLAttempt);
SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared); SubscribeLocalEvent<WarDeclaredEvent>(OnWarDeclared);
SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt); SubscribeLocalEvent<CommunicationConsoleCallShuttleAttemptEvent>(OnShuttleCallAttempt);
@@ -65,7 +66,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
SubscribeLocalEvent<NukeopsRuleComponent, RuleLoadedGridsEvent>(OnRuleLoadedGrids); SubscribeLocalEvent<NukeopsRuleComponent, RuleLoadedGridsEvent>(OnRuleLoadedGrids);
} }
protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, protected override void Started(EntityUid uid,
NukeopsRuleComponent component,
GameRuleComponent gameRule,
GameRuleStartedEvent args) GameRuleStartedEvent args)
{ {
var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>(); var eligible = new List<Entity<StationEventEligibleComponent, NpcFactionMemberComponent>>();
@@ -85,7 +88,9 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
} }
#region Event Handlers #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) ref RoundEndTextAppendEvent args)
{ {
var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}");
@@ -227,7 +232,8 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// If the disk is currently at Central Command, the crew wins - just slightly. // If the disk is currently at Central Command, the crew wins - just slightly.
// This also implies that some nuclear operatives have died. // This also implies that some nuclear operatives have died.
SetWinType(ent, diskAtCentCom SetWinType(ent,
diskAtCentCom
? WinType.CrewMinor ? WinType.CrewMinor
: WinType.OpsMinor); : WinType.OpsMinor);
ent.Comp.WinConditions.Add(diskAtCentCom ent.Comp.WinConditions.Add(diskAtCentCom
@@ -456,8 +462,11 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
: WinCondition.AllNukiesDead); : WinCondition.AllNukiesDead);
SetWinType(ent, WinType.CrewMajor, false); SetWinType(ent, WinType.CrewMajor, false);
_roundEndSystem.DoRoundEndBehavior( _roundEndSystem.DoRoundEndBehavior(nukeops.RoundEndBehavior,
nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); nukeops.EvacShuttleTime,
nukeops.RoundEndTextSender,
nukeops.RoundEndTextShuttleCall,
nukeops.RoundEndTextAnnouncement);
// prevent it called multiple times // prevent it called multiple times
nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; nukeops.RoundEndBehavior = RoundEndBehavior.Nothing;
@@ -465,16 +474,22 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args) private void OnAfterAntagEntSelected(Entity<NukeopsRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{ {
if (ent.Comp.TargetStation is not { } station) var target = (ent.Comp.TargetStation is not null) ? Name(ent.Comp.TargetStation.Value) : "the target";
return;
_antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", _antag.SendBriefing(args.Session,
("station", station), Loc.GetString("nukeops-welcome",
("station", target),
("name", Name(ent))), ("name", Name(ent))),
Color.Red, Color.Red,
ent.Comp.GreetSoundNotification); ent.Comp.GreetSoundNotification);
} }
private void OnGetBriefing(Entity<NukeopsRoleComponent> role, ref GetBriefingEvent args)
{
// TODO Different character screen briefing for the 3 nukie types
args.Append(Loc.GetString("nukeops-briefing"));
}
/// <remarks> /// <remarks>
/// Is this method the shitty glue holding together the last of my sanity? yes. /// Is this method the shitty glue holding together the last of my sanity? yes.
/// Do i have a better solution? not presently. /// Do i have a better solution? not presently.

View File

@@ -15,7 +15,6 @@ using Content.Shared.Database;
using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Components;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Mind;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Content.Shared.Mindshield.Components; using Content.Shared.Mindshield.Components;
using Content.Shared.Mobs; using Content.Shared.Mobs;
@@ -38,8 +37,8 @@ namespace Content.Server.GameTicking.Rules;
public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent> public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleComponent>
{ {
[Dependency] private readonly IAdminLogManager _adminLogManager = default!; [Dependency] private readonly IAdminLogManager _adminLogManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
[Dependency] private readonly EuiManager _euiMan = default!; [Dependency] private readonly EuiManager _euiMan = default!;
[Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MobStateSystem _mobState = default!;
@@ -49,7 +48,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
[Dependency] private readonly SharedStunSystem _stun = default!; [Dependency] private readonly SharedStunSystem _stun = default!;
[Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!;
[Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; [Dependency] private readonly IGameTiming _timing = default!;
//Used in OnPostFlash, no reference to the rule component is available //Used in OnPostFlash, no reference to the rule component is available
public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary"; public readonly ProtoId<NpcFactionPrototype> RevolutionaryNpcFaction = "Revolutionary";
@@ -59,9 +58,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged); SubscribeLocalEvent<CommandStaffComponent, MobStateChangedEvent>(OnCommandMobStateChanged);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash); SubscribeLocalEvent<HeadRevolutionaryComponent, AfterFlashedEvent>(OnPostFlash);
SubscribeLocalEvent<HeadRevolutionaryComponent, MobStateChangedEvent>(OnHeadRevMobStateChanged);
SubscribeLocalEvent<RevolutionaryRoleComponent, GetBriefingEvent>(OnGetBriefing);
} }
protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
@@ -85,7 +87,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
} }
} }
protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, protected override void AppendRoundEndText(EntityUid uid,
RevolutionaryRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args) ref RoundEndTextAppendEvent args)
{ {
base.AppendRoundEndText(uid, component, gameRule, ref args); base.AppendRoundEndText(uid, component, gameRule, ref args);
@@ -101,7 +105,9 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count))); args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count)));
foreach (var (mind, data, name) in sessionData) foreach (var (mind, data, name) in sessionData)
{ {
var count = CompOrNull<RevolutionaryRoleComponent>(mind)?.ConvertedCount ?? 0; _role.MindHasRole<RevolutionaryRoleComponent>(mind, out var role);
var count = CompOrNull<RevolutionaryRoleComponent>(role)?.ConvertedCount ?? 0;
args.AddLine(Loc.GetString("rev-headrev-name-user", args.AddLine(Loc.GetString("rev-headrev-name-user",
("name", name), ("name", name),
("username", data.UserName), ("username", data.UserName),
@@ -113,10 +119,8 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args) private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref GetBriefingEvent args)
{ {
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null) var ent = args.Mind.Comp.OwnedEntity;
return; var head = HasComp<HeadRevolutionaryComponent>(ent);
var head = HasComp<HeadRevolutionaryComponent>(mind.OwnedEntity);
args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing")); args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing"));
} }
@@ -145,15 +149,20 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
if (ev.User != null) if (ev.User != null)
{ {
_adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary"); _adminLogManager.Add(LogType.Mind,
LogImpact.Medium,
$"{ToPrettyString(ev.User.Value)} converted {ToPrettyString(ev.Target)} into a Revolutionary");
if (_mind.TryGetRole<RevolutionaryRoleComponent>(ev.User.Value, out var headrev)) if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
headrev.ConvertedCount++; {
if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out _, out var role))
role.Value.Comp.ConvertedCount++;
}
} }
if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId)) if (mindId == default || !_role.MindHasRole<RevolutionaryRoleComponent>(mindId))
{ {
_role.MindAddRole(mindId, new RevolutionaryRoleComponent { PrototypeId = RevPrototypeId }); _role.MindAddRole(mindId, "MindRoleRevolutionary");
} }
if (mind?.Session != null) if (mind?.Session != null)

View File

@@ -1,18 +1,12 @@
using Content.Server.Antag; using Content.Server.Antag;
using Content.Server.GameTicking.Rules.Components; using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Server.Objectives;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Mind;
using Content.Shared.Objectives.Components;
using Robust.Shared.Random;
namespace Content.Server.GameTicking.Rules; namespace Content.Server.GameTicking.Rules;
public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent> public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
{ {
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!;
public override void Initialize() public override void Initialize()
@@ -24,32 +18,33 @@ public sealed class ThiefRuleSystem : GameRuleSystem<ThiefRuleComponent>
SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing); SubscribeLocalEvent<ThiefRoleComponent, GetBriefingEvent>(OnGetBriefing);
} }
private void AfterAntagSelected(Entity<ThiefRuleComponent> ent, ref AfterAntagEntitySelectedEvent args) // Greeting upon thief activation
private void AfterAntagSelected(Entity<ThiefRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{ {
if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) var ent = args.EntityUid;
return; _antag.SendBriefing(ent, MakeBriefing(ent), null, null);
//Generate objectives
_antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null);
} }
//Add mind briefing // Character screen briefing
private void OnGetBriefing(Entity<ThiefRoleComponent> thief, ref GetBriefingEvent args) private void OnGetBriefing(Entity<ThiefRoleComponent> role, ref GetBriefingEvent args)
{ {
if (!TryComp<MindComponent>(thief.Owner, out var mind) || mind.OwnedEntity == null) var ent = args.Mind.Comp.OwnedEntity;
return;
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<HumanoidAppearanceComponent>(thief); var isHuman = HasComp<HumanoidAppearanceComponent>(ent);
var briefing = isHuman var briefing = isHuman
? Loc.GetString("thief-role-greeting-human") ? Loc.GetString("thief-role-greeting-human")
: Loc.GetString("thief-role-greeting-animal"); : Loc.GetString("thief-role-greeting-animal");
if (isHuman)
briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n"; briefing += "\n \n" + Loc.GetString("thief-role-greeting-equipment") + "\n";
return briefing; return briefing;
} }
} }

View File

@@ -10,9 +10,7 @@ using Content.Shared.Database;
using Content.Shared.GameTicking.Components; using Content.Shared.GameTicking.Components;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.NPC.Systems; using Content.Shared.NPC.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Radio;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Roles.Jobs; using Content.Shared.Roles.Jobs;
using Content.Shared.Roles.RoleCodeword; using Content.Shared.Roles.RoleCodeword;
@@ -28,22 +26,21 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b"); private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b");
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
[Dependency] private readonly AntagSelectionSystem _antag = 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 IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = 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 SharedRoleCodewordSystem _roleCodewordSystem = default!; [Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected); SubscribeLocalEvent<TraitorRuleComponent, AfterAntagEntitySelectedEvent>(AfterEntitySelected);
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend); SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
} }
@@ -80,7 +77,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true) 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)) if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
return false; return false;
@@ -92,7 +89,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
{ {
// Calculate the amount of currency on the uplink. // Calculate the amount of currency on the uplink.
var startingBalance = component.StartingBalance; 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); startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0);
// creadth: we need to create uplink for the antag. // creadth: we need to create uplink for the antag.
@@ -111,14 +108,18 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification); _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
component.TraitorMinds.Add(mindId); component.TraitorMinds.Add(mindId);
// Assign briefing // 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<TraitorRoleComponent>(mindId, out var traitorRole);
if (traitorRole is not null)
{ {
Briefing = briefing AddComp<RoleBriefingComponent>(traitorRole.Value.Owner);
}, mind, true); Comp<RoleBriefingComponent>(traitorRole.Value.Owner).Briefing = briefing;
}
// Send codewords to only the traitor client // Send codewords to only the traitor client
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found

View File

@@ -13,6 +13,7 @@ using Content.Shared.Mind;
using Content.Shared.Mobs; using Content.Shared.Mobs;
using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Roles;
using Content.Shared.Zombies; using Content.Shared.Zombies;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -22,15 +23,16 @@ namespace Content.Server.GameTicking.Rules;
public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent> public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
{ {
[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 StationSystem _station = default!;
[Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly AntagSelectionSystem _antag = default!;
[Dependency] private readonly ChatSystem _chat = 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 IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ZombieSystem _zombie = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -41,23 +43,20 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf); SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
} }
private void OnGetBriefing(EntityUid uid, InitialInfectedRoleComponent component, ref GetBriefingEvent args) private void OnGetBriefing(Entity<InitialInfectedRoleComponent> role, ref GetBriefingEvent args)
{ {
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null) if (!_roles.MindHasRole<ZombieRoleComponent>(args.Mind.Owner))
return;
if (HasComp<ZombieRoleComponent>(uid)) // don't show both briefings
return;
args.Append(Loc.GetString("zombie-patientzero-role-greeting")); args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
} }
private void OnGetBriefing(EntityUid uid, ZombieRoleComponent component, ref GetBriefingEvent args) private void OnGetBriefing(Entity<ZombieRoleComponent> role, ref GetBriefingEvent args)
{ {
if (!TryComp<MindComponent>(uid, out var mind) || mind.OwnedEntity == null)
return;
args.Append(Loc.GetString("zombie-infection-greeting")); 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) ref RoundEndTextAppendEvent args)
{ {
base.AppendRoundEndText(uid, component, gameRule, ref args); base.AppendRoundEndText(uid, component, gameRule, ref args);

View File

@@ -1,11 +1,13 @@
namespace Content.Server.Ghost.Roles; using Content.Shared.Roles;
namespace Content.Server.Ghost.Roles;
/// <summary> /// <summary>
/// This is used for round end display of 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. /// It may also be used to ensure some ghost roles count as antagonists in future.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
public sealed partial class GhostRoleMarkerRoleComponent : Component public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
{ {
[DataField("name")] public string? Name; [DataField("name")] public string? Name;
} }

View File

@@ -71,18 +71,22 @@ public sealed class GhostRoleSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset); SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached); SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded); SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved); SubscribeLocalEvent<GhostTakeoverAvailableComponent, MindRemovedMessage>(OnMindRemoved);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged); SubscribeLocalEvent<GhostTakeoverAvailableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<GhostRoleComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup); SubscribeLocalEvent<GhostRoleComponent, ComponentStartup>(OnRoleStartup);
SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown); SubscribeLocalEvent<GhostRoleComponent, ComponentShutdown>(OnRoleShutdown);
SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused); SubscribeLocalEvent<GhostRoleComponent, EntityPausedEvent>(OnPaused);
SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused); SubscribeLocalEvent<GhostRoleComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit); SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentInit>(OnRaffleInit);
SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown); SubscribeLocalEvent<GhostRoleRaffleComponent, ComponentShutdown>(OnRaffleShutdown);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole); SubscribeLocalEvent<GhostRoleMobSpawnerComponent, TakeGhostRoleEvent>(OnSpawnerTakeRole);
SubscribeLocalEvent<GhostTakeoverAvailableComponent, TakeGhostRoleEvent>(OnTakeoverTakeRole);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb); SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GetVerbsEvent<Verb>>(OnVerb);
SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage); SubscribeLocalEvent<GhostRoleMobSpawnerComponent, GhostRoleRadioMessage>(OnGhostRoleRadioMessage);
_playerManager.PlayerStatusChanged += PlayerStatusChanged; _playerManager.PlayerStatusChanged += PlayerStatusChanged;
@@ -509,7 +513,11 @@ public sealed class GhostRoleSystem : EntitySystem
var newMind = _mindSystem.CreateMind(player.UserId, var newMind = _mindSystem.CreateMind(player.UserId,
EntityManager.GetComponent<MetaDataComponent>(mob).EntityName); EntityManager.GetComponent<MetaDataComponent>(mob).EntityName);
_roleSystem.MindAddRole(newMind, new GhostRoleMarkerRoleComponent { Name = role.RoleName });
_roleSystem.MindAddRole(newMind, "MindRoleGhostMarker");
if(_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind, out _, out var markerRole))
markerRole.Value.Comp.Name = role.RoleName;
_mindSystem.SetUserId(newMind, player.UserId); _mindSystem.SetUserId(newMind, player.UserId);
_mindSystem.TransferTo(newMind, mob); _mindSystem.TransferTo(newMind, mob);
@@ -602,10 +610,7 @@ public sealed class GhostRoleSystem : EntitySystem
if (ghostRole.JobProto != null) if (ghostRole.JobProto != null)
{ {
if (HasComp<JobComponent>(args.Mind)) _roleSystem.MindAddJobRole(args.Mind, args.Mind, silent:false,ghostRole.JobProto);
_roleSystem.MindRemoveRole<JobComponent>(args.Mind);
_roleSystem.MindAddRole(args.Mind, new JobComponent { Prototype = ghostRole.JobProto });
} }
ghostRole.Taken = true; ghostRole.Taken = true;

View File

@@ -43,7 +43,7 @@ namespace Content.Server.Mind.Commands
builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity); builder.AppendFormat("player: {0}, mob: {1}\nroles: ", mind.UserId, mind.OwnedEntity);
var roles = _entities.System<SharedRoleSystem>(); var roles = _entities.System<SharedRoleSystem>();
foreach (var role in roles.MindGetAllRoles(mindId)) foreach (var role in roles.MindGetAllRoleInfo(mindId))
{ {
builder.AppendFormat("{0} ", role.Name); builder.AppendFormat("{0} ", role.Name);
} }

View File

@@ -89,7 +89,7 @@ public sealed class KillPersonConditionSystem : EntitySystem
foreach (var mind in allHumans) foreach (var mind in allHumans)
{ {
// RequireAdminNotify used as a cheap way to check for command department // 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); allHeads.Add(mind);
} }

View File

@@ -1,9 +1,10 @@
using Content.Server.Objectives.Components; using Content.Server.Objectives.Components;
using Content.Server.Roles;
using Content.Server.Warps; using Content.Server.Warps;
using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Components;
using Content.Shared.Ninja.Components; using Content.Shared.Ninja.Components;
using Content.Shared.Roles;
using Robust.Shared.Random; using Robust.Shared.Random;
using Content.Server.Roles;
namespace Content.Server.Objectives.Systems; namespace Content.Server.Objectives.Systems;
@@ -16,6 +17,7 @@ public sealed class NinjaConditionsSystem : EntitySystem
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly NumberObjectiveSystem _number = default!; [Dependency] private readonly NumberObjectiveSystem _number = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -46,10 +48,8 @@ public sealed class NinjaConditionsSystem : EntitySystem
// spider charge // spider charge
private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args) private void OnSpiderChargeRequirementCheck(EntityUid uid, SpiderChargeConditionComponent comp, ref RequirementCheckEvent args)
{ {
if (args.Cancelled || !HasComp<NinjaRoleComponent>(args.MindId)) if (args.Cancelled || !_roles.MindHasRole<NinjaRoleComponent>(args.MindId))
{
return; return;
}
// choose spider charge detonation point // choose spider charge detonation point
var warps = new List<EntityUid>(); var warps = new List<EntityUid>();

View File

@@ -21,7 +21,7 @@ public sealed class NotCommandRequirementSystem : EntitySystem
return; return;
// cheap equivalent to checking that job department is command, since all command members require admin notification when leaving // 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; args.Cancelled = true;
} }
} }

View File

@@ -8,6 +8,8 @@ namespace Content.Server.Objectives.Systems;
/// </summary> /// </summary>
public sealed class NotJobRequirementSystem : EntitySystem public sealed class NotJobRequirementSystem : EntitySystem
{ {
[Dependency] private readonly SharedJobSystem _jobs = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -20,11 +22,10 @@ public sealed class NotJobRequirementSystem : EntitySystem
if (args.Cancelled) if (args.Cancelled)
return; return;
// if player has no job then don't care _jobs.MindTryGetJob(args.MindId, out var proto);
if (!TryComp<JobComponent>(args.MindId, out var job))
return;
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; args.Cancelled = true;
} }
} }

View File

@@ -22,8 +22,6 @@ public sealed class RoleRequirementSystem : EntitySystem
if (args.Cancelled) if (args.Cancelled)
return; 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)) if (_whitelistSystem.IsWhitelistFail(comp.Roles, args.MindId))
args.Cancelled = true; args.Cancelled = true;
} }

View File

@@ -30,14 +30,15 @@ namespace Content.Server.Players.PlayTimeTracking;
/// </summary> /// </summary>
public sealed class PlayTimeTrackingSystem : EntitySystem public sealed class PlayTimeTrackingSystem : EntitySystem
{ {
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IAfkManager _afk = 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 IConfigurationManager _cfg = default!;
[Dependency] private readonly MindSystem _minds = default!; [Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IServerPreferencesManager _preferencesManager = 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() public override void Initialize()
{ {
@@ -101,10 +102,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
public IEnumerable<string> GetTimedRoles(EntityUid mindId) public IEnumerable<string> GetTimedRoles(EntityUid mindId)
{ {
var ev = new MindGetAllRolesEvent(new List<RoleInfo>()); foreach (var role in _roles.MindGetAllRoleInfo(mindId))
RaiseLocalEvent(mindId, ref ev);
foreach (var role in ev.Roles)
{ {
if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId)) if (string.IsNullOrWhiteSpace(role.PlayTimeTrackerId))
continue; continue;

View File

@@ -10,3 +10,5 @@ public sealed partial class CommandStaffComponent : Component
{ {
} }
//TODO this should probably be on a mind role, not the mob

View File

@@ -4,9 +4,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
/// <summary> /// <summary>
/// Role used to keep track of space dragons for antag purposes. /// Added to mind role entities to tag that they are a space dragon.
/// </summary> /// </summary>
[RegisterComponent, Access(typeof(DragonSystem)), ExclusiveAntagonist] [RegisterComponent, Access(typeof(DragonSystem))]
public sealed partial class DragonRoleComponent : AntagonistRoleComponent public sealed partial class DragonRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -2,8 +2,11 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist] /// <summary>
public sealed partial class InitialInfectedRoleComponent : AntagonistRoleComponent /// Added to mind role entities to tag that they are an initial infected.
/// </summary>
[RegisterComponent]
public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -30,7 +30,7 @@ public sealed class JobSystem : SharedJobSystem
if (!_mind.TryGetSession(mindId, out var session)) if (!_mind.TryGetSession(mindId, out var session))
return; return;
if (!MindTryGetJob(mindId, out _, out var prototype)) if (!MindTryGetJob(mindId, out var prototype))
return; return;
_chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name", _chat.DispatchServerMessage(session, Loc.GetString("job-greet-introduce-job-name",
@@ -47,6 +47,6 @@ public sealed class JobSystem : SharedJobSystem
if (MindHasJobWithId(mindId, jobPrototypeId)) if (MindHasJobWithId(mindId, jobPrototypeId))
return; return;
_roles.MindAddRole(mindId, new JobComponent { Prototype = jobPrototypeId }); _roles.MindAddJobRole(mindId, null, false, jobPrototypeId);
} }
} }

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist] /// <summary>
public sealed partial class NinjaRoleComponent : AntagonistRoleComponent /// Added to mind role entities to tag that they are a space ninja.
/// </summary>
[RegisterComponent]
public sealed partial class NinjaRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -3,9 +3,9 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
[RegisterComponent, ExclusiveAntagonist] [RegisterComponent]
public sealed partial class NukeopsRoleComponent : AntagonistRoleComponent public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -45,7 +45,7 @@ namespace Content.Server.Roles
var roles = _entityManager.System<SharedRoleSystem>(); var roles = _entityManager.System<SharedRoleSystem>();
var jobs = _entityManager.System<SharedJobSystem>(); var jobs = _entityManager.System<SharedJobSystem>();
if (jobs.MindHasJobWithId(mind, args[1])) if (jobs.MindHasJobWithId(mind, args[1]))
roles.MindRemoveRole<JobComponent>(mind.Value); roles.MindTryRemoveRole<JobRoleComponent>(mind.Value);
} }
} }
} }

View File

@@ -3,10 +3,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
/// <summary> /// <summary>
/// Added to mind entities to tag that they are a Revolutionary. /// Added to mind role entities to tag that they are a Revolutionary.
/// </summary> /// </summary>
[RegisterComponent, ExclusiveAntagonist] [RegisterComponent]
public sealed partial class RevolutionaryRoleComponent : AntagonistRoleComponent public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent
{ {
/// <summary> /// <summary>
/// For headrevs, how many people you have converted. /// For headrevs, how many people you have converted.

View File

@@ -1,32 +1,40 @@
using Content.Shared.Mind;
using Content.Shared.Roles; using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
public sealed class RoleSystem : SharedRoleSystem public sealed class RoleSystem : SharedRoleSystem
{ {
public override void Initialize()
{
// TODO make roles entities
base.Initialize();
SubscribeAntagEvents<DragonRoleComponent>();
SubscribeAntagEvents<InitialInfectedRoleComponent>();
SubscribeAntagEvents<NinjaRoleComponent>();
SubscribeAntagEvents<NukeopsRoleComponent>();
SubscribeAntagEvents<RevolutionaryRoleComponent>();
SubscribeAntagEvents<SubvertedSiliconRoleComponent>();
SubscribeAntagEvents<TraitorRoleComponent>();
SubscribeAntagEvents<ZombieRoleComponent>();
SubscribeAntagEvents<ThiefRoleComponent>();
}
public string? MindGetBriefing(EntityUid? mindId) public string? MindGetBriefing(EntityUid? mindId)
{ {
if (mindId == null) if (mindId == null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null; return null;
}
TryComp<MindComponent>(mindId.Value, out var mindComp);
if (mindComp is null)
{
Log.Error($"MingGetBriefing failed for mind {mindId}");
return null;
}
var ev = new GetBriefingEvent(); var ev = new GetBriefingEvent();
RaiseLocalEvent(mindId.Value, ref ev);
// This is on the event because while this Entity<T> 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; return ev.Briefing;
} }
} }
@@ -38,8 +46,16 @@ public sealed class RoleSystem : SharedRoleSystem
[ByRefEvent] [ByRefEvent]
public sealed class GetBriefingEvent public sealed class GetBriefingEvent
{ {
/// <summary>
/// The text that will be shown on the Character Screen
/// </summary>
public string? Briefing; public string? Briefing;
/// <summary>
/// The Mind to whose Mind Role Entities the briefing is sent to
/// </summary>
public Entity<MindComponent> Mind;
public GetBriefingEvent(string? briefing = null) public GetBriefingEvent(string? briefing = null)
{ {
Briefing = briefing; Briefing = briefing;

View File

@@ -2,7 +2,10 @@
namespace Content.Server.Roles; namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a hacked borg.
/// </summary>
[RegisterComponent] [RegisterComponent]
public sealed partial class SubvertedSiliconRoleComponent : AntagonistRoleComponent public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
/// <summary>
/// Added to mind role entities to tag that they are a thief.
/// </summary>
[RegisterComponent] [RegisterComponent]
public sealed partial class ThiefRoleComponent : AntagonistRoleComponent public sealed partial class ThiefRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist] /// <summary>
public sealed partial class TraitorRoleComponent : AntagonistRoleComponent /// Added to mind role entities to tag that they are a syndicate traitor.
/// </summary>
[RegisterComponent]
public sealed partial class TraitorRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -2,7 +2,10 @@ using Content.Shared.Roles;
namespace Content.Server.Roles; namespace Content.Server.Roles;
[RegisterComponent, ExclusiveAntagonist] /// <summary>
public sealed partial class ZombieRoleComponent : AntagonistRoleComponent /// Added to mind role entities to tag that they are a zombie.
/// </summary>
[RegisterComponent]
public sealed partial class ZombieRoleComponent : BaseMindRoleComponent
{ {
} }

View File

@@ -28,12 +28,12 @@ namespace Content.Server.Silicons.Laws;
public sealed class SiliconLawSystem : SharedSiliconLawSystem public sealed class SiliconLawSystem : SharedSiliconLawSystem
{ {
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly SharedStunSystem _stunSystem = default!;
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
@@ -178,10 +178,8 @@ public sealed class SiliconLawSystem : SharedSiliconLawSystem
if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _)) if (component.AntagonistRole == null || !_mind.TryGetMind(uid, out var mindId, out _))
return; return;
if (_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId)) if (!_roles.MindHasRole<SubvertedSiliconRoleComponent>(mindId))
return; _roles.MindAddRole(mindId, "MindRoleSubvertedSilicon");
_roles.MindAddRole(mindId, new SubvertedSiliconRoleComponent { PrototypeId = component.AntagonistRole });
} }
public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null) public SiliconLawset GetLaws(EntityUid uid, SiliconLawBoundComponent? component = null)

View File

@@ -12,10 +12,10 @@ namespace Content.Server.Spawners.EntitySystems;
public sealed class ContainerSpawnPointSystem : EntitySystem public sealed class ContainerSpawnPointSystem : EntitySystem
{ {
[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 ContainerSystem _container = default!;
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSystem _station = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = 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 it's just a spawn pref check if it's for cryo (silly).
if (args.HumanoidCharacterProfile?.SpawnPriority != SpawnPriorityPreference.Cryosleep && 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; return;
} }
@@ -49,7 +49,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (spawnPoint.SpawnType == SpawnPointType.Unset) if (spawnPoint.SpawnType == SpawnPointType.Unset)
{ {
// make sure we also check the job here for various reasons. // 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)); possibleContainers.Add((uid, spawnPoint, container, xform));
continue; continue;
} }
@@ -61,7 +61,7 @@ public sealed class ContainerSpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound && if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job && 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)); possibleContainers.Add((uid, spawnPoint, container, xform));
} }

View File

@@ -39,7 +39,7 @@ public sealed class SpawnPointSystem : EntitySystem
if (_gameTicker.RunLevel != GameRunLevel.InRound && if (_gameTicker.RunLevel != GameRunLevel.InRound &&
spawnPoint.SpawnType == SpawnPointType.Job && spawnPoint.SpawnType == SpawnPointType.Job &&
(args.Job == null || spawnPoint.Job == args.Job.Prototype)) (args.Job == null || spawnPoint.Job == args.Job))
{ {
possiblePositions.Add(xform.Coordinates); possiblePositions.Add(xform.Coordinates);
} }

View File

@@ -1,4 +1,3 @@
using System.Linq;
using Content.Server.Access.Systems; using Content.Server.Access.Systems;
using Content.Server.DetailExaminable; using Content.Server.DetailExaminable;
using Content.Server.Humanoid; using Content.Server.Humanoid;
@@ -17,13 +16,10 @@ using Content.Shared.Humanoid.Prototypes;
using Content.Shared.PDA; using Content.Shared.PDA;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Random; using Content.Shared.Random;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Roles.Jobs;
using Content.Shared.Station; using Content.Shared.Station;
using Content.Shared.StatusIcon;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -41,16 +37,18 @@ namespace Content.Server.Station.Systems;
[PublicAPI] [PublicAPI]
public sealed class StationSpawningSystem : SharedStationSpawningSystem public sealed class StationSpawningSystem : SharedStationSpawningSystem
{ {
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly SharedAccessSystem _accessSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ActorSystem _actors = 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 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 IdentitySystem _identity = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly PdaSystem _pdaSystem = 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; private bool _randomizeCharacters;
@@ -73,7 +71,7 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <remarks> /// <remarks>
/// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable. /// This only spawns the character, and does none of the mind-related setup you'd need for it to be playable.
/// </remarks> /// </remarks>
public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, JobComponent? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null) public EntityUid? SpawnPlayerCharacterOnStation(EntityUid? station, ProtoId<JobPrototype>? job, HumanoidCharacterProfile? profile, StationSpawningComponent? stationSpawning = null)
{ {
if (station != null && !Resolve(station.Value, ref stationSpawning)) if (station != null && !Resolve(station.Value, ref stationSpawning))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
@@ -101,12 +99,12 @@ public sealed class StationSpawningSystem : SharedStationSpawningSystem
/// <returns>The spawned entity</returns> /// <returns>The spawned entity</returns>
public EntityUid SpawnPlayerMob( public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates, EntityCoordinates coordinates,
JobComponent? job, ProtoId<JobPrototype>? job,
HumanoidCharacterProfile? profile, HumanoidCharacterProfile? profile,
EntityUid? station, EntityUid? station,
EntityUid? entity = null) EntityUid? entity = null)
{ {
_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out var prototype); _prototypeManager.TryIndex(job ?? string.Empty, out var prototype);
RoleLoadout? loadout = null; RoleLoadout? loadout = null;
// Need to get the loadout up-front to handle names if we use an entity spawn override. // 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; return entity.Value;
} }
private void DoJobSpecials(JobComponent? job, EntityUid entity) private void DoJobSpecials(ProtoId<JobPrototype>? job, EntityUid entity)
{ {
if (!_prototypeManager.TryIndex(job?.Prototype ?? string.Empty, out JobPrototype? prototype)) if (!_prototypeManager.TryIndex(job ?? string.Empty, out JobPrototype? prototype))
return; return;
foreach (var jobSpecial in prototype.Special) foreach (var jobSpecial in prototype.Special)
@@ -269,7 +267,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// <summary> /// <summary>
/// The job to use, if any. /// The job to use, if any.
/// </summary> /// </summary>
public readonly JobComponent? Job; public readonly ProtoId<JobPrototype>? Job;
/// <summary> /// <summary>
/// The profile to use, if any. /// The profile to use, if any.
/// </summary> /// </summary>
@@ -279,7 +277,7 @@ public sealed class PlayerSpawningEvent : EntityEventArgs
/// </summary> /// </summary>
public readonly EntityUid? Station; public readonly EntityUid? Station;
public PlayerSpawningEvent(JobComponent? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station) public PlayerSpawningEvent(ProtoId<JobPrototype>? job, HumanoidCharacterProfile? humanoidCharacterProfile, EntityUid? station)
{ {
Job = job; Job = job;
HumanoidCharacterProfile = humanoidCharacterProfile; HumanoidCharacterProfile = humanoidCharacterProfile;

View File

@@ -33,16 +33,16 @@ public sealed partial class BuyerAntagCondition : ListingCondition
return true; return true;
var roleSystem = ent.System<SharedRoleSystem>(); var roleSystem = ent.System<SharedRoleSystem>();
var roles = roleSystem.MindGetAllRoles(mindId); var roles = roleSystem.MindGetAllRoleInfo(mindId);
if (Blacklist != null) if (Blacklist != null)
{ {
foreach (var role in roles) foreach (var role in roles)
{ {
if (role.Component is not AntagonistRoleComponent blacklistantag) if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue; continue;
if (blacklistantag.PrototypeId != null && Blacklist.Contains(blacklistantag.PrototypeId)) if (Blacklist.Contains(role.Prototype))
return false; return false;
} }
} }
@@ -52,10 +52,11 @@ public sealed partial class BuyerAntagCondition : ListingCondition
var found = false; var found = false;
foreach (var role in roles) foreach (var role in roles)
{ {
if (role.Component is not AntagonistRoleComponent antag)
if (!role.Antagonist || string.IsNullOrEmpty(role.Prototype))
continue; continue;
if (antag.PrototypeId != null && Whitelist.Contains(antag.PrototypeId)) if (Whitelist.Contains(role.Prototype))
found = true; found = true;
} }
if (!found) if (!found)

View File

@@ -37,13 +37,13 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
return true; return true;
var jobs = ent.System<SharedJobSystem>(); var jobs = ent.System<SharedJobSystem>();
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<DepartmentPrototype>()) foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{ {
if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) if (department.Roles.Contains(job.ID) && Blacklist.Contains(department.ID))
return false; return false;
} }
} }
@@ -52,11 +52,11 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
{ {
var found = false; var found = false;
if (job?.Prototype != null) if (job != null)
{ {
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>()) foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{ {
if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) if (department.Roles.Contains(job.ID) && Whitelist.Contains(department.ID))
{ {
found = true; found = true;
break; break;

View File

@@ -34,17 +34,17 @@ public sealed partial class BuyerJobCondition : ListingCondition
return true; return true;
var jobs = ent.System<SharedJobSystem>(); var jobs = ent.System<SharedJobSystem>();
jobs.MindTryGetJob(mindId, out var job, out _); jobs.MindTryGetJob(mindId, out var job);
if (Blacklist != null) if (Blacklist != null)
{ {
if (job?.Prototype != null && Blacklist.Contains(job.Prototype)) if (job is not null && Blacklist.Contains(job.ID))
return false; return false;
} }
if (Whitelist != null) if (Whitelist != null)
{ {
if (job?.Prototype == null || !Whitelist.Contains(job.Prototype)) if (job == null || !Whitelist.Contains(job.ID))
return false; return false;
} }

View File

@@ -11,7 +11,6 @@ using Content.Server.Mind.Commands;
using Content.Server.NPC; using Content.Server.NPC;
using Content.Server.NPC.HTN; using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems; using Content.Server.NPC.Systems;
using Content.Server.Roles;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Server.Temperature.Components; using Content.Server.Temperature.Components;
using Content.Shared.CombatMode; using Content.Shared.CombatMode;
@@ -47,18 +46,18 @@ namespace Content.Server.Zombies;
/// </remarks> /// </remarks>
public sealed partial class ZombieSystem public sealed partial class ZombieSystem
{ {
[Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ServerInventorySystem _inventory = default!; [Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly NpcFactionSystem _faction = 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 HumanoidAppearanceSystem _humanoidAppearance = default!;
[Dependency] private readonly IdentitySystem _identity = default!; [Dependency] private readonly IdentitySystem _identity = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly ServerInventorySystem _inventory = default!;
[Dependency] private readonly SharedCombatModeSystem _combat = default!;
[Dependency] private readonly IChatManager _chatMan = default!;
[Dependency] private readonly MindSystem _mind = 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 SharedRoleSystem _roles = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
/// <summary> /// <summary>
/// Handles an entity turning into a zombie when they die or go into crit /// 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)) if (hasMind && _mind.TryGetSession(mindId, out var session))
{ {
//Zombie role for player manifest //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 //Greeting message for new bebe zombers
_chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting"));

View File

@@ -14,7 +14,6 @@ public sealed partial class MindContainerComponent : Component
/// The mind controlling this mob. Can be null. /// The mind controlling this mob. Can be null.
/// </summary> /// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public EntityUid? Mind { get; set; } public EntityUid? Mind { get; set; }
/// <summary> /// <summary>
@@ -35,7 +34,6 @@ public sealed partial class MindContainerComponent : Component
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("ghostOnShutdown")] [DataField("ghostOnShutdown")]
[Access(typeof(SharedMindSystem), Other = AccessPermissions.ReadWriteExecute)] // FIXME Friends
public bool GhostOnShutdown { get; set; } = true; public bool GhostOnShutdown { get; set; } = true;
} }

View File

@@ -1,4 +1,3 @@
using Content.Shared.Actions;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.Mind.Components; using Content.Shared.Mind.Components;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -87,17 +86,21 @@ public sealed partial class MindComponent : Component
/// <summary> /// <summary>
/// Prevents user from ghosting out /// Prevents user from ghosting out
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("preventGhosting")]
public bool PreventGhosting { get; set; } public bool PreventGhosting { get; set; }
/// <summary> /// <summary>
/// Prevents user from suiciding /// Prevents user from suiciding
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("preventSuicide")]
public bool PreventSuicide { get; set; } public bool PreventSuicide { get; set; }
/// <summary>
/// Mind Role Entities belonging to this Mind
/// </summary>
[DataField, AutoNetworkedField]
public List<EntityUid> MindRoles = new List<EntityUid>();
/// <summary> /// <summary>
/// The session of the player owning this mind. /// The session of the player owning this mind.
/// Can be null, in which case the player is currently not logged in. /// Can be null, in which case the player is currently not logged in.

View File

@@ -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<AntagPrototype>))]
public string? PrototypeId;
}
/// <summary>
/// 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
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(AntagonistRoleComponent))]
public sealed partial class ExclusiveAntagonistAttribute : Attribute
{
}

View File

@@ -1,14 +0,0 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind entities to hold the data for the player's current job.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class JobComponent : Component
{
[DataField(required: true), AutoNetworkedField]
public ProtoId<JobPrototype>? Prototype;
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Roles.Jobs;
/// <summary>
/// Added to mind role entities to mark them as a job role entity.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class JobRoleComponent : BaseMindRoleComponent
{
}

View File

@@ -13,8 +13,10 @@ namespace Content.Shared.Roles.Jobs;
/// </summary> /// </summary>
public abstract class SharedJobSystem : EntitySystem public abstract class SharedJobSystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedPlayerSystem _playerSystem = default!; [Dependency] private readonly SharedPlayerSystem _playerSystem = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
private readonly Dictionary<string, string> _inverseTrackerLookup = new(); private readonly Dictionary<string, string> _inverseTrackerLookup = new();
public override void Initialize() public override void Initialize()
@@ -100,32 +102,44 @@ public abstract class SharedJobSystem : EntitySystem
public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) public bool MindHasJobWithId(EntityUid? mindId, string prototypeId)
{ {
return CompOrNull<JobComponent>(mindId)?.Prototype == prototypeId;
MindRoleComponent? comp = null;
if (mindId is null)
return false;
_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role);
if (role is null)
return false;
comp = role.Value.Comp;
return (comp.JobPrototype == prototypeId);
} }
public bool MindTryGetJob( public bool MindTryGetJob(
[NotNullWhen(true)] EntityUid? mindId, [NotNullWhen(true)] EntityUid? mindId,
[NotNullWhen(true)] out JobComponent? comp,
[NotNullWhen(true)] out JobPrototype? prototype) [NotNullWhen(true)] out JobPrototype? prototype)
{ {
comp = null;
prototype = null; prototype = null;
MindTryGetJobId(mindId, out var protoId);
return TryComp(mindId, out comp) && return (_prototypes.TryIndex<JobPrototype>(protoId, out prototype) || prototype is not null);
comp.Prototype != null &&
_prototypes.TryIndex(comp.Prototype, out prototype);
} }
public bool MindTryGetJobId([NotNullWhen(true)] EntityUid? mindId, out ProtoId<JobPrototype>? job) public bool MindTryGetJobId(
{ [NotNullWhen(true)] EntityUid? mindId,
if (!TryComp(mindId, out JobComponent? comp)) out ProtoId<JobPrototype>? job)
{ {
job = null; job = null;
return false;
}
job = comp.Prototype; if (mindId is null)
return true; return false;
if (_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role))
job = role.Value.Comp.JobPrototype;
return (job is not null);
} }
/// <summary> /// <summary>
@@ -134,7 +148,7 @@ public abstract class SharedJobSystem : EntitySystem
/// </summary> /// </summary>
public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name) 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; name = prototype.LocalizedName;
return true; return true;
@@ -161,7 +175,7 @@ public abstract class SharedJobSystem : EntitySystem
if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) if (_playerSystem.ContentData(player) is not { Mind: { } mindId })
return true; return true;
if (!MindTryGetJob(mindId, out _, out var prototype)) if (!MindTryGetJob(mindId, out var prototype))
return true; return true;
return prototype.CanBeAntag; return prototype.CanBeAntag;

View File

@@ -7,7 +7,7 @@ namespace Content.Shared.Roles;
/// </summary> /// </summary>
/// <param name="Roles">The list of roles on the player.</param> /// <param name="Roles">The list of roles on the player.</param>
[ByRefEvent] [ByRefEvent]
public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles); public readonly record struct MindGetAllRoleInfoEvent(List<RoleInfo> Roles);
/// <summary> /// <summary>
/// Returned by <see cref="MindGetAllRolesEvent"/> to give some information about a player's role. /// Returned by <see cref="MindGetAllRolesEvent"/> to give some information about a player's role.
@@ -17,4 +17,4 @@ public readonly record struct MindGetAllRolesEvent(List<RoleInfo> Roles);
/// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param> /// <param name="Antagonist">Whether or not this role makes this player an antagonist.</param>
/// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param> /// <param name="PlayTimeTrackerId">The <see cref="PlayTimeTrackerPrototype"/> id associated with the role.</param>
/// <param name="Prototype">The prototype ID of the role</param> /// <param name="Prototype">The prototype ID of the role</param>
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);

View File

@@ -0,0 +1,48 @@
using Content.Shared.Mind;
using JetBrains.Annotations;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles;
/// <summary>
/// This holds data for, and indicates, a Mind Role entity
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class MindRoleComponent : BaseMindRoleComponent
{
/// <summary>
/// Marks this Mind Role as Antagonist
/// A single antag Mind Role is enough to make the owner mind count as Antagonist.
/// </summary>
[DataField]
public bool Antag { get; set; } = false;
/// <summary>
/// True if this mindrole is an exclusive antagonist. Antag setting is not checked if this is True.
/// </summary>
[DataField]
public bool ExclusiveAntag { get; set; } = false;
/// <summary>
/// The Mind that this role belongs to
/// </summary>
public Entity<MindComponent> Mind { get; set; }
/// <summary>
/// The Antagonist prototype of this role
/// </summary>
[DataField]
public ProtoId<AntagPrototype>? AntagPrototype { get; set; }
/// <summary>
/// The Job prototype of this role
/// </summary>
[DataField]
public ProtoId<JobPrototype>? JobPrototype { get; set; }
}
public abstract partial class BaseMindRoleComponent : Component
{
}

View File

@@ -1,34 +1,31 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Administration.Logs; using Content.Shared.Administration.Logs;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Ghost.Roles; using Content.Shared.GameTicking;
using Content.Shared.Mind; using Content.Shared.Mind;
using Content.Shared.Roles.Jobs; using Content.Shared.Roles.Jobs;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Roles; namespace Content.Shared.Roles;
public abstract class SharedRoleSystem : EntitySystem public abstract class SharedRoleSystem : EntitySystem
{ {
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
// TODO please lord make role entities [Dependency] private readonly SharedGameTicker _gameTicker = default!;
private readonly HashSet<Type> _antagTypes = new(); [Dependency] private readonly IPrototypeManager _prototypes = default!;
private JobRequirementOverridePrototype? _requirementOverride; private JobRequirementOverridePrototype? _requirementOverride;
public override void Initialize() public override void Initialize()
{ {
// TODO make roles entities
SubscribeLocalEvent<JobComponent, MindGetAllRolesEvent>(OnJobGetAllRoles);
Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true);
} }
@@ -44,124 +41,117 @@ public abstract class SharedRoleSystem : EntitySystem
Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); Log.Error($"Unknown JobRequirementOverridePrototype: {value}");
} }
private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args)
{
var name = "game-ticker-unknown-role";
var prototype = "";
string? playTimeTracker = null;
if (component.Prototype != null && _prototypes.TryIndex(component.Prototype, out JobPrototype? job))
{
name = job.Name;
prototype = job.ID;
playTimeTracker = job.PlayTimeTracker;
}
name = Loc.GetString(name);
args.Roles.Add(new RoleInfo(component, name, false, playTimeTracker, prototype));
}
protected void SubscribeAntagEvents<T>() where T : AntagonistRoleComponent
{
SubscribeLocalEvent((EntityUid _, T component, ref MindGetAllRolesEvent args) =>
{
var name = "game-ticker-unknown-role";
var prototype = "";
if (component.PrototypeId != null && _prototypes.TryIndex(component.PrototypeId, out AntagPrototype? antag))
{
name = antag.Name;
prototype = antag.ID;
}
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<ExclusiveAntagonistAttribute>(out _); });
_antagTypes.Add(typeof(T));
}
public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false)
{
if (!Resolve(mindId, ref mind))
return;
EntityManager.AddComponents(mindId, components);
var antagonist = false;
foreach (var compReg in components.Values)
{
var compType = compReg.Component.GetType();
var comp = EntityManager.ComponentFactory.GetComponent(compType);
if (IsAntagonistRole(comp.GetType()))
{
antagonist = true;
break;
}
}
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
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 components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}");
}
public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false)
{
if (!Resolve(mindId, ref mind))
return;
if (HasComp(mindId, component.GetType()))
{
throw new ArgumentException($"We already have this role: {component}");
}
EntityManager.AddComponent(mindId, component);
var antagonist = IsAntagonistRole(component.GetType());
var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv);
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)}");
}
/// <summary> /// <summary>
/// Gives this mind a new role. /// Adds multiple mind roles to a mind
/// </summary> /// </summary>
/// <param name="mindId">The mind to add the role to.</param> /// <param name="mindId">The mind entity to add the role to</param>
/// <param name="component">The role instance to add.</param> /// <param name="roles">The list of mind roles to add</param>
/// <typeparam name="T">The role type to add.</typeparam> /// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">Whether or not the role should be added silently</param> /// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
/// <returns>The instance of the role.</returns> public void MindAddRoles(EntityUid mindId,
/// <exception cref="ArgumentException"> List<ProtoId<EntityPrototype>>? roles,
/// Thrown if we already have a role with this type. MindComponent? mind = null,
/// </exception> bool silent = false)
public void MindAddRole<T>(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : IComponent, new()
{ {
if (!Resolve(mindId, ref mind)) if (roles is null || roles.Count == 0)
return; return;
if (HasComp<T>(mindId)) foreach (var proto in roles)
{ {
throw new ArgumentException($"We already have this role: {typeof(T)}"); MindAddRole(mindId, proto, mind, silent);
}
} }
AddComp(mindId, component); /// <summary>
var antagonist = IsAntagonistRole<T>(); /// Adds a mind role to a mind
/// </summary>
/// <param name="mindId">The mind entity to add the role to</param>
/// <param name="protoId">The mind role to add</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
public void MindAddRole(EntityUid mindId,
ProtoId<EntityPrototype> protoId,
MindComponent? mind = null,
bool silent = false)
{
if (protoId == "MindRoleJob")
MindAddJobRole(mindId, mind, silent, "");
else
MindAddRoleDo(mindId, protoId, mind, silent);
}
/// <summary>
/// Adds a Job mind role with the specified job prototype
/// </summary>
/// /// <param name="mindId">The mind entity to add the job role to</param>
/// <param name="mind">If the mind component is provided, it will be checked if it belongs to the mind entity</param>
/// <param name="silent">If true, no briefing will be generated upon receiving the mind role</param>
/// <param name="jobPrototype">The Job prototype for the new role</param>
public void MindAddJobRole(EntityUid mindId,
MindComponent? mind = null,
bool silent = false,
string? jobPrototype = null)
{
// Can't have someone get paid for two jobs now, can we
if (MindHasRole<JobRoleComponent>(mindId, out var jobRole)
&& jobRole.Value.Comp.JobPrototype != jobPrototype)
{
Resolve(mindId, ref mind);
if (mind is not null)
{
_adminLogger.Add(LogType.Mind,
LogImpact.Low,
$"Job Role of {ToPrettyString(mind.OwnedEntity)} changed from '{jobRole.Value.Comp.JobPrototype}' to '{jobPrototype}'");
}
jobRole.Value.Comp.JobPrototype = jobPrototype;
}
else
MindAddRoleDo(mindId, "MindRoleJob", mind, silent, jobPrototype);
}
/// <summary>
/// Creates a Mind Role
/// </summary>
private void MindAddRoleDo(EntityUid mindId,
ProtoId<EntityPrototype> 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;
}
var antagonist = false;
if (!_prototypes.TryIndex(protoId, out var protoEnt))
{
Log.Error($"Failed to add role {protoId} to mind {mindId} : Role prototype does not exist");
return;
}
//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<MindRoleComponent>(mindRoleId);
var mindRoleComp = Comp<MindRoleComponent>(mindRoleId);
mindRoleComp.Mind = (mindId,mind);
if (jobPrototype is not null)
{
mindRoleComp.JobPrototype = jobPrototype;
EnsureComp<JobRoleComponent>(mindRoleId);
}
if (mindRoleComp.Antag || mindRoleComp.ExclusiveAntag)
antagonist = true;
mind.MindRoles.Add(mindRoleId);
var mindEv = new MindRoleAddedEvent(silent); var mindEv = new MindRoleAddedEvent(silent);
RaiseLocalEvent(mindId, ref mindEv); RaiseLocalEvent(mindId, ref mindEv);
@@ -172,94 +162,330 @@ public abstract class SharedRoleSystem : EntitySystem
RaiseLocalEvent(mind.OwnedEntity.Value, message, true); RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
} }
_adminLogger.Add(LogType.Mind, LogImpact.Low, var name = Loc.GetString(protoEnt.Name);
$"'Role {typeof(T).Name}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); 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)}");
}
} }
/// <summary> /// <summary>
/// Removes a role from this mind. /// Removes all instances of a specific role from this mind.
/// </summary> /// </summary>
/// <param name="mindId">The mind to remove the role from.</param> /// <param name="mindId">The mind to remove the role from.</param>
/// <typeparam name="T">The type of the role to remove.</typeparam> /// <typeparam name="T">The type of the role to remove.</typeparam>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">Thrown if the mind does not exist or does not have this role.</exception>
/// Thrown if we do not have this role. /// <returns>Returns False if there was something wrong with the mind or the removal. True if successful</returns>>
/// </exception> public bool MindRemoveRole<T>(EntityUid mindId) where T : IComponent
public void MindRemoveRole<T>(EntityUid mindId) where T : IComponent
{ {
if (!RemComp<T>(mindId)) if (!TryComp<MindComponent>(mindId, out var mind) )
throw new ArgumentException($"{mindId} does not exist or does not have mind component");
var found = false;
var antagonist = false;
var delete = new List<EntityUid>();
foreach (var role in mind.MindRoles)
{ {
throw new ArgumentException($"We do not have this role: {typeof(T)}"); if (!HasComp<T>(role))
continue;
var roleComp = Comp<MindRoleComponent>(role);
antagonist = roleComp.Antag;
_entityManager.DeleteEntity(role);
delete.Add(role);
found = true;
}
foreach (var role in delete)
{
mind.MindRoles.Remove(role);
}
if (!found)
{
throw new ArgumentException($"{mindId} does not have this role: {typeof(T)}");
} }
var mind = Comp<MindComponent>(mindId);
var antagonist = IsAntagonistRole<T>();
var message = new RoleRemovedEvent(mindId, mind, antagonist); var message = new RoleRemovedEvent(mindId, mind, antagonist);
if (mind.OwnedEntity != null) if (mind.OwnedEntity != null)
{ {
RaiseLocalEvent(mind.OwnedEntity.Value, message, true); RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
} }
_adminLogger.Add(LogType.Mind, LogImpact.Low, _adminLogger.Add(LogType.Mind,
$"'Role {typeof(T).Name}' removed from mind of {_minds.MindOwnerLoggingString(mind)}"); LogImpact.Low,
} $"'Role {typeof(T).Name}' removed from mind of {ToPrettyString(mind.OwnedEntity)}");
public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
{
if (!MindHasRole<T>(mindId))
return false;
MindRemoveRole<T>(mindId);
return true; return true;
} }
public bool MindHasRole<T>(EntityUid mindId) where T : IComponent /// <summary>
/// Finds and removes all mind roles of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to remove.</typeparam>
/// <returns>True if the role existed and was removed</returns>
public bool MindTryRemoveRole<T>(EntityUid mindId) where T : IComponent
{ {
DebugTools.Assert(HasComp<MindComponent>(mindId)); if (!MindHasRole<T>(mindId))
return HasComp<T>(mindId); {
Log.Warning($"Failed to remove role {typeof(T)} from {mindId} : mind does not have role ");
return false;
} }
public List<RoleInfo> MindGetAllRoles(EntityUid mindId) if (typeof(T) == typeof(MindRoleComponent))
{
DebugTools.Assert(HasComp<MindComponent>(mindId));
var ev = new MindGetAllRolesEvent(new List<RoleInfo>());
RaiseLocalEvent(mindId, ref ev);
return ev.Roles;
}
public bool MindIsAntagonist(EntityUid? mindId)
{
if (mindId == null)
return false; return false;
DebugTools.Assert(HasComp<MindComponent>(mindId)); return MindRemoveRole<T>(mindId);
var ev = new MindIsAntagonistEvent(); }
RaiseLocalEvent(mindId.Value, ref ev);
return ev.IsAntagonist; /// <summary>
/// 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
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <param name="role">The Mind Role entity component</param>
/// <param name="roleT">The Mind Role's entity component for T</param>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role,
[NotNullWhen(true)] out Entity<T>? roleT) where T : IComponent
{
role = null;
roleT = null;
if (!TryComp<MindComponent>(mindId, out var mind))
return false;
var found = false;
foreach (var roleEnt in mind.MindRoles)
{
if (!HasComp<T>(roleEnt))
continue;
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
roleT = (roleEnt,Comp<T>(roleEnt));
found = true;
break;
}
return found;
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// Outputs an entity component for the mind role's MindRoleComponent
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <param name="type">The Type to look for</param>
/// <param name="role">The output role</param>
/// <returns>True if the role is found</returns>
public bool MindHasRole(EntityUid mindId,
Type type,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role)
{
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"))
{
Log.Error($"Something attempted to query mind role 'MindRoleComponent' on mind {mindId}. This component is present on every single mind role.");
return false;
}
if (!TryComp<MindComponent>(mindId, out var mind))
return false;
var found = false;
foreach (var roleEnt in mind.MindRoles)
{
if (!HasComp(roleEnt, type))
continue;
role = (roleEnt,Comp<MindRoleComponent>(roleEnt));
found = true;
break;
}
return found;
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// Outputs an entity component for the mind role's MindRoleComponent
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <param name="role">The Mind Role entity component</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId,
[NotNullWhen(true)] out Entity<MindRoleComponent>? role) where T : IComponent
{
return MindHasRole<T>(mindId, out role, out _);
}
/// <summary>
/// Finds the first mind role of a specific type on a mind entity.
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <typeparam name="T">The type of the role to find.</typeparam>
/// <returns>True if the role is found</returns>
public bool MindHasRole<T>(EntityUid mindId) where T : IComponent
{
return MindHasRole<T>(mindId, out _, out _);
}
//TODO: Delete this later
/// <summary>
/// Returns the first mind role of a specific type
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>Entity Component of the mind role</returns>
[Obsolete("Use MindHasRole's output value")]
public Entity<MindRoleComponent>? MindGetRole<T>(EntityUid mindId) where T : IComponent
{
Entity<MindRoleComponent>? result = null;
var mind = Comp<MindComponent>(mindId);
foreach (var uid in mind.MindRoles)
{
if (HasComp<T>(uid) && TryComp<MindRoleComponent>(uid, out var comp))
result = (uid,comp);
}
return result;
}
/// <summary>
/// Reads all Roles of a mind Entity and returns their data as RoleInfo
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>RoleInfo list</returns>
public List<RoleInfo> MindGetAllRoleInfo(EntityUid mindId)
{
var roleInfo = new List<RoleInfo>();
if (!TryComp<MindComponent>(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<MindRoleComponent>(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;
}
/// <summary>
/// Does this mind possess an antagonist role
/// </summary>
/// <param name="mindId">The mind entity</param>
/// <returns>True if the mind possesses any antag roles</returns>
public bool MindIsAntagonist(EntityUid? mindId)
{
if (mindId is null)
{
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind entity not found");
return false;
}
return CheckAntagonistStatus(mindId.Value).Item1;
} }
/// <summary> /// <summary>
/// Does this mind possess an exclusive antagonist role /// Does this mind possess an exclusive antagonist role
/// </summary> /// </summary>
/// <param name="mindId">The mind entity</param> /// <param name="mindId">The mind entity</param>
/// <returns>True if the mind possesses an exclusive antag role</returns> /// <returns>True if the mind possesses any exclusive antag roles</returns>
public bool MindIsExclusiveAntagonist(EntityUid? mindId) 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; return false;
var ev = new MindIsAntagonistEvent();
RaiseLocalEvent(mindId.Value, ref ev);
return ev.IsExclusiveAntagonist;
} }
public bool IsAntagonistRole<T>() return CheckAntagonistStatus(mindId.Value).Item2;
{
return _antagTypes.Contains(typeof(T));
} }
public bool IsAntagonistRole(Type component) private (bool, bool) CheckAntagonistStatus(EntityUid mindId)
{ {
return _antagTypes.Contains(component); if (!TryComp<MindComponent>(mindId, out var mind))
{
Log.Warning($"Antagonist status of mind entity {mindId} could not be determined - mind component not found");
return (false, false);
}
var antagonist = false;
var exclusiveAntag = false;
foreach (var role in mind.MindRoles)
{
var roleComp = Comp<MindRoleComponent>(role);
if (roleComp.Antag || exclusiveAntag)
antagonist = true;
if (roleComp.ExclusiveAntag)
exclusiveAntag = true;
}
return (antagonist, exclusiveAntag);
} }
/// <summary> /// <summary>

View File

@@ -32,6 +32,12 @@ public sealed partial class EntityWhitelist
[DataField] public string[]? Components; [DataField] public string[]? Components;
// TODO yaml validation // TODO yaml validation
/// <summary>
/// Mind Role Prototype names that are allowed in the whitelist.
/// </summary>
[DataField] public string[]? MindRoles;
// TODO yaml validation
/// <summary> /// <summary>
/// Item sizes that are allowed in the whitelist. /// Item sizes that are allowed in the whitelist.
/// </summary> /// </summary>

View File

@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Content.Shared.Item; using Content.Shared.Item;
using Content.Shared.Roles;
using Content.Shared.Tag; using Content.Shared.Tag;
namespace Content.Shared.Whitelist; namespace Content.Shared.Whitelist;
@@ -7,6 +8,7 @@ namespace Content.Shared.Whitelist;
public sealed class EntityWhitelistSystem : EntitySystem public sealed class EntityWhitelistSystem : EntitySystem
{ {
[Dependency] private readonly IComponentFactory _factory = default!; [Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly SharedRoleSystem _roles = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
private EntityQuery<ItemComponent> _itemQuery; private EntityQuery<ItemComponent> _itemQuery;
@@ -46,9 +48,30 @@ public sealed class EntityWhitelistSystem : EntitySystem
public bool IsValid(EntityWhitelist list, EntityUid uid) public bool IsValid(EntityWhitelist list, EntityUid uid)
{ {
if (list.Components != null) if (list.Components != null)
EnsureRegistrations(list); {
var regs = StringsToRegs(list.Components);
if (list.Registrations != null) list.Registrations ??= new List<ComponentRegistration>();
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 && list.Registrations.Count > 0)
{ {
foreach (var reg in list.Registrations) foreach (var reg in list.Registrations)
{ {
@@ -162,24 +185,27 @@ public sealed class EntityWhitelistSystem : EntitySystem
return IsWhitelistFailOrNull(blacklist, uid); return IsWhitelistFailOrNull(blacklist, uid);
} }
private void EnsureRegistrations(EntityWhitelist list) private List<ComponentRegistration> StringsToRegs(string[]? input)
{ {
if (list.Components == null) var list = new List<ComponentRegistration>();
return;
list.Registrations = new List<ComponentRegistration>(); if (input == null || input.Length == 0)
foreach (var name in list.Components) return list;
foreach (var name in input)
{ {
var availability = _factory.GetComponentAvailability(name); var availability = _factory.GetComponentAvailability(name);
if (_factory.TryGetRegistration(name, out var registration) if (_factory.TryGetRegistration(name, out var registration)
&& availability == ComponentAvailability.Available) && availability == ComponentAvailability.Available)
{ {
list.Registrations.Add(registration); list.Add(registration);
} }
else if (availability == ComponentAvailability.Unknown) 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;
} }
} }

View File

@@ -4,6 +4,7 @@ nukeops-description = Nuclear operatives have targeted the station. Try to keep
nukeops-welcome = 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. 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! 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-opsmajor = [color=crimson]Syndicate major victory![/color]
nukeops-opsminor = [color=crimson]Syndicate minor victory![/color] nukeops-opsminor = [color=crimson]Syndicate minor victory![/color]

View File

@@ -176,9 +176,8 @@
min: 1 min: 1
max: 1 max: 1
pickPlayer: false pickPlayer: false
mindComponents: mindRoles:
- type: DragonRole - MindRoleDragon
prototype: Dragon
- type: entity - type: entity
parent: BaseGameRule parent: BaseGameRule
@@ -226,9 +225,8 @@
nameSegments: nameSegments:
- names_ninja_title - names_ninja_title
- names_ninja - names_ninja
mindComponents: mindRoles:
- type: NinjaRole - MindRoleNinja
prototype: SpaceNinja
- type: entity - type: entity
parent: BaseGameRule parent: BaseGameRule
@@ -439,9 +437,8 @@
- type: ZombifyOnDeath - type: ZombifyOnDeath
- type: IncurableZombie - type: IncurableZombie
- type: InitialInfected - type: InitialInfected
mindComponents: mindRoles:
- type: InitialInfectedRole - MindRoleInitialInfected
prototype: InitialInfected
- type: entity - type: entity
parent: BaseNukeopsRule parent: BaseNukeopsRule
@@ -466,7 +463,6 @@
startingGear: SyndicateLoneOperativeGearFull startingGear: SyndicateLoneOperativeGearFull
roleLoadout: roleLoadout:
- RoleSurvivalNukie - RoleSurvivalNukie
components: components:
- type: NukeOperative - type: NukeOperative
- type: RandomMetadata - type: RandomMetadata
@@ -476,9 +472,8 @@
- type: NpcFactionMember - type: NpcFactionMember
factions: factions:
- Syndicate - Syndicate
mindComponents: mindRoles:
- type: NukeopsRole - MindRoleNukeops
prototype: Nukeops
- type: entity - type: entity
parent: BaseTraitorRule parent: BaseTraitorRule
@@ -505,9 +500,8 @@
blacklist: blacklist:
components: components:
- AntagImmune - AntagImmune
mindComponents: mindRoles:
- type: TraitorRole - MindRoleTraitorSleeper
prototype: TraitorSleeper
- type: entity - type: entity
id: MassHallucinations id: MassHallucinations

View File

@@ -26,8 +26,7 @@
startingGear: ThiefGear startingGear: ThiefGear
components: components:
- type: Pacified - type: Pacified
mindComponents: mindRoles:
- type: ThiefRole - MindRoleThief
prototype: Thief
briefing: briefing:
sound: "/Audio/Misc/thief_greeting.ogg" sound: "/Audio/Misc/thief_greeting.ogg"

View File

@@ -114,9 +114,8 @@
- type: NpcFactionMember - type: NpcFactionMember
factions: factions:
- Syndicate - Syndicate
mindComponents: mindRoles:
- type: NukeopsRole - MindRoleNukeopsCommander
prototype: NukeopsCommander
- prefRoles: [ NukeopsMedic ] - prefRoles: [ NukeopsMedic ]
fallbackRoles: [ Nukeops, NukeopsCommander ] fallbackRoles: [ Nukeops, NukeopsCommander ]
spawnerPrototype: SpawnPointNukeopsMedic spawnerPrototype: SpawnPointNukeopsMedic
@@ -132,9 +131,8 @@
- type: NpcFactionMember - type: NpcFactionMember
factions: factions:
- Syndicate - Syndicate
mindComponents: mindRoles:
- type: NukeopsRole - MindRoleNukeopsMedic
prototype: NukeopsMedic
- prefRoles: [ Nukeops ] - prefRoles: [ Nukeops ]
fallbackRoles: [ NukeopsCommander, NukeopsMedic ] fallbackRoles: [ NukeopsCommander, NukeopsMedic ]
spawnerPrototype: SpawnPointNukeopsOperative spawnerPrototype: SpawnPointNukeopsOperative
@@ -152,9 +150,8 @@
- type: NpcFactionMember - type: NpcFactionMember
factions: factions:
- Syndicate - Syndicate
mindComponents: mindRoles:
- type: NukeopsRole - MindRoleNukeops
prototype: Nukeops
- type: entity - type: entity
abstract: true abstract: true
@@ -189,9 +186,8 @@
components: components:
- AntagImmune - AntagImmune
lateJoinAdditional: true lateJoinAdditional: true
mindComponents: mindRoles:
- type: TraitorRole - MindRoleTraitor
prototype: Traitor
- type: entity - type: entity
id: Revolutionary id: Revolutionary
@@ -213,9 +209,8 @@
components: components:
- type: Revolutionary - type: Revolutionary
- type: HeadRevolutionary - type: HeadRevolutionary
mindComponents: mindRoles:
- type: RevolutionaryRole - MindRoleHeadRevolutionary
prototype: HeadRev
- type: entity - type: entity
id: Sandbox id: Sandbox
@@ -257,9 +252,8 @@
- type: ZombifyOnDeath - type: ZombifyOnDeath
- type: IncurableZombie - type: IncurableZombie
- type: InitialInfected - type: InitialInfected
mindComponents: mindRoles:
- type: InitialInfectedRole - MindRoleInitialInfected
prototype: InitialInfected
# event schedulers # event schedulers

View File

@@ -9,7 +9,7 @@
issuer: objective-issuer-dragon issuer: objective-issuer-dragon
- type: RoleRequirement - type: RoleRequirement
roles: roles:
components: mindRoles:
- DragonRole - DragonRole
- type: entity - type: entity

View File

@@ -9,7 +9,7 @@
issuer: objective-issuer-spiderclan issuer: objective-issuer-spiderclan
- type: RoleRequirement - type: RoleRequirement
roles: roles:
components: mindRoles:
- NinjaRole - NinjaRole
- type: entity - type: entity

View File

@@ -7,7 +7,7 @@
issuer: objective-issuer-thief issuer: objective-issuer-thief
- type: RoleRequirement - type: RoleRequirement
roles: roles:
components: mindRoles:
- ThiefRole - ThiefRole
- type: entity - type: entity

View File

@@ -7,7 +7,7 @@
issuer: objective-issuer-syndicate issuer: objective-issuer-syndicate
- type: RoleRequirement - type: RoleRequirement
roles: roles:
components: mindRoles:
- TraitorRole - TraitorRole
- type: entity - type: entity

View File

@@ -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