diff --git a/Content.Server/GameTicking/Rules/Components/ProvocateurRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ProvocateurRuleComponent.cs new file mode 100644 index 0000000000000..926f54f9ef135 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/ProvocateurRuleComponent.cs @@ -0,0 +1,10 @@ +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// Stores data for . +/// The Provocateur is a pacifist agent focused on social manipulation and creating distrust. +/// Unlike the ninja (tech-focused sabotage), the Provocateur uses deception to turn crew against each other. +/// They have kill objectives but cannot kill directly - they must manipulate the crew into doing it. +/// +[RegisterComponent, Access(typeof(ProvocateurRuleSystem))] +public sealed partial class ProvocateurRuleComponent : Component; diff --git a/Content.Server/GameTicking/Rules/ProvocateurRuleSystem.cs b/Content.Server/GameTicking/Rules/ProvocateurRuleSystem.cs new file mode 100644 index 0000000000000..7b9500ff61411 --- /dev/null +++ b/Content.Server/GameTicking/Rules/ProvocateurRuleSystem.cs @@ -0,0 +1,55 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Roles; +using Content.Shared.Humanoid; + +namespace Content.Server.GameTicking.Rules; + +/// +/// The Provocateur is a pacifist agent focused on social manipulation and creating distrust among the crew. +/// Unlike the ninja who uses advanced tech for sabotage, the Provocateur uses deception and framing +/// to turn the crew against each other. They have kill objectives but cannot kill directly. +/// +public sealed class ProvocateurRuleSystem : GameRuleSystem +{ + [Dependency] private readonly AntagSelectionSystem _antag = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(AfterAntagSelected); + + SubscribeLocalEvent(OnGetBriefing); + } + + // Greeting upon provocateur activation + private void AfterAntagSelected(Entity mindId, ref AfterAntagEntitySelectedEvent args) + { + var ent = args.EntityUid; + _antag.SendBriefing(ent, MakeBriefing(ent), null, null); + } + + // Character screen briefing + private void OnGetBriefing(Entity role, ref GetBriefingEvent args) + { + var ent = args.Mind.Comp.OwnedEntity; + + if (ent is null) + return; + args.Append(MakeBriefing(ent.Value)); + } + + private string MakeBriefing(EntityUid ent) + { + var isHuman = HasComp(ent); + var briefing = isHuman + ? Loc.GetString("provocateur-role-greeting-human") + : Loc.GetString("provocateur-role-greeting-animal"); + + if (isHuman) + briefing += "\n \n" + Loc.GetString("provocateur-role-greeting-equipment") + "\n"; + + return briefing; + } +} diff --git a/Content.Server/Roles/ProvocateurRoleComponent.cs b/Content.Server/Roles/ProvocateurRoleComponent.cs new file mode 100644 index 0000000000000..9d356adaa766d --- /dev/null +++ b/Content.Server/Roles/ProvocateurRoleComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Roles; + +namespace Content.Server.Roles; + +/// +/// Added to mind role entities to tag that they are a provocateur. +/// +[RegisterComponent] +public sealed partial class ProvocateurRoleComponent : BaseMindRoleComponent +{ +} diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-provocateur.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-provocateur.ftl new file mode 100644 index 0000000000000..c0404102ed20b --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-provocateur.ftl @@ -0,0 +1,2 @@ +provocateur-title = Provocateur +provocateur-description = Corporate provocateurs have infiltrated the station. They cannot harm anyone directly, but they will manipulate the crew into doing their dirty work... diff --git a/Resources/Locale/en-US/provocateur/provocateur.ftl b/Resources/Locale/en-US/provocateur/provocateur.ftl new file mode 100644 index 0000000000000..b2ff0c20931f5 --- /dev/null +++ b/Resources/Locale/en-US/provocateur/provocateur.ftl @@ -0,0 +1,31 @@ +provocateur-role-greeting-human = + You are a provocateur, an agent of corporate espionage sent by a rival company. + You have been implanted with a pacifism chip - you CANNOT harm anyone directly. + Your mission is to manipulate the crew into eliminating your targets for you. + Frame the innocent. Spread rumors. Turn security against your enemies. + Impersonate authority figures. Forge evidence. Create chaos. + Your hands must stay clean, but the blood will flow nonetheless. + Remember: the best assassin never throws a punch. + +provocateur-role-greeting-animal = + You are a trained provocateur animal. + Cause confusion and distrust among the crew. + +provocateur-role-greeting-equipment = + You have been provided with tools for social manipulation: + a chameleon ID card, a voice-changing mask, and a chameleon projector. + Use these to impersonate crew and manipulate them against each other. + Your pacifism implant prevents direct violence - be creative. + +objective-issuer-rival-corp = [color=#ff8c00]Rival Corporation[/color] + +provocateur-round-end-agent-name = provocateur + +objective-condition-provocateur-description = Acquire this item to aid in your deception and social manipulation. +objective-condition-provocateur-eliminate-title = Ensure the death of {$targetName}, {$job} +objective-condition-provocateur-eliminate-head-title = Ensure the death of {$targetName}, {$job} + +roles-antag-provocateur-name = Provocateur +roles-antag-provocateur-objective = Manipulate the crew into eliminating your targets, sow chaos through deception, then escape with clean hands. + +role-subtype-provocateur = provocateur diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 6056ac98d6117..4a7182672642c 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -22,6 +22,8 @@ rules: - id: Thief prob: 0.5 + - id: Provocateur + prob: 0.3 - id: SubWizard prob: 0.05 @@ -33,6 +35,8 @@ rules: - id: Thief prob: 0.5 + - id: Provocateur + prob: 0.3 - type: entity parent: BaseGameRule diff --git a/Resources/Prototypes/GameRules/subgamemodes.yml b/Resources/Prototypes/GameRules/subgamemodes.yml index 2213623c2873c..808e983f10ee5 100644 --- a/Resources/Prototypes/GameRules/subgamemodes.yml +++ b/Resources/Prototypes/GameRules/subgamemodes.yml @@ -35,6 +35,42 @@ briefing: sound: "/Audio/Misc/thief_greeting.ogg" +# Provocateur - A pacifist agent who manipulates crew into killing their targets +# Unlike traitors (direct violence) or ninja (tech sabotage), the Provocateur +# must use social manipulation to get the crew to eliminate targets for them. +- type: entity + parent: BaseGameRule + id: Provocateur + components: + - type: ProvocateurRule + - type: AntagObjectives + objectives: + - EscapeProvocateurShuttleObjective + - type: AntagRandomObjectives + sets: + - groups: ProvocateurObjectiveGroupKill + maxPicks: 1 # One kill target - more would be too hard for a pacifist + - groups: ProvocateurObjectiveGroups + maxPicks: 2 # Two social manipulation objectives + maxDifficulty: 4.0 + - type: AntagSelection + agentName: provocateur-round-end-agent-name + selectionTime: IntraPlayerSpawn + definitions: + - prefRoles: [ Provocateur ] + max: 2 # Max 2 provocateurs - too many would make manipulation obvious + playerRatio: 25 # ~1 per 25 players, less common than traitors due to unique playstyle + lateJoinAdditional: true + multiAntagSetting: NotExclusive + startingGear: ProvocateurGear + components: + - type: Pacified + mindRoles: + - MindRoleProvocateur + briefing: + text: provocateur-role-greeting-human + color: Orange + # Needs testing - type: entity parent: BaseWizardRule diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index 50a1af174aff8..04cceea22dcd5 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -116,4 +116,50 @@ id: ThiefObjectiveGroupEscape weights: EscapeThiefShuttleObjective: 1 + +# Provocateur groups - focused on social manipulation, not tech sabotage +# These objectives are about stealing items for impersonation and framing, +# not infrastructure destruction like the ninja's doorjacking and spider bombs. +# CRITICAL: The Provocateur is PACIFIED - kill objectives require manipulating +# the crew into doing the killing for them! + +# Kill objectives - Provocateur cannot kill directly, must manipulate others +- type: weightedRandom + id: ProvocateurObjectiveGroupKill + weights: + ProvocateurKillRandomPersonObjective: 1 + ProvocateurKillRandomHeadObjective: 0.3 + +- type: weightedRandom + id: ProvocateurObjectiveGroups + weights: + ProvocateurObjectiveGroupIdentity: 1.5 + ProvocateurObjectiveGroupAuthority: 1 + ProvocateurObjectiveGroupEvidence: 1 + ProvocateurObjectiveGroupCommand: 0.3 + +- type: weightedRandom + id: ProvocateurObjectiveGroupIdentity + weights: + ProvocateurIdCollectionObjective: 1 + ProvocateurHeadsetStealObjective: 1 + +- type: weightedRandom + id: ProvocateurObjectiveGroupAuthority + weights: + ProvocateurStampCollectionObjective: 1 + ProvocateurWardenHatStealObjective: 1 + ProvocateurHeadCloakStealObjective: 1 + ProvocateurMedalStealObjective: 1 + +- type: weightedRandom + id: ProvocateurObjectiveGroupEvidence + weights: + ProvocateurForensicScannerStealObjective: 1 + ProvocateurWantedListStealObjective: 1 + +- type: weightedRandom + id: ProvocateurObjectiveGroupCommand + weights: + ProvocateurCaptainIdStealObjective: 1 #Changeling, crew, wizard, when you code it... diff --git a/Resources/Prototypes/Objectives/provocateur.yml b/Resources/Prototypes/Objectives/provocateur.yml new file mode 100644 index 0000000000000..b0cfefa31efa8 --- /dev/null +++ b/Resources/Prototypes/Objectives/provocateur.yml @@ -0,0 +1,191 @@ +# Provocateur objectives +# The Provocateur is a unique antagonist focused on social manipulation. +# Unlike the Ninja (tech-focused sabotage with special gadgets), +# the Provocateur uses cunning, deception, and impersonation. +# CRITICAL: The Provocateur is PACIFIED and cannot kill directly. +# They must manipulate the crew into killing their targets for them. + +- type: entity + abstract: true + parent: BaseObjective + id: BaseProvocateurObjective + components: + - type: Objective + issuer: objective-issuer-rival-corp + - type: RoleRequirement + roles: + - ProvocateurRole + +- type: entity + abstract: true + parent: [BaseProvocateurObjective, BaseStealObjective] + id: BaseProvocateurStealObjective + components: + - type: StealCondition + verifyMapExistence: false + descriptionText: objective-condition-provocateur-description + +# Kill objectives - the Provocateur cannot kill directly due to pacifism +# They must frame targets, spread rumors, or manipulate security into eliminating them +- type: entity + abstract: true + parent: [BaseProvocateurObjective, BaseKillObjective] + id: BaseProvocateurKillObjective + components: + - type: Objective + icon: + sprite: Objects/Misc/bureaucracy.rsi + state: folder-white + +# Ensure a random person dies - you can't kill them yourself! +- type: entity + parent: BaseProvocateurKillObjective + id: ProvocateurKillRandomPersonObjective + description: This person needs to die, but your hands must stay clean. Frame them, turn the crew against them, or arrange an "accident" - just don't get caught. + components: + - type: Objective + difficulty: 2.5 + unique: false + - type: TargetObjective + title: objective-condition-provocateur-eliminate-title + - type: PickRandomPerson + - type: KillPersonCondition + requireDead: true + +# Ensure a head of staff dies - harder target, cleaner required +- type: entity + parent: BaseProvocateurKillObjective + id: ProvocateurKillRandomHeadObjective + description: This head of staff is a threat to our interests. Manipulate the crew or security into eliminating them. Your pacifism implant prevents direct action. + components: + - type: Objective + difficulty: 3.5 + unique: true + - type: TargetObjective + title: objective-condition-provocateur-eliminate-head-title + - type: PickRandomHead + - type: KillPersonCondition + requireDead: true + +# Escape objective for Provocateur +- type: entity + parent: [BaseProvocateurObjective, BaseLivingObjective] + id: EscapeProvocateurShuttleObjective + name: Escape to centcomm alive and unrestrained. + description: Your corporate sponsors need you alive to debrief. Get out while the station is in chaos. + components: + - type: Objective + difficulty: 1.3 + icon: + sprite: Structures/Furniture/chairs.rsi + state: shuttle + - type: EscapeShuttleCondition + +# Social manipulation objectives - steal items that help frame others or cause distrust +# These are fundamentally different from ninja objectives (doorjacking, research stealing, bombs) + +## Steal identification items to impersonate crew +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurIdCollectionObjective + components: + - type: StealCondition + stealGroup: IDCard + minCollectionSize: 3 + maxCollectionSize: 5 + verifyMapExistence: false + - type: Objective + difficulty: 1.0 + +## Steal stamps to forge documents +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurStampCollectionObjective + components: + - type: StealCondition + stealGroup: Stamp + minCollectionSize: 3 + maxCollectionSize: 5 + - type: Objective + difficulty: 0.8 + +## Steal department keys for impersonation +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurHeadsetStealObjective + components: + - type: NotJobRequirement + job: Captain + - type: StealCondition + stealGroup: ClothingHeadsetAltMedical + - type: Objective + difficulty: 1.2 + +## Steal evidence planting materials (forensic tools) +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurForensicScannerStealObjective + components: + - type: NotJobRequirement + job: Detective + - type: StealCondition + stealGroup: ForensicScanner + - type: Objective + difficulty: 1.0 + +## Steal the warden's hat - a symbol of authority +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurWardenHatStealObjective + components: + - type: StealCondition + stealGroup: ClothingHeadHatWarden + - type: Objective + difficulty: 1.5 + +## Steal the Captain's ID - ultimate impersonation +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurCaptainIdStealObjective + components: + - type: NotJobRequirement + job: Captain + - type: StealCondition + stealGroup: CaptainIDCard + - type: Objective + difficulty: 2.5 + +## Steal medals to discredit heroes +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurMedalStealObjective + components: + - type: StealCondition + stealGroup: ClothingNeckGoldmedal + - type: Objective + difficulty: 1.2 + +## Steal criminal records cartridge to manipulate records +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurWantedListStealObjective + components: + - type: NotJobRequirement + job: SecurityOfficer + - type: StealCondition + stealGroup: WantedListCartridge + - type: Objective + difficulty: 1.0 + +## Steal head cloaks to impersonate leadership +- type: entity + parent: BaseProvocateurStealObjective + id: ProvocateurHeadCloakStealObjective + components: + - type: StealCondition + stealGroup: HeadCloak + minCollectionSize: 2 + maxCollectionSize: 3 + - type: Objective + difficulty: 1.5 + diff --git a/Resources/Prototypes/Roles/Antags/provocateur.yml b/Resources/Prototypes/Roles/Antags/provocateur.yml new file mode 100644 index 0000000000000..dad95cd45f297 --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/provocateur.yml @@ -0,0 +1,26 @@ +- type: antag + id: Provocateur + name: roles-antag-provocateur-name + antagonist: true + setPreference: true + objective: roles-antag-provocateur-objective + requirements: + - !type:OverallPlaytimeRequirement + # 2h requirement - higher than traitor because this role requires understanding + # of station dynamics, social systems, and creative problem-solving to succeed + # as a pacifist who must manipulate others into killing their targets + time: 7200 + +# Provocateur gear - focused on social manipulation, not tech sabotage +# Unlike the ninja who has advanced tech (stealth suit, gloves, katana), +# the Provocateur uses mundane but cunning tools for social manipulation +- type: startingGear + id: ProvocateurGear + storage: + back: + - AgentIDCard + - ClothingMaskGasVoiceChameleon + - ChameleonProjector + - Paper + - Pen + - HandLabeler diff --git a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml index 79223ed67da2a..30814db480116 100644 --- a/Resources/Prototypes/Roles/MindRoles/mind_roles.yml +++ b/Resources/Prototypes/Roles/MindRoles/mind_roles.yml @@ -242,6 +242,17 @@ subtype: role-subtype-thief - type: ThiefRole +# Provocateur +- type: entity + parent: BaseMindRoleAntag + id: MindRoleProvocateur + name: Provocateur Role + components: + - type: MindRole + antagPrototype: Provocateur + subtype: role-subtype-provocateur + - type: ProvocateurRole + # Traitors - type: entity parent: BaseMindRoleAntag diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 160bb5e4a08ba..b3ab2fc1ea8ff 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -159,6 +159,23 @@ - SpaceTrafficControlEventScheduler - BasicRoundstartVariation +- type: gamePreset + id: Provocateur + alias: + - provocateur + - provo + name: provocateur-title + description: provocateur-description + showInVote: false + rules: + - DummyNonAntagChance + - Provocateur + - SubGamemodesRule + - BasicStationEventScheduler + - MeteorSwarmScheduler + - SpaceTrafficControlEventScheduler + - BasicRoundstartVariation + - type: gamePreset id: Deathmatch alias: