diff --git a/Content.Server/Objectives/Components/TargetObjectiveImmuneComponent.cs b/Content.Server/Objectives/Components/TargetObjectiveImmuneComponent.cs
new file mode 100644
index 00000000000..1f2bc961fd0
--- /dev/null
+++ b/Content.Server/Objectives/Components/TargetObjectiveImmuneComponent.cs
@@ -0,0 +1,9 @@
+namespace Content.Server.Objectives.Components;
+
+///
+/// Use this to mark a player as immune to any target objectives, useful for ghost roles or events.
+///
+[RegisterComponent]
+public sealed partial class TargetObjectiveImmuneComponent : Component
+{
+}
diff --git a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs
index 8dcbf191b36..d61310908c3 100644
--- a/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs
+++ b/Content.Server/Objectives/Systems/KillPersonConditionSystem.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Server.Objectives.Components;
using Content.Server.Revolutionary.Components;
using Content.Server.Shuttles.Systems;
@@ -25,9 +26,7 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnGetProgress);
-
SubscribeLocalEvent(OnPersonAssigned);
-
SubscribeLocalEvent(OnHeadAssigned);
}
@@ -41,29 +40,15 @@ private void OnGetProgress(EntityUid uid, KillPersonConditionComponent comp, ref
private void OnPersonAssigned(EntityUid uid, PickRandomPersonComponent comp, ref ObjectiveAssignedEvent args)
{
- // invalid objective prototype
- if (!TryComp(uid, out var target))
- {
- args.Cancelled = true;
- return;
- }
-
- // target already assigned
- if (target.Target != null)
- return;
-
- // no other humans to kill
- var allHumans = _mind.GetAliveHumansExcept(args.MindId);
- if (allHumans.Count == 0)
- {
- args.Cancelled = true;
- return;
- }
-
- _target.SetTarget(uid, _random.Pick(allHumans), target);
+ AssignRandomTarget(uid, args, _ => true);
}
private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ AssignRandomTarget(uid, args, mind => HasComp(uid));
+ }
+
+ private void AssignRandomTarget(EntityUid uid, ObjectiveAssignedEvent args, Predicate filter, bool fallbackToAny = true)
{
// invalid prototype
if (!TryComp(uid, out var target))
@@ -76,25 +61,30 @@ private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref Obj
if (target.Target != null)
return;
- // no other humans to kill
- var allHumans = _mind.GetAliveHumansExcept(args.MindId);
- if (allHumans.Count == 0)
+ // Get all alive humans, filter out any with TargetObjectiveImmuneComponent
+ var allHumans = _mind.GetAliveHumansExcept(args.MindId)
+ .Where(mindId =>
+ {
+ if (!TryComp(mindId, out var mindComp) || mindComp.OwnedEntity == null)
+ return false;
+ return !HasComp(mindComp.OwnedEntity.Value);
+ })
+ .ToList();
+
+ // Filter out targets based on the filter
+ var filteredHumans = allHumans.Where(mind => filter(mind)).ToList();
+
+ // There's no humans and we can't fall back to any other target
+ if (filteredHumans.Count == 0 && !fallbackToAny)
{
args.Cancelled = true;
return;
}
- var allHeads = new List();
- foreach (var person in allHumans)
- {
- if (TryComp(person, out var mind) && mind.OwnedEntity is { } ent && HasComp(ent))
- allHeads.Add(person);
- }
-
- if (allHeads.Count == 0)
- allHeads = allHumans; // fallback to non-head target
+ // Pick between humans matching our filter or fall back to all humans alive
+ var selectedHumans = filteredHumans.Count > 0 ? filteredHumans : allHumans;
- _target.SetTarget(uid, _random.Pick(allHeads), target);
+ _target.SetTarget(uid, _random.Pick(selectedHumans), target);
}
private float GetProgress(EntityUid target, bool requireDead)