diff --git a/Content.Server/GameTicking/Rules/Components/ParadoxCloneRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ParadoxCloneRuleComponent.cs index a6ffb4e669..d0d3c1e9db 100644 --- a/Content.Server/GameTicking/Rules/Components/ParadoxCloneRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ParadoxCloneRuleComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Cloning; +using Content.Shared.Whitelist; using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules.Components; @@ -20,4 +21,23 @@ public sealed partial class ParadoxCloneRuleComponent : Component /// [DataField] public EntProtoId GibProto = "MobParadoxTimed"; + + /// + /// Mind entity of the original player. + /// Gets assigned when cloning. + /// + [DataField] + public EntityUid? Original; + + /// + /// Whitelist for Objectives to be copied to the clone. + /// + [DataField] + public EntityWhitelist? ObjectiveWhitelist; + + /// + /// Blacklist for Objectives to be copied to the clone. + /// + [DataField] + public EntityWhitelist? ObjectiveBlacklist; } diff --git a/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs b/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs index 80fad7d2ef..076479727f 100644 --- a/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ParadoxCloneRuleSystem.cs @@ -13,7 +13,6 @@ namespace Content.Server.GameTicking.Rules; public sealed class ParadoxCloneRuleSystem : GameRuleSystem { [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly CloningSystem _cloning = default!; @@ -23,6 +22,7 @@ public sealed class ParadoxCloneRuleSystem : GameRuleSystem(OnAntagSelectEntity); + SubscribeLocalEvent(AfterAntagEntitySelected); } protected override void Started(EntityUid uid, ParadoxCloneRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) @@ -45,12 +45,6 @@ public sealed class ParadoxCloneRuleSystem : GameRuleSystem ent, ref AfterAntagEntitySelectedEvent args) + { + if (ent.Comp.Original == null) + return; + + if (!_mind.TryGetMind(args.EntityUid, out var cloneMindId, out var cloneMindComp)) + return; + + _mind.CopyObjectives(ent.Comp.Original.Value, (cloneMindId, cloneMindComp), ent.Comp.ObjectiveWhitelist, ent.Comp.ObjectiveBlacklist); } } diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 0f5f9172cc..fc1be3f1ba 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Systems; using Content.Shared.Players; +using Content.Shared.Whitelist; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; @@ -26,6 +27,7 @@ public abstract class SharedMindSystem : EntitySystem [Dependency] private readonly SharedObjectivesSystem _objectives = default!; [Dependency] private readonly SharedPlayerSystem _player = default!; [Dependency] private readonly MetaDataSystem _metadata = default!; + [Dependency] private readonly EntityWhitelistSystem _whitelist = default!; [ViewVariables] protected readonly Dictionary UserMinds = new(); @@ -364,6 +366,16 @@ public abstract class SharedMindSystem : EntitySystem var title = Name(objective); _adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) removed from the mind of {MindOwnerLoggingString(mind)}"); mind.Objectives.Remove(objective); + + // garbage collection - only delete the objective entity if no mind uses it anymore + // This comes up for stuff like paradox clones where the objectives share the same entity + var mindQuery = new AllEntityQueryEnumerator(); + while (mindQuery.MoveNext(out _, out var queryComp)) + { + if (queryComp.Objectives.Contains(objective)) + return true; + } + Del(objective); return true; } @@ -395,6 +407,33 @@ public abstract class SharedMindSystem : EntitySystem return false; } + /// + /// Copies objectives from one mind to another, so that they are shared between two players. + /// + /// + /// Only copies the reference to the objective entity, not the entity itself. + /// This relies on the fact that objectives are never changed after spawning them. + /// If someone ever changes that, they will have to address this. + /// + /// mind entity of the player to copy from + /// mind entity of the player to copy to + /// whitelist for objectives that should be copied + /// blacklist for objectives that should not be copied + public void CopyObjectives(Entity source, Entity target, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null) + { + if (!Resolve(source, ref source.Comp) || !Resolve(target, ref target.Comp)) + return; + + foreach (var objective in source.Comp.Objectives) + { + if (target.Comp.Objectives.Contains(objective)) + continue; // target already has this objective + + if (_whitelist.CheckBoth(objective, blacklist, whitelist)) + AddObjective(target, target.Comp, objective); + } + } + /// /// Tries to find an objective that has the same prototype as the argument. /// diff --git a/Resources/Prototypes/Entities/Mobs/Player/clone.yml b/Resources/Prototypes/Entities/Mobs/Player/clone.yml index cdc273f667..8208c30095 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/clone.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/clone.yml @@ -27,6 +27,7 @@ - Clumsy - MindShield - MimePowers + - SpaceNinja # accents - Accentless - BackwardsAccent diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 444c5e5d09..f942cafe6c 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -244,6 +244,9 @@ reoccurrenceDelay: 20 minimumPlayers: 15 - type: ParadoxCloneRule + objectiveBlacklist: + tags: + - ParadoxCloneObjectiveBlacklist - type: AntagObjectives objectives: - ParadoxCloneKillObjective diff --git a/Resources/Prototypes/Objectives/paradoxClone.yml b/Resources/Prototypes/Objectives/paradoxClone.yml index 80aa4f15df..073c014af0 100644 --- a/Resources/Prototypes/Objectives/paradoxClone.yml +++ b/Resources/Prototypes/Objectives/paradoxClone.yml @@ -11,6 +11,9 @@ roles: mindRoles: - ParadoxCloneRole + - type: Tag + tags: + - ParadoxCloneObjectiveBlacklist # don't copy the objectives from other clones - type: entity parent: [BaseParadoxCloneObjective, BaseLivingObjective] diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 179651a113..e3dcb9b11b 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -954,6 +954,9 @@ - type: Tag id: Packet +- type: Tag + id: ParadoxCloneObjectiveBlacklist # objective entities with this tag don't get copied to paradox clones + - type: Tag id: Paper