using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.GameTicking.Events; using Content.Shared.Mind; using Content.Shared.Mind.Components; using JetBrains.Annotations; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Prototypes; using Robust.Shared.Random; using static System.Linq.Enumerable; namespace Content.Shared.Codewords; /// /// System that generates and handles the assignment of codewords. /// public sealed partial class CodewordSystem : EntitySystem { [Dependency] private ISharedAdminLogManager _adminLogger = default!; [Dependency] private IRobustRandom _random = default!; [Dependency] private SharedMindSystem _mind = default!; [Dependency] private EntityQuery _codewordQuery; [SubscribeLocalEvent] private void OnRoundStart(RoundStartingEvent ev) { var manager = Spawn(); AddComp(manager); } /// /// Retrieves codewords for the faction specified. /// public string[] GetCodewords(ProtoId faction) { var query = EntityQueryEnumerator(); while (query.MoveNext(out _, out var manager)) { if (!manager.Codewords.TryGetValue(faction, out var codewordEntity)) return GenerateForFaction(faction, ref manager); return Comp(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 faction, ref CodewordManagerComponent manager) { var factionProto = ProtoMan.Index(faction.Id); var codewords = GenerateCodewords(factionProto.Generator); var codewordsContainer = Spawn(prototype: null, MapCoordinates.Nullspace); EnsureComp(codewordsContainer) .Codewords = codewords; manager.Codewords[faction] = codewordsContainer; _adminLogger.Add(LogType.EventStarted, LogImpact.Low, $"Codewords generated for faction {faction}: {string.Join(", ", codewords)}"); return codewords; } /// /// Generates codewords as specified by the codeword generator. /// public string[] GenerateCodewords(ProtoId generatorId) { var generator = ProtoMan.Index(generatorId); var codewordPool = new List(); foreach (var dataset in generator.Words .Select(datasetPrototype => ProtoMan.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; } /// /// Returns all codewords a specific player knows /// /// Player account /// All codewords known by this entity. [PublicAPI] public List GetPlayerCodewords(NetUserId? player) { return player != null && _mind.TryGetMind(player.Value, out var mind) ? GetMindCodewords(mind.Value.AsNullable()) : new List(); } /// /// Returns all codewords stored in a mind. /// /// Entity /// All codewords known by this entity. [PublicAPI] public List GetEntityCodewords(Entity entity) { return _mind.TryGetMind(entity, out var uid, out var mind) ? GetMindCodewords((uid, mind)) : new List(); } /// /// Returns all codewords stored in a mind. /// /// Mind entity /// All codewords known by this mind. [PublicAPI] public List GetMindCodewords(Entity mind) { var data = new List(); if (!Resolve(mind, ref mind.Comp)) return data; foreach (var role in mind.Comp.MindRoleContainer.ContainedEntities) { if (GetRoleCodewords(role) is not { } codewords) continue; data.Add(codewords); } return data; } /// /// Returns codewords associated with this role /// /// Role entity /// Codewords for this role, if they exist. [PublicAPI] public CodewordsData? GetRoleCodewords(Entity role) { if (!_codewordQuery.Resolve(role, ref role.Comp, false)) return null; return role.Comp.RoleCodewords; } /// /// Sets the codewords for a given mindrole entity. /// /// Mindrole entity. /// Codewords /// Color the codewords show as [PublicAPI] public void SetRoleCodewords(Entity ent, List codewords, Color color) { var data = new CodewordsData(color, codewords); ent.Comp.RoleCodewords = data; Dirty(ent); } }