Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Content.Server.GameTicking.Rules.Components;

/// <summary>
/// Stores data for <see cref="ProvocateurRuleSystem"/>.
/// 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.
/// </summary>
[RegisterComponent, Access(typeof(ProvocateurRuleSystem))]
public sealed partial class ProvocateurRuleComponent : Component;
55 changes: 55 additions & 0 deletions Content.Server/GameTicking/Rules/ProvocateurRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// 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.
/// </summary>
public sealed class ProvocateurRuleSystem : GameRuleSystem<ProvocateurRuleComponent>
{
[Dependency] private readonly AntagSelectionSystem _antag = default!;

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ProvocateurRuleComponent, AfterAntagEntitySelectedEvent>(AfterAntagSelected);

SubscribeLocalEvent<ProvocateurRoleComponent, GetBriefingEvent>(OnGetBriefing);
}

// Greeting upon provocateur activation
private void AfterAntagSelected(Entity<ProvocateurRuleComponent> mindId, ref AfterAntagEntitySelectedEvent args)
{
var ent = args.EntityUid;
_antag.SendBriefing(ent, MakeBriefing(ent), null, null);
}

// Character screen briefing
private void OnGetBriefing(Entity<ProvocateurRoleComponent> 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<HumanoidAppearanceComponent>(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;
}
}
11 changes: 11 additions & 0 deletions Content.Server/Roles/ProvocateurRoleComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Content.Shared.Roles;

namespace Content.Server.Roles;

/// <summary>
/// Added to mind role entities to tag that they are a provocateur.
/// </summary>
[RegisterComponent]
public sealed partial class ProvocateurRoleComponent : BaseMindRoleComponent
{
}
Original file line number Diff line number Diff line change
@@ -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...
31 changes: 31 additions & 0 deletions Resources/Locale/en-US/provocateur/provocateur.ftl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions Resources/Prototypes/GameRules/roundstart.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
rules:
- id: Thief
prob: 0.5
- id: Provocateur
prob: 0.3
- id: SubWizard
prob: 0.05

Expand All @@ -33,6 +35,8 @@
rules:
- id: Thief
prob: 0.5
- id: Provocateur
prob: 0.3

- type: entity
parent: BaseGameRule
Expand Down
36 changes: 36 additions & 0 deletions Resources/Prototypes/GameRules/subgamemodes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions Resources/Prototypes/Objectives/objectiveGroups.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Loading
Loading