diff --git a/Content.Shared/_ES/Masks/Components/ESTargetMaskBlacklistComponent.cs b/Content.Shared/_ES/Masks/Components/ESTargetMaskBlacklistComponent.cs
new file mode 100644
index 0000000000..5e879ca788
--- /dev/null
+++ b/Content.Shared/_ES/Masks/Components/ESTargetMaskBlacklistComponent.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._ES.Masks.Components;
+
+///
+/// This is used for blacklisting certain masks from targeted objectives.
+///
+[RegisterComponent]
+public sealed partial class ESTargetMaskBlacklistComponent : Component
+{
+ ///
+ /// A blacklist of masks that cannot be targeted.
+ ///
+ [DataField]
+ public HashSet> MaskBlacklist;
+}
diff --git a/Content.Shared/_ES/Masks/ESTargetMaskSystem.cs b/Content.Shared/_ES/Masks/ESTargetMaskSystem.cs
new file mode 100644
index 0000000000..376501bb83
--- /dev/null
+++ b/Content.Shared/_ES/Masks/ESTargetMaskSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared._ES.Masks.Components;
+using Content.Shared._ES.Objectives.Target.Components;
+
+namespace Content.Shared._ES.Masks;
+
+public sealed class ESTargetMaskSystem : EntitySystem
+{
+ [Dependency] private readonly ESSharedMaskSystem _mask = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(Handler);
+ }
+
+ private void Handler(Entity ent, ref ESValidateObjectiveTargetCandidates args)
+ {
+ if (_mask.GetMaskOrNull(args.Candidate) is {} mask && ent.Comp.MaskBlacklist.Contains(mask))
+ args.Invalidate();
+ }
+}
diff --git a/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskEvent.cs b/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskEvent.cs
new file mode 100644
index 0000000000..8459aaab7f
--- /dev/null
+++ b/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskEvent.cs
@@ -0,0 +1,13 @@
+using Content.Shared.Actions;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._ES.Masks.MaskCycle;
+
+///
+/// An action event for changing to another mask.
+///
+public sealed partial class ESActionChangeMaskEvent : InstantActionEvent
+{
+ [DataField(required: true)]
+ public ProtoId Mask;
+}
diff --git a/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskSystem.cs b/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskSystem.cs
new file mode 100644
index 0000000000..1538514eaa
--- /dev/null
+++ b/Content.Shared/_ES/Masks/MaskCycle/ESActionChangeMaskSystem.cs
@@ -0,0 +1,32 @@
+using Content.Shared.Mind;
+
+namespace Content.Shared._ES.Masks.MaskCycle;
+
+///
+/// This handles the mask change action.
+///
+public sealed class ESActionChangeMaskSystem : EntitySystem
+{
+ [Dependency] private readonly ESSharedMaskSystem _mask = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(Handler);
+ }
+
+ private void Handler(ESActionChangeMaskEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (!_mind.TryGetMind(args.Performer, out var mind, out var mindComp))
+ return;
+
+ _mask.RemoveMask((mind, mindComp));
+ _mask.ApplyMask((mind, mindComp), args.Mask);
+
+ args.Handled = true;
+ }
+}
diff --git a/Resources/Locale/en-US/_ES/masks/masks.ftl b/Resources/Locale/en-US/_ES/masks/masks.ftl
index f0d16002db..b6cef52db9 100644
--- a/Resources/Locale/en-US/_ES/masks/masks.ftl
+++ b/Resources/Locale/en-US/_ES/masks/masks.ftl
@@ -75,6 +75,11 @@ es-mask-subverter-desc = As a Subverter, you have received two brain-altering ch
es-mask-demolitionist-name = Demolitionist
es-mask-demolitionist-desc = As a Demolitionist, you have been trained by the Syndicate for both controlled and uncontrolled "demolitions"--including in the use of explosive implants, should things turn out that way.
+# Oddballs
+
+es-mask-turncoat-name = Turncoat
+es-mask-turncoat-desc = As a Turncoat, you play to both sides, switching teams at will in an effort to win with either the crew or the traitors. Do so carefully, as you can only switch sides so often.
+
# Meta
es-objective-issuer-mask = Mask
diff --git a/Resources/Prototypes/_ES/Actions/Masks/turncoat.yml b/Resources/Prototypes/_ES/Actions/Masks/turncoat.yml
new file mode 100644
index 0000000000..42e0208618
--- /dev/null
+++ b/Resources/Prototypes/_ES/Actions/Masks/turncoat.yml
@@ -0,0 +1,35 @@
+- type: entity
+ parent: BaseMentalAction
+ id: ESActionMaskTurncoatSwapTeamsBase
+ name: Turn your coat
+ description: Switch to the enemy's side, and try to win with them instead.
+ abstract: true
+ components:
+ - type: Action
+ checkCanInteract: false
+ icon:
+ sprite: _ES/Actions/masks/aura.rsi
+ state: aura
+ iconOn:
+ sprite: _ES/Actions/masks/aura.rsi
+ state: aura-on
+ useDelay: 10m
+ startDelay: true
+
+- type: entity
+ parent: ESActionMaskTurncoatSwapTeamsBase
+ id: ESActionMaskTurncoatSwapTeamsCrew
+ description: Switch to the crew's side, and try to win with them instead.
+ components:
+ - type: InstantAction
+ event: !type:ESActionChangeMaskEvent
+ mask: ESTurncoatCrew
+
+- type: entity
+ parent: ESActionMaskTurncoatSwapTeamsBase
+ id: ESActionMaskTurncoatSwapTeamsTraitors
+ description: Switch to the traitors' side, and try to win with them instead.
+ components:
+ - type: InstantAction
+ event: !type:ESActionChangeMaskEvent
+ mask: ESTurncoatTraitor
diff --git a/Resources/Prototypes/_ES/Masks/masks.yml b/Resources/Prototypes/_ES/Masks/masks.yml
index f440169d2b..9edad98a00 100644
--- a/Resources/Prototypes/_ES/Masks/masks.yml
+++ b/Resources/Prototypes/_ES/Masks/masks.yml
@@ -298,3 +298,28 @@
- type: ESMaskCacheSpawner
cacheProto:
id: ESCrateCacheTraitorDemolitionist
+
+# Neutral/oddball masks
+- type: esMask
+ id: ESTurncoatCrew
+ name: es-mask-turncoat-name
+ troupe: ESCrew
+ description: es-mask-turncoat-desc
+ color: gray
+ weight: 0.1
+ components:
+ - type: ActionGrant
+ actions:
+ - ESActionMaskTurncoatSwapTeamsTraitors
+
+- type: esMask
+ parent: ESBaseTraitor
+ id: ESTurncoatTraitor
+ name: es-mask-turncoat-name
+ description: es-mask-turncoat-desc
+ color: gray
+ weight: 0
+ components:
+ - type: ActionGrant
+ actions:
+ - ESActionMaskTurncoatSwapTeamsCrew
diff --git a/Resources/Prototypes/_ES/Masquerades/redcarpet.yml b/Resources/Prototypes/_ES/Masquerades/redcarpet.yml
index 6558d7cc0b..6d55dcd0f2 100644
--- a/Resources/Prototypes/_ES/Masquerades/redcarpet.yml
+++ b/Resources/Prototypes/_ES/Masquerades/redcarpet.yml
@@ -17,7 +17,7 @@
13: ["ESAssassin"] # 0 generic crew, 3 traitors.
14: ["ESEmpath"]
16: ["ESAvenger"] # 1 generic crew, 3 traitors.
- 17: ["#Reaped"]
+ 17: ["ESTurncoatCrew"]
18: ["ESArmsDealer"]
19: ["ESMarauder"] # 1 generic crew, 4 traitors.
20: ["ESVIP"]
diff --git a/Resources/Prototypes/_ES/Masquerades/traitors.yml b/Resources/Prototypes/_ES/Masquerades/traitors.yml
index 9bdd4e0aab..d6f3f2c8b8 100644
--- a/Resources/Prototypes/_ES/Masquerades/traitors.yml
+++ b/Resources/Prototypes/_ES/Masquerades/traitors.yml
@@ -17,7 +17,7 @@
13: ["ESAssassin"] # 0 generic crew, 3 traitors.
14: ["ESEmpath"]
16: ["#Firmsafes"] # 1 generic crew, 3 traitors.
- 17: ["#Reaped"]
+ 17: ["ESTurncoatCrew"]
18: ["ESArmsDealer"]
19: ["ESMarauder"] # 1 generic crew, 4 traitors.
20: ["#Softsafes"]
diff --git a/Resources/Prototypes/_ES/Objectives/traitor.yml b/Resources/Prototypes/_ES/Objectives/traitor.yml
index 6a093c8058..952a2ad23e 100644
--- a/Resources/Prototypes/_ES/Objectives/traitor.yml
+++ b/Resources/Prototypes/_ES/Objectives/traitor.yml
@@ -26,6 +26,10 @@
- type: ESTargetTroupeObjective
troupe: ESTraitor
invert: true
+ - type: ESTargetMaskBlacklist
+ maskBlacklist:
+ - ESTurncoatCrew
+ - ESTurncoatTraitor
- type: ESKillTargetObjective
### Sabotage objectives