From 61a1e89339d0187f1a59b37284108935669c6826 Mon Sep 17 00:00:00 2001 From: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:14:38 +0200 Subject: [PATCH] Add codeword highlighting (#30092) * Added codeword highlighting * Updated to support more codeword roles, color is set serverside * Review feedback * Change to a Component-based system using SessionSpecific * Tidied up CanGetState, set Access restrictions on component * Clean-up * Makes the injection ignore brackets, restore some codewords, remove "Taste/Touch" from adjectives --- Content.Client/Roles/RoleCodewordSystem.cs | 8 +++ .../Systems/Chat/ChatUIController.cs | 18 +++++++ .../GameTicking/Rules/TraitorRuleSystem.cs | 12 +++++ .../Roles/RoleCodeword/RoleCodewordSystem.cs | 8 +++ Content.Shared/Chat/SharedChatSystem.cs | 13 +++++ .../RoleCodeword/RoleCodewordComponent.cs | 37 ++++++++++++++ .../RoleCodeword/SharedRoleCodewordSystem.cs | 49 +++++++++++++++++++ Resources/Prototypes/Datasets/adjectives.yml | 1 - Resources/Prototypes/Datasets/verbs.yml | 3 +- 9 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 Content.Client/Roles/RoleCodewordSystem.cs create mode 100644 Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs create mode 100644 Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs create mode 100644 Content.Shared/Roles/RoleCodeword/SharedRoleCodewordSystem.cs diff --git a/Content.Client/Roles/RoleCodewordSystem.cs b/Content.Client/Roles/RoleCodewordSystem.cs new file mode 100644 index 0000000000..8cc2e93099 --- /dev/null +++ b/Content.Client/Roles/RoleCodewordSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles.RoleCodeword; + +namespace Content.Client.Roles; + +public sealed class RoleCodewordSystem : SharedRoleCodewordSystem +{ + +} diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 904b922baa..8f66340a30 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -9,6 +9,8 @@ using Content.Client.Chat.UI; using Content.Client.Examine; using Content.Client.Gameplay; using Content.Client.Ghost; +using Content.Client.Mind; +using Content.Client.Roles; using Content.Client.Stylesheets; using Content.Client.UserInterface.Screens; using Content.Client.UserInterface.Systems.Chat.Widgets; @@ -20,6 +22,7 @@ using Content.Shared.Damage.ForceSay; using Content.Shared.Decals; using Content.Shared.Input; using Content.Shared.Radio; +using Content.Shared.Roles.RoleCodeword; using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.Input; @@ -60,6 +63,8 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default; [UISystemDependency] private readonly ChatSystem? _chatSys = default; [UISystemDependency] private readonly TransformSystem? _transform = default; + [UISystemDependency] private readonly MindSystem? _mindSystem = default!; + [UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!; [ValidatePrototypeId] private const string ChatNamePalette = "ChatNames"; @@ -819,6 +824,19 @@ public sealed class ChatUIController : UIController msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name"))); } + // Color any codewords for minds that have roles that use them + if (_player.LocalUser != null && _mindSystem != null && _roleCodewordSystem != null) + { + if (_mindSystem.TryGetMind(_player.LocalUser.Value, out var mindId) && _ent.TryGetComponent(mindId, out RoleCodewordComponent? codewordComp)) + { + foreach (var (_, codewordData) in codewordComp.RoleCodewords) + { + foreach (string codeword in codewordData.Codewords) + msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, codeword, "color", codewordData.Color.ToHex()); + } + } + } + // Log all incoming chat to repopulate when filter is un-toggled if (!msg.HideChat) { diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index 9e8a878a2d..0465e1bac4 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -10,8 +10,10 @@ using Content.Shared.Mind; using Content.Shared.NPC.Systems; using Content.Shared.Objectives.Components; using Content.Shared.PDA; +using Content.Shared.Radio; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; +using Content.Shared.Roles.RoleCodeword; using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Linq; @@ -21,6 +23,8 @@ namespace Content.Server.GameTicking.Rules; public sealed class TraitorRuleSystem : GameRuleSystem { + private static readonly Color TraitorCodewordColor = Color.FromHex("#cc3b3b"); + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; @@ -29,6 +33,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; + [Dependency] private readonly SharedRoleCodewordSystem _roleCodewordSystem = default!; public override void Initialize() { @@ -102,6 +107,7 @@ public sealed class TraitorRuleSystem : GameRuleSystem _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification); + component.TraitorMinds.Add(mindId); // Assign briefing @@ -110,6 +116,12 @@ public sealed class TraitorRuleSystem : GameRuleSystem Briefing = briefing }, mind, true); + // Send codewords to only the traitor client + var color = TraitorCodewordColor; // Fall back to a dark red Syndicate color if a prototype is not found + + RoleCodewordComponent codewordComp = EnsureComp(mindId); + _roleCodewordSystem.SetRoleCodewords(codewordComp, "traitor", component.Codewords.ToList(), color); + // Change the faction _npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false); _npcFaction.AddFaction(traitor, component.SyndicateFaction); diff --git a/Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs b/Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs new file mode 100644 index 0000000000..a93d0f773b --- /dev/null +++ b/Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles.RoleCodeword; + +namespace Content.Server.Roles.RoleCodeword; + +public sealed class RoleCodewordSystem : SharedRoleCodewordSystem +{ + +} diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index 4f0b1465cd..bd9ca4fa28 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -1,4 +1,5 @@ using System.Collections.Frozen; +using System.Text.RegularExpressions; using Content.Shared.Popups; using Content.Shared.Radio; using Content.Shared.Speech; @@ -237,6 +238,18 @@ public abstract class SharedChatSystem : EntitySystem return rawmsg; } + + /// + /// Injects a tag around all found instances of a specific string in a ChatMessage. + /// Excludes strings inside other tags and brackets. + /// + public static string InjectTagAroundString(ChatMessage message, string targetString, string tag, string? tagParameter) + { + var rawmsg = message.WrappedMessage; + rawmsg = Regex.Replace(rawmsg, "(?i)(" + targetString + ")(?-i)(?![^[]*])", $"[{tag}={tagParameter}]$1[/{tag}]"); + return rawmsg; + } + public static string GetStringInsideTag(ChatMessage message, string tag) { var rawmsg = message.WrappedMessage; diff --git a/Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs b/Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs new file mode 100644 index 0000000000..a1723dbc7e --- /dev/null +++ b/Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs @@ -0,0 +1,37 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared.Roles.RoleCodeword; + +/// +/// Used to display and highlight codewords in chat messages on the client. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRoleCodewordSystem), Other = AccessPermissions.Read)] +public sealed partial class RoleCodewordComponent : Component +{ + /// + /// Contains the codewords tied to a role. + /// Key string should be unique for the role. + /// + [DataField, AutoNetworkedField] + public Dictionary RoleCodewords = new(); + + public override bool SessionSpecific => true; +} + +[DataDefinition, Serializable, NetSerializable] +public partial struct CodewordsData +{ + [DataField] + public Color Color; + + [DataField] + public List Codewords; + + public CodewordsData(Color color, List codewords) + { + Color = color; + Codewords = codewords; + } +} diff --git a/Content.Shared/Roles/RoleCodeword/SharedRoleCodewordSystem.cs b/Content.Shared/Roles/RoleCodeword/SharedRoleCodewordSystem.cs new file mode 100644 index 0000000000..9f860715fb --- /dev/null +++ b/Content.Shared/Roles/RoleCodeword/SharedRoleCodewordSystem.cs @@ -0,0 +1,49 @@ +using Content.Shared.Mind; +using Robust.Shared.GameStates; +using Robust.Shared.Player; + +namespace Content.Shared.Roles.RoleCodeword; + +public abstract class SharedRoleCodewordSystem : EntitySystem +{ + [Dependency] private readonly SharedMindSystem _mindSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnCodewordCompGetStateAttempt); + } + + /// + /// Determines if a codeword component should be sent to the client. + /// + private void OnCodewordCompGetStateAttempt(EntityUid uid, RoleCodewordComponent comp, ref ComponentGetStateAttemptEvent args) + { + args.Cancelled = !CanGetState(args.Player, comp); + } + + /// + /// The criteria that determine whether a codeword component should be sent to a client. + /// Sends the component if its owner is the player mind. + /// + /// The Player the component will be sent to. + /// The component being checked against + /// + private bool CanGetState(ICommonSession? player, RoleCodewordComponent comp) + { + if (!_mindSystem.TryGetMind(player, out EntityUid mindId, out var _)) + return false; + + if (!TryComp(mindId, out RoleCodewordComponent? playerComp) && comp != playerComp) + return false; + + return true; + } + + public void SetRoleCodewords(RoleCodewordComponent comp, string key, List codewords, Color color) + { + var data = new CodewordsData(color, codewords); + comp.RoleCodewords[key] = data; + } +} diff --git a/Resources/Prototypes/Datasets/adjectives.yml b/Resources/Prototypes/Datasets/adjectives.yml index 86fb290952..fd243a8869 100644 --- a/Resources/Prototypes/Datasets/adjectives.yml +++ b/Resources/Prototypes/Datasets/adjectives.yml @@ -314,7 +314,6 @@ - slow - swift - young - - Taste/Touch - bitter - delicious - fresh diff --git a/Resources/Prototypes/Datasets/verbs.yml b/Resources/Prototypes/Datasets/verbs.yml index f53c18a71b..ce24537021 100644 --- a/Resources/Prototypes/Datasets/verbs.yml +++ b/Resources/Prototypes/Datasets/verbs.yml @@ -1,4 +1,4 @@ -- type: dataset +- type: dataset id: verbs values: - accept @@ -616,7 +616,6 @@ - whine - whip - whirl - - whisper - whistle - wink - wipe -- 2.51.2