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