From 9c546a0072eaf62255ce0fe25b22067260b58a8c Mon Sep 17 00:00:00 2001 From: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Date: Thu, 21 Aug 2025 03:10:07 +0200 Subject: [PATCH] Predict Mind Roles (#39611) --- .../Roles/ParadoxCloneRoleSystem.cs | 6 +- Content.Server/Roles/RoleSystem.cs | 2 +- .../EffectConditions/JobCondition.cs | 8 +-- Content.Shared/Mind/MindComponent.cs | 15 ++-- Content.Shared/Mind/SharedMindSystem.Relay.cs | 4 +- Content.Shared/Mind/SharedMindSystem.cs | 5 +- .../Roles/Components/MindRoleComponent.cs | 8 --- Content.Shared/Roles/SharedRoleSystem.cs | 68 ++++++++----------- 8 files changed, 53 insertions(+), 63 deletions(-) diff --git a/Content.Server/Roles/ParadoxCloneRoleSystem.cs b/Content.Server/Roles/ParadoxCloneRoleSystem.cs index c957692b70..85332ef4a4 100644 --- a/Content.Server/Roles/ParadoxCloneRoleSystem.cs +++ b/Content.Server/Roles/ParadoxCloneRoleSystem.cs @@ -19,11 +19,13 @@ public sealed class ParadoxCloneRoleSystem : EntitySystem private void OnRefreshNameModifiers(Entity ent, ref MindRelayedEvent args) { - if (!TryComp(ent.Owner, out var roleComp)) + var mindId = Transform(ent).ParentUid; // the mind role entity is in a container in the mind entity + + if (!TryComp(mindId, out var mindComp)) return; // only show for ghosts - if (!HasComp(roleComp.Mind.Comp.OwnedEntity)) + if (!HasComp(mindComp.OwnedEntity)) return; if (ent.Comp.NameModifier != null) diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs index 6cbd039c73..346e13bd07 100644 --- a/Content.Server/Roles/RoleSystem.cs +++ b/Content.Server/Roles/RoleSystem.cs @@ -36,7 +36,7 @@ public sealed class RoleSystem : SharedRoleSystem // 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) + foreach (var role in mindComp.MindRoleContainer.ContainedEntities) { RaiseLocalEvent(role, ref ev); } diff --git a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs index 0b942a3e09..96f3be64c6 100644 --- a/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs +++ b/Content.Shared/EntityEffects/EffectConditions/JobCondition.cs @@ -16,13 +16,13 @@ public sealed partial class JobCondition : EntityEffectCondition { args.EntityManager.TryGetComponent(args.TargetEntity, out var mindContainer); - if ( mindContainer is null - || !args.EntityManager.TryGetComponent(mindContainer.Mind, out var mind)) + if (mindContainer is null + || !args.EntityManager.TryGetComponent(mindContainer.Mind, out var mind)) return false; - foreach (var roleId in mind.MindRoles) + foreach (var roleId in mind.MindRoleContainer.ContainedEntities) { - if(!args.EntityManager.HasComponent(roleId)) + if (!args.EntityManager.HasComponent(roleId)) continue; if (!args.EntityManager.TryGetComponent(roleId, out var mindRole)) diff --git a/Content.Shared/Mind/MindComponent.cs b/Content.Shared/Mind/MindComponent.cs index 57cd628f90..efb8e45b94 100644 --- a/Content.Shared/Mind/MindComponent.cs +++ b/Content.Shared/Mind/MindComponent.cs @@ -1,8 +1,7 @@ -using Content.Shared.GameTicking; using Content.Shared.Mind.Components; +using Robust.Shared.Containers; using Robust.Shared.GameStates; using Robust.Shared.Network; -using Robust.Shared.Player; using Robust.Shared.Prototypes; namespace Content.Shared.Mind; @@ -100,10 +99,16 @@ public sealed partial class MindComponent : Component public bool PreventSuicide { get; set; } /// - /// Mind Role Entities belonging to this Mind + /// Mind Role Entities belonging to this Mind are stored in this container. /// - [DataField, AutoNetworkedField] - public List MindRoles = new List(); + [ViewVariables] + public Container MindRoleContainer = default!; + + /// + /// The id for the MindRoleContainer. + /// + [ViewVariables] + public const string MindRoleContainerId = "mind_roles"; /// /// The mind's current antagonist/special role, or lack thereof; diff --git a/Content.Shared/Mind/SharedMindSystem.Relay.cs b/Content.Shared/Mind/SharedMindSystem.Relay.cs index 60ad2fc30a..0ad18e2e08 100644 --- a/Content.Shared/Mind/SharedMindSystem.Relay.cs +++ b/Content.Shared/Mind/SharedMindSystem.Relay.cs @@ -23,7 +23,7 @@ public abstract partial class SharedMindSystem : EntitySystem { RaiseLocalEvent(mindId, ref ev); - foreach (var role in mindComp.MindRoles) + foreach (var role in mindComp.MindRoleContainer.ContainedEntities) RaiseLocalEvent(role, ref ev); } } @@ -36,7 +36,7 @@ public abstract partial class SharedMindSystem : EntitySystem { RaiseLocalEvent(mindId, ref ev); - foreach (var role in mindComp.MindRoles) + foreach (var role in mindComp.MindRoleContainer.ContainedEntities) RaiseLocalEvent(role, ref ev); } diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 98ff77810c..8906e73248 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -15,8 +15,8 @@ using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Systems; using Content.Shared.Players; using Content.Shared.Speech; - using Content.Shared.Whitelist; +using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; @@ -36,6 +36,7 @@ public abstract partial class SharedMindSystem : EntitySystem [Dependency] private readonly ISharedPlayerManager _playerManager = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; [ViewVariables] protected readonly Dictionary UserMinds = new(); @@ -64,6 +65,8 @@ public abstract partial class SharedMindSystem : EntitySystem private void OnMindStartup(EntityUid uid, MindComponent component, ComponentStartup args) { + component.MindRoleContainer = _container.EnsureContainer(uid, MindComponent.MindRoleContainerId); + if (component.UserId == null) return; diff --git a/Content.Shared/Roles/Components/MindRoleComponent.cs b/Content.Shared/Roles/Components/MindRoleComponent.cs index 45ab808192..b85ee8f5e0 100644 --- a/Content.Shared/Roles/Components/MindRoleComponent.cs +++ b/Content.Shared/Roles/Components/MindRoleComponent.cs @@ -35,14 +35,6 @@ public sealed partial class MindRoleComponent : BaseMindRoleComponent [DataField] public bool ExclusiveAntag; - /// - /// The Mind that this role belongs to. - /// - /// - /// TODO: Make this a datafield. Also components should not store other components. - /// - public Entity Mind; - /// /// The Antagonist prototype of this role. /// diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 3269680c32..d1afae9fd1 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -10,7 +10,6 @@ using Content.Shared.Whitelist; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Configuration; -using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -35,7 +34,6 @@ public abstract class SharedRoleSystem : EntitySystem { Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); - SubscribeLocalEvent(OnComponentShutdown); SubscribeLocalEvent(OnSpawn); } @@ -55,7 +53,7 @@ public abstract class SharedRoleSystem : EntitySystem return; } - if (!_prototypes.TryIndex(value, out _requirementOverride )) + if (!_prototypes.TryIndex(value, out _requirementOverride)) Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } @@ -152,22 +150,23 @@ public abstract class SharedRoleSystem : EntitySystem //If that was somehow to occur, a second mindrole for that comp would be created //Meaning any mind role checks could return wrong results, since they just return the first match they find - var mindRoleId = Spawn(protoId, MapCoordinates.Nullspace); - EnsureComp(mindRoleId); - var mindRoleComp = Comp(mindRoleId); + if (!PredictedTrySpawnInContainer(protoId, mindId, MindComponent.MindRoleContainerId, out var mindRoleId)) + { + Log.Error($"Failed to add role {protoId} to {ToPrettyString(mindId)} : Could not spawn the role entity inside the container"); + return; + } + + var mindRoleComp = EnsureComp(mindRoleId.Value); - mindRoleComp.Mind = (mindId,mind); if (jobPrototype is not null) { mindRoleComp.JobPrototype = jobPrototype; - EnsureComp(mindRoleId); + EnsureComp(mindRoleId.Value); DebugTools.AssertNull(mindRoleComp.AntagPrototype); DebugTools.Assert(!mindRoleComp.Antag); DebugTools.Assert(!mindRoleComp.ExclusiveAntag); } - mind.MindRoles.Add(mindRoleId); - var update = MindRolesUpdate((mindId, mind)); // RoleType refresh, Role time tracking, Update Admin playerlist @@ -201,13 +200,13 @@ public abstract class SharedRoleSystem : EntitySystem /// > private bool MindRolesUpdate(Entity ent) { - if(!Resolve(ent.Owner, ref ent.Comp)) + if (!Resolve(ent.Owner, ref ent.Comp)) return false; //get the most important/latest mind role var (roleType, subtype) = GetRoleTypeByTime(ent.Comp); - if (ent.Comp.RoleType == roleType && ent.Comp.Subtype == subtype) + if (ent.Comp.RoleType == roleType && ent.Comp.Subtype == subtype) return false; SetRoleType(ent.Owner, roleType, subtype); @@ -230,7 +229,7 @@ public abstract class SharedRoleSystem : EntitySystem { var roles = new List>(); - foreach (var role in mind.MindRoles) + foreach (var role in mind.MindRoleContainer.ContainedEntities) { var comp = Comp(role); if (comp.RoleType is not null) @@ -301,7 +300,7 @@ public abstract class SharedRoleSystem : EntitySystem var original = "'" + typeof(T).Name + "'"; var deleteName = original; - foreach (var role in mind.Comp.MindRoles) + foreach (var role in mind.Comp.MindRoleContainer.ContainedEntities) { if (!HasComp(role)) { @@ -351,7 +350,7 @@ public abstract class SharedRoleSystem : EntitySystem return MindRemoveRole((mindId, mind)); } - /// + /// /// Finds and removes all mind roles of a specific type /// /// The mind entity and component @@ -359,14 +358,14 @@ public abstract class SharedRoleSystem : EntitySystem /// True if the role existed and was removed public bool MindRemoveRole(Entity mind, EntProtoId protoId) { - if ( !Resolve(mind.Owner, ref mind.Comp)) + if (!Resolve(mind.Owner, ref mind.Comp)) return false; // If there were no matches and thus no mind role entity names, we'll need the protoId, to report what role failed to be removed var original = "'" + protoId + "'"; var deleteName = original; var delete = new List(); - foreach (var role in mind.Comp.MindRoles) + foreach (var role in mind.Comp.MindRoleContainer.ContainedEntities) { if (!HasComp(role)) { @@ -390,7 +389,7 @@ public abstract class SharedRoleSystem : EntitySystem /// private bool MindRemoveRoleDo(Entity mind, List delete, string? logName = "") { - if ( !Resolve(mind.Owner, ref mind.Comp)) + if (!Resolve(mind.Owner, ref mind.Comp)) return false; if (delete.Count <= 0) @@ -416,17 +415,6 @@ public abstract class SharedRoleSystem : EntitySystem return true; } - // Removing the mind role's reference on component shutdown - // to make sure the reference gets removed even if the mind role entity was deleted by outside code - private void OnComponentShutdown(Entity ent, ref ComponentShutdown args) - { - //TODO: Just ensure that the tests don't spawn unassociated mind role entities - if (ent.Comp.Mind.Comp is null) - return; - - ent.Comp.Mind.Comp.MindRoles.Remove(ent.Owner); - } - /// /// 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 @@ -442,7 +430,7 @@ public abstract class SharedRoleSystem : EntitySystem if (!Resolve(mind.Owner, ref mind.Comp)) return false; - foreach (var roleEnt in mind.Comp.MindRoles) + foreach (var roleEnt in mind.Comp.MindRoleContainer.ContainedEntities) { if (!TryComp(roleEnt, out T? tcomp)) continue; @@ -487,7 +475,7 @@ public abstract class SharedRoleSystem : EntitySystem var found = false; - foreach (var roleEnt in mind.MindRoles) + foreach (var roleEnt in mind.MindRoleContainer.ContainedEntities) { if (!HasComp(roleEnt, type)) continue; @@ -511,7 +499,7 @@ public abstract class SharedRoleSystem : EntitySystem /// public bool MindHasRole(Entity mind, EntityWhitelist whitelist) { - foreach (var roleEnt in mind.Comp.MindRoles) + foreach (var roleEnt in mind.Comp.MindRoleContainer.ContainedEntities) { if (_whitelist.IsWhitelistPass(whitelist, roleEnt)) return true; @@ -544,10 +532,10 @@ public abstract class SharedRoleSystem : EntitySystem var mind = Comp(mindId); - foreach (var uid in mind.MindRoles) + foreach (var uid in mind.MindRoleContainer.ContainedEntities) { if (HasComp(uid) && TryComp(uid, out var comp)) - result = (uid,comp); + result = (uid, comp); } return result; } @@ -564,7 +552,7 @@ public abstract class SharedRoleSystem : EntitySystem if (!Resolve(mind.Owner, ref mind.Comp)) return roleInfo; - foreach (var role in mind.Comp.MindRoles) + foreach (var role in mind.Comp.MindRoleContainer.ContainedEntities) { var valid = false; var name = "game-ticker-unknown-role"; @@ -644,14 +632,14 @@ public abstract class SharedRoleSystem : EntitySystem return CheckAntagonistStatus(mindId.Value).ExclusiveAntag; } - private (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity mind) - { - if (!Resolve(mind.Owner, ref mind.Comp)) - return (false, false); + private (bool Antag, bool ExclusiveAntag) CheckAntagonistStatus(Entity mind) + { + if (!Resolve(mind.Owner, ref mind.Comp)) + return (false, false); var antagonist = false; var exclusiveAntag = false; - foreach (var role in mind.Comp.MindRoles) + foreach (var role in mind.Comp.MindRoleContainer.ContainedEntities) { if (!TryComp(role, out var roleComp)) {