protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
{
base.Added(uid, component, gameRule, args);
- MakeCodewords(component);
+ SetCodewords(component);
}
private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
MakeTraitor(args.EntityUid, ent);
}
- private void MakeCodewords(TraitorRuleComponent component)
+ private void SetCodewords(TraitorRuleComponent component)
+ {
+ component.Codewords = GenerateTraitorCodewords(component);
+ }
+
+ public string[] GenerateTraitorCodewords(TraitorRuleComponent component)
{
var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values;
var verbs = _prototypeManager.Index(component.CodewordVerbs).Values;
var codewordPool = adjectives.Concat(verbs).ToList();
var finalCodewordCount = Math.Min(component.CodewordCount, codewordPool.Count);
- component.Codewords = new string[finalCodewordCount];
+ string[] codewords = new string[finalCodewordCount];
for (var i = 0; i < finalCodewordCount; i++)
{
- component.Codewords[i] = _random.PickAndTake(codewordPool);
+ codewords[i] = _random.PickAndTake(codewordPool);
}
+ return codewords;
}
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true)
--- /dev/null
+namespace Content.Server.Traitor.Components;
+
+/// <summary>
+/// Paper with written traitor codewords on it.
+/// </summary>
+[RegisterComponent]
+public sealed partial class TraitorCodePaperComponent : Component
+{
+ /// <summary>
+ /// The number of codewords that should be generated on this paper.
+ /// Will not extend past the max number of available codewords.
+ /// </summary>
+ [DataField]
+ public int CodewordAmount = 1;
+
+ /// <summary>
+ /// Whether the codewords should be faked if there is no traitor gamerule set.
+ /// </summary>
+ [DataField]
+ public bool FakeCodewords = true;
+
+ /// <summary>
+ /// Whether all codewords added to the round should be used. Overrides CodewordAmount if true.
+ /// </summary>
+ [DataField]
+ public bool CodewordShowAll = false;
+}
--- /dev/null
+using System.Diagnostics.CodeAnalysis;
+using Content.Server.GameTicking;
+using Content.Server.GameTicking.Rules;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Paper;
+using Content.Server.Traitor.Components;
+using Robust.Shared.Random;
+using Robust.Shared.Utility;
+using System.Linq;
+
+namespace Content.Server.Traitor.Systems;
+
+public sealed class TraitorCodePaperSystem : EntitySystem
+{
+ [Dependency] private readonly GameTicker _gameTicker = default!;
+ [Dependency] private readonly TraitorRuleSystem _traitorRuleSystem = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly PaperSystem _paper = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent<TraitorCodePaperComponent, MapInitEvent>(OnMapInit);
+ }
+
+ private void OnMapInit(EntityUid uid, TraitorCodePaperComponent component, MapInitEvent args)
+ {
+ SetupPaper(uid, component);
+ }
+
+ private void SetupPaper(EntityUid uid, TraitorCodePaperComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (HasComp<PaperComponent>(uid))
+ {
+ if (TryGetTraitorCode(out var paperContent, component))
+ {
+ _paper.SetContent(uid, paperContent);
+ }
+ }
+ }
+
+ private bool TryGetTraitorCode([NotNullWhen(true)] out string? traitorCode, TraitorCodePaperComponent component)
+ {
+ traitorCode = null;
+
+ var codesMessage = new FormattedMessage();
+ List<string> codeList = new();
+ // Find the first nuke that matches the passed location.
+ if (_gameTicker.IsGameRuleAdded<TraitorRuleComponent>())
+ {
+ var ruleEnts = _gameTicker.GetAddedGameRules();
+ foreach (var ruleEnt in ruleEnts)
+ {
+ if (TryComp(ruleEnt, out TraitorRuleComponent? traitorComp))
+ {
+ codeList.AddRange(traitorComp.Codewords.ToList());
+ }
+ }
+ }
+ if (codeList.Count == 0)
+ {
+ if (component.FakeCodewords)
+ codeList = _traitorRuleSystem.GenerateTraitorCodewords(new TraitorRuleComponent()).ToList();
+ else
+ codeList = [Loc.GetString("traitor-codes-none")];
+ }
+
+ _random.Shuffle(codeList);
+
+ int i = 0;
+ foreach (var code in codeList)
+ {
+ i++;
+ if (i > component.CodewordAmount && !component.CodewordShowAll)
+ break;
+
+ codesMessage.PushNewline();
+ codesMessage.AddMarkup(code);
+ }
+
+ if (!codesMessage.IsEmpty)
+ {
+ if (i == 1)
+ traitorCode = Loc.GetString("traitor-codes-message-singular") + codesMessage;
+ else
+ traitorCode = Loc.GetString("traitor-codes-message-plural") + codesMessage;
+ }
+ return !codesMessage.IsEmpty;
+ }
+}
thief-backpack-category-chameleon-name = chameleon kit
thief-backpack-category-chameleon-description =
You are everyone and no one; you are a master of disguise.
- Includes: Set of chameleon clothing, a chameleon projector.
+ Includes: A full set of chameleon clothing,
+ a chameleon projector, and an Agent ID.
Disguise as anyone and anything.
thief-backpack-category-tools-name = breacher kit
thief-backpack-category-syndie-description =
Trinkets from a disavowed past, or stolen from a careless agent?
You've made some connections. Whiskey, echo...
- Includes: Agent ID card, Emag, syndicate pAI, Interdyne cigs,
- and some strange red crystals.
+ Includes: An Emag, Interdyne cigs, a Syndicate codeword,
+ a Radio Jammer, a lighter and some strange red crystals.
thief-backpack-category-sleeper-name = sleeper kit
thief-backpack-category-sleeper-description =
--- /dev/null
+traitor-codes-message-singular = syndicate codeword:
+traitor-codes-message-plural = syndicate codewords:
+traitor-codes-none = no known codewords
- ClothingShoesChameleon
- BarberScissors
- ChameleonProjector
+ - AgentIDCard
- type: thiefBackpackSet
id: ToolsSet
sprite: Objects/Specific/Syndicate/telecrystal.rsi
state: telecrystal
content:
- - AgentIDCard
+ - RadioJammer
+ - TraitorCodePaper
- Emag
- - SyndicatePersonalAI
+ - Lighter
- CigPackSyndicate
- Telecrystal10 #The thief cannot use them, but it may induce communication with traitors
- type: StealTarget
stealGroup: BoxFolderQmClipboard
+- type: entity
+ parent: Paper
+ id: TraitorCodePaper
+ name: syndicate codeword
+ description: A leaked codeword to possibly get in touch with the Syndicate.
+ suffix: DO NOT MAP
+ components:
+ - type: TraitorCodePaper
+
+- type: entity
+ parent: Paper
+ id: AllTraitorCodesPaper
+ name: syndicate codewords registry
+ description: A registry of all active Syndicate codewords.
+ suffix: Admeme
+ components:
+ - type: TraitorCodePaper
+ fakeCodewords: false
+ codewordShowAll: true
+
- type: entity
name: envelope
parent: BaseItem