diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index acd2a3ffde..9e8a878a2d 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -42,7 +42,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem 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 ent, ref AfterAntagEntitySelectedEvent args) @@ -50,17 +50,23 @@ public sealed class TraitorRuleSystem : GameRuleSystem 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) diff --git a/Content.Server/Traitor/Components/TraitorCodePaperComponent.cs b/Content.Server/Traitor/Components/TraitorCodePaperComponent.cs new file mode 100644 index 0000000000..7887248f21 --- /dev/null +++ b/Content.Server/Traitor/Components/TraitorCodePaperComponent.cs @@ -0,0 +1,27 @@ +namespace Content.Server.Traitor.Components; + +/// +/// Paper with written traitor codewords on it. +/// +[RegisterComponent] +public sealed partial class TraitorCodePaperComponent : Component +{ + /// + /// The number of codewords that should be generated on this paper. + /// Will not extend past the max number of available codewords. + /// + [DataField] + public int CodewordAmount = 1; + + /// + /// Whether the codewords should be faked if there is no traitor gamerule set. + /// + [DataField] + public bool FakeCodewords = true; + + /// + /// Whether all codewords added to the round should be used. Overrides CodewordAmount if true. + /// + [DataField] + public bool CodewordShowAll = false; +} diff --git a/Content.Server/Traitor/Systems/TraitorCodePaperSystem.cs b/Content.Server/Traitor/Systems/TraitorCodePaperSystem.cs new file mode 100644 index 0000000000..72baa2c91d --- /dev/null +++ b/Content.Server/Traitor/Systems/TraitorCodePaperSystem.cs @@ -0,0 +1,93 @@ +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(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(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 codeList = new(); + // Find the first nuke that matches the passed location. + if (_gameTicker.IsGameRuleAdded()) + { + 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; + } +} diff --git a/Resources/Locale/en-US/thief/backpack.ftl b/Resources/Locale/en-US/thief/backpack.ftl index 0911bc042b..6a69ab8bc6 100644 --- a/Resources/Locale/en-US/thief/backpack.ftl +++ b/Resources/Locale/en-US/thief/backpack.ftl @@ -18,7 +18,8 @@ thief-backpack-button-deselect = Select [X] 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 @@ -38,8 +39,8 @@ thief-backpack-category-syndie-name = syndie 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 = diff --git a/Resources/Locale/en-US/traitor/traitor-codes-component.ftl b/Resources/Locale/en-US/traitor/traitor-codes-component.ftl new file mode 100644 index 0000000000..35246037f6 --- /dev/null +++ b/Resources/Locale/en-US/traitor/traitor-codes-component.ftl @@ -0,0 +1,3 @@ +traitor-codes-message-singular = syndicate codeword: +traitor-codes-message-plural = syndicate codewords: +traitor-codes-none = no known codewords diff --git a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml index 322fd9261b..fe1a4fe75c 100644 --- a/Resources/Prototypes/Catalog/thief_toolbox_sets.yml +++ b/Resources/Prototypes/Catalog/thief_toolbox_sets.yml @@ -16,6 +16,7 @@ - ClothingShoesChameleon - BarberScissors - ChameleonProjector + - AgentIDCard - type: thiefBackpackSet id: ToolsSet @@ -57,9 +58,10 @@ 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 diff --git a/Resources/Prototypes/Entities/Objects/Misc/paper.yml b/Resources/Prototypes/Entities/Objects/Misc/paper.yml index edd4aee3ba..617db53c4e 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/paper.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/paper.yml @@ -565,6 +565,26 @@ - 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