_prototypeManager.RegisterIgnore("alertLevels");
_prototypeManager.RegisterIgnore("nukeopsRole");
_prototypeManager.RegisterIgnore("ghostRoleRaffleDecider");
+ _prototypeManager.RegisterIgnore("codewordGenerator");
+ _prototypeManager.RegisterIgnore("codewordFaction");
_componentFactory.GenerateNetIds();
_adminManager.Initialize();
--- /dev/null
+namespace Content.Server.Codewords;
+
+/// <summary>
+/// Container for generated codewords.
+/// </summary>
+[RegisterComponent, Access(typeof(CodewordSystem))]
+public sealed partial class CodewordComponent : Component
+{
+ /// <summary>
+ /// The codewords that were generated.
+ /// </summary>
+ [DataField]
+ public string[] Codewords = [];
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Codewords;
+
+/// <summary>
+/// This is a prototype for easy access to codewords using identifiers instead of magic strings.
+/// </summary>
+[Prototype]
+public sealed partial class CodewordFactionPrototype : IPrototype
+{
+ /// <inheritdoc/>
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ /// <summary>
+ /// The generator to use for this faction.
+ /// </summary>
+ [DataField(required:true)]
+ public ProtoId<CodewordGeneratorPrototype> Generator { get; } = default!;
+}
--- /dev/null
+using Content.Shared.Dataset;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Codewords;
+
+/// <summary>
+/// This is a prototype for specifying codeword generation
+/// </summary>
+[Prototype]
+public sealed partial class CodewordGeneratorPrototype : IPrototype
+{
+ /// <inheritdoc/>
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ /// <summary>
+ /// List of datasets to use for word generation. All values will be concatenated into one list and then randomly chosen from
+ /// </summary>
+ [DataField]
+ public List<ProtoId<LocalizedDatasetPrototype>> Words { get; } =
+ [
+ "Adjectives",
+ "Verbs",
+ ];
+
+
+ /// <summary>
+ /// How many codewords should be generated?
+ /// </summary>
+ [DataField]
+ public int Amount = 3;
+}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Codewords;
+
+/// <summary>
+/// Component that defines <see cref="CodewordGeneratorPrototype"/> to use and keeps track of generated codewords.
+/// </summary>
+[RegisterComponent, Access(typeof(CodewordSystem))]
+public sealed partial class CodewordManagerComponent : Component
+{
+ /// <summary>
+ /// The generated codewords. The value contains the entity that has the <see cref="CodewordComponent"/>
+ /// </summary>
+ [DataField]
+ [ViewVariables(VVAccess.ReadOnly)]
+ public Dictionary<ProtoId<CodewordFactionPrototype>, EntityUid> Codewords = new();
+}
--- /dev/null
+using System.Linq;
+using Content.Server.Administration.Logs;
+using Content.Server.GameTicking.Events;
+using Content.Shared.Database;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.Codewords;
+
+/// <summary>
+/// Gamerule that provides codewords for other gamerules that rely on them.
+/// </summary>
+public sealed class CodewordSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
+ }
+
+ private void OnRoundStart(RoundStartingEvent ev)
+ {
+ var manager = Spawn();
+ AddComp<CodewordManagerComponent>(manager);
+ }
+
+ /// <summary>
+ /// Retrieves codewords for the faction specified.
+ /// </summary>
+ public string[] GetCodewords(ProtoId<CodewordFactionPrototype> faction)
+ {
+ var query = EntityQueryEnumerator<CodewordManagerComponent>();
+ while (query.MoveNext(out _, out var manager))
+ {
+ if (!manager.Codewords.TryGetValue(faction, out var codewordEntity))
+ return GenerateForFaction(faction, ref manager);
+
+ return Comp<CodewordComponent>(codewordEntity).Codewords;
+ }
+
+ Log.Warning("Codeword system not initialized. Returning empty array.");
+ // While throwing in this situation would be cool, that causes a test fail (in SpawnAndDeleteEntityCountTest)
+ // as the traitor codewords paper gets spawned in and calls this method,
+ // but the "start round" event never gets called in this test case.
+ return [];
+ }
+
+ private string[] GenerateForFaction(ProtoId<CodewordFactionPrototype> faction, ref CodewordManagerComponent manager)
+ {
+ var factionProto = _prototypeManager.Index<CodewordFactionPrototype>(faction.Id);
+
+ var codewords = GenerateCodewords(factionProto.Generator);
+ var codewordsContainer = EntityManager.Spawn(protoName:null, MapCoordinates.Nullspace);
+ EnsureComp<CodewordComponent>(codewordsContainer)
+ .Codewords = codewords;
+ manager.Codewords[faction] = codewordsContainer;
+ _adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for faction {faction}: {string.Join(", ", codewords)}");
+
+ return codewords;
+ }
+
+ /// <summary>
+ /// Generates codewords as specified by the <see cref="CodewordGeneratorPrototype"/> codeword generator.
+ /// </summary>
+ public string[] GenerateCodewords(ProtoId<CodewordGeneratorPrototype> generatorId)
+ {
+ var generator = _prototypeManager.Index(generatorId);
+
+ var codewordPool = new List<string>();
+ foreach (var dataset in generator.Words
+ .Select(datasetPrototype => _prototypeManager.Index(datasetPrototype)))
+ {
+ codewordPool.AddRange(dataset.Values);
+ }
+
+ var finalCodewordCount = Math.Min(generator.Amount, codewordPool.Count);
+ var codewords = new string[finalCodewordCount];
+ for (var i = 0; i < finalCodewordCount; i++)
+ {
+ codewords[i] = Loc.GetString(_random.PickAndTake(codewordPool));
+ }
+ return codewords;
+ }
+}
+using Content.Server.Codewords;
using Content.Shared.Dataset;
using Content.Shared.FixedPoint;
-using Content.Shared.NPC.Prototypes;
+using Content.Shared.NPC.Prototypes;
using Content.Shared.Random;
using Content.Shared.Roles;
using Robust.Shared.Audio;
public ProtoId<AntagPrototype> TraitorPrototypeId = "Traitor";
[DataField]
- public ProtoId<NpcFactionPrototype> NanoTrasenFaction = "NanoTrasen";
+ public ProtoId<CodewordFactionPrototype> CodewordFactionPrototypeId = "Traitor";
[DataField]
- public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
-
- [DataField]
- public ProtoId<LocalizedDatasetPrototype> CodewordAdjectives = "Adjectives";
+ public ProtoId<NpcFactionPrototype> NanoTrasenFaction = "NanoTrasen";
[DataField]
- public ProtoId<LocalizedDatasetPrototype> CodewordVerbs = "Verbs";
+ public ProtoId<NpcFactionPrototype> SyndicateFaction = "Syndicate";
[DataField]
public ProtoId<LocalizedDatasetPrototype> ObjectiveIssuers = "TraitorCorporations";
public bool GiveBriefing = true;
public int TotalTraitors => TraitorMinds.Count;
- public string[] Codewords = new string[3];
public enum SelectionState
{
[DataField]
public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg");
- /// <summary>
- /// The amount of codewords that are selected.
- /// </summary>
- [DataField]
- public int CodewordCount = 4;
-
/// <summary>
/// The amount of TC traitors start with.
/// </summary>
using Robust.Shared.Random;
using System.Linq;
using System.Text;
+using Content.Server.Codewords;
namespace Content.Server.GameTicking.Rules;
[Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!;
[Dependency] private readonly SharedRoleSystem _roleSystem = default!;
[Dependency] private readonly UplinkSystem _uplink = default!;
+ [Dependency] private readonly CodewordSystem _codewordSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<TraitorRuleComponent, ObjectivesTextPrependEvent>(OnObjectivesTextPrepend);
}
- protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args)
- {
- base.Added(uid, component, gameRule, args);
- SetCodewords(component, args.RuleEntity);
- }
-
private void AfterEntitySelected(Entity<TraitorRuleComponent> ent, ref AfterAntagEntitySelectedEvent args)
{
Log.Debug($"AfterAntagEntitySelected {ToPrettyString(ent)}");
MakeTraitor(args.EntityUid, ent);
}
- private void SetCodewords(TraitorRuleComponent component, EntityUid ruleEntity)
- {
- component.Codewords = GenerateTraitorCodewords(component);
- _adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for game rule {ToPrettyString(ruleEntity)}: {string.Join(", ", component.Codewords)}");
- }
-
- 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);
- string[] codewords = new string[finalCodewordCount];
- for (var i = 0; i < finalCodewordCount; i++)
- {
- codewords[i] = Loc.GetString(_random.PickAndTake(codewordPool));
- }
- return codewords;
- }
-
public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component)
{
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - start");
+ var factionCodewords = _codewordSystem.GetCodewords(component.CodewordFactionPrototypeId);
//Grab the mind if it wasn't provided
if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind))
if (component.GiveCodewords)
{
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - added codewords flufftext to briefing");
- briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords)));
+ briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", factionCodewords)));
}
var issuer = _random.Pick(_prototypeManager.Index(component.ObjectiveIssuers));
if (component.GiveCodewords)
{
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - set codewords from component");
- codewords = component.Codewords;
+ codewords = factionCodewords;
}
if (component.GiveBriefing)
var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found
RoleCodewordComponent codewordComp = EnsureComp<RoleCodewordComponent>(mindId);
- _roleCodewordSystem.SetRoleCodewords(codewordComp, "traitor", component.Codewords.ToList(), color);
+ _roleCodewordSystem.SetRoleCodewords(codewordComp, "traitor", factionCodewords.ToList(), color);
// Change the faction
Log.Debug($"MakeTraitor {ToPrettyString(traitor)} - Change faction");
private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextPrependEvent args)
{
if(comp.GiveCodewords)
- args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords)));
+ args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", _codewordSystem.GetCodewords(comp.CodewordFactionPrototypeId))));
}
// TODO: figure out how to handle this? add priority to briefing event?
+using Content.Server.Codewords;
+using Robust.Shared.Prototypes;
+
namespace Content.Server.Traitor.Components;
/// <summary>
[RegisterComponent]
public sealed partial class TraitorCodePaperComponent : Component
{
+ /// <summary>
+ /// The faction to get codewords for.
+ /// </summary>
+ [DataField]
+ public ProtoId<CodewordFactionPrototype> CodewordFaction = "Traitor";
+
+ /// <summary>
+ /// The generator to use for the fake words.
+ /// </summary>
+ [DataField]
+ public ProtoId<CodewordGeneratorPrototype> CodewordGenerator = "TraitorCodewordGenerator";
+
/// <summary>
/// The number of codewords that should be generated on this paper.
/// Will not extend past the max number of available codewords.
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Linq;
+using Content.Server.Codewords;
using Content.Shared.Paper;
namespace Content.Server.Traitor.Systems;
[Dependency] private readonly TraitorRuleSystem _traitorRuleSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly PaperSystem _paper = default!;
+ [Dependency] private readonly CodewordSystem _codewordSystem = default!;
public override void Initialize()
{
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());
- }
- }
- }
+ var codeList = _codewordSystem.GetCodewords(component.CodewordFaction).ToList();
+
if (codeList.Count == 0)
{
if (component.FakeCodewords)
- codeList = _traitorRuleSystem.GenerateTraitorCodewords(new TraitorRuleComponent()).ToList();
+ codeList = _codewordSystem.GenerateCodewords(component.CodewordGenerator).ToList();
else
codeList = [Loc.GetString("traitor-codes-none")];
}
--- /dev/null
+- type: codewordFaction
+ id: Traitor
+ generator: TraitorCodewordGenerator
--- /dev/null
+- type: codewordGenerator
+ id: TraitorCodewordGenerator
+ words:
+ - Adjectives
+ - Verbs
+ amount: 4