]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add codeword highlighting (#30092)
authorSlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com>
Fri, 23 Aug 2024 09:14:38 +0000 (11:14 +0200)
committerGitHub <noreply@github.com>
Fri, 23 Aug 2024 09:14:38 +0000 (11:14 +0200)
* 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 [new file with mode: 0644]
Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
Content.Server/GameTicking/Rules/TraitorRuleSystem.cs
Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs [new file with mode: 0644]
Content.Shared/Chat/SharedChatSystem.cs
Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs [new file with mode: 0644]
Content.Shared/Roles/RoleCodeword/SharedRoleCodewordSystem.cs [new file with mode: 0644]
Resources/Prototypes/Datasets/adjectives.yml
Resources/Prototypes/Datasets/verbs.yml

diff --git a/Content.Client/Roles/RoleCodewordSystem.cs b/Content.Client/Roles/RoleCodewordSystem.cs
new file mode 100644 (file)
index 0000000..8cc2e93
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Roles.RoleCodeword;
+
+namespace Content.Client.Roles;
+
+public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
+{
+
+}
index 904b922baa0b92de6e4ba3dbc538303c149434b2..8f66340a30fedc340d788c6f31a7a624ad585ab5 100644 (file)
@@ -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<ColorPalettePrototype>]
     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)
         {
index 9e8a878a2d1cf103242078a4dca32f7498845b45..0465e1bac4e4a5b86e7e292548428fe10364ed69 100644 (file)
@@ -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<TraitorRuleComponent>
 {
+    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<TraitorRuleComponent>
     [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<TraitorRuleComponent>
 
         _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<TraitorRuleComponent>
             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<RoleCodewordComponent>(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 (file)
index 0000000..a93d0f7
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Roles.RoleCodeword;
+
+namespace Content.Server.Roles.RoleCodeword;
+
+public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
+{
+
+}
index 4f0b1465cd2b2f0fecab9f9cb86371165886384d..bd9ca4fa28cf70dac1c7ea5d9e091ad19104f4b5 100644 (file)
@@ -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;
     }
+
+    /// <summary>
+    /// Injects a tag around all found instances of a specific string in a ChatMessage.
+    /// Excludes strings inside other tags and brackets.
+    /// </summary>
+    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 (file)
index 0000000..a1723db
--- /dev/null
@@ -0,0 +1,37 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Roles.RoleCodeword;
+
+/// <summary>
+/// Used to display and highlight codewords in chat messages on the client.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedRoleCodewordSystem), Other = AccessPermissions.Read)]
+public sealed partial class RoleCodewordComponent : Component
+{
+    /// <summary>
+    /// Contains the codewords tied to a role.
+    /// Key string should be unique for the role.
+    /// </summary>
+    [DataField, AutoNetworkedField]
+    public Dictionary<string, CodewordsData> RoleCodewords = new();
+
+    public override bool SessionSpecific => true;
+}
+
+[DataDefinition, Serializable, NetSerializable]
+public partial struct CodewordsData
+{
+    [DataField]
+    public Color Color;
+
+    [DataField]
+    public List<string> Codewords;
+
+    public CodewordsData(Color color, List<string> 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 (file)
index 0000000..9f86071
--- /dev/null
@@ -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<RoleCodewordComponent, ComponentGetStateAttemptEvent>(OnCodewordCompGetStateAttempt);
+    }
+
+    /// <summary>
+    /// Determines if a codeword component should be sent to the client.
+    /// </summary>
+    private void OnCodewordCompGetStateAttempt(EntityUid uid, RoleCodewordComponent comp, ref ComponentGetStateAttemptEvent args)
+    {
+        args.Cancelled = !CanGetState(args.Player, comp);
+    }
+
+    /// <summary>
+    /// The criteria that determine whether a codeword component should be sent to a client.
+    /// Sends the component if its owner is the player mind.
+    /// </summary>
+    /// <param name="player"> The Player the component will be sent to.</param>
+    /// <param name="comp"> The component being checked against</param>
+    /// <returns></returns>
+    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<string> codewords, Color color)
+    {
+        var data = new CodewordsData(color, codewords);
+        comp.RoleCodewords[key] = data;
+    }
+}
index 86fb29095217adfb812361df413f8321642ae80a..fd243a88696d332a9069c8eb00a2857229d58e4b 100644 (file)
   - slow
   - swift
   - young
-  - Taste/Touch
   - bitter
   - delicious
   - fresh
index f53c18a71bf7b90210659cf1adfd578a4e8fc1d4..ce24537021377a888500163bcfdac2095ace7d96 100644 (file)
@@ -1,4 +1,4 @@
-- type: dataset
+- type: dataset
   id: verbs
   values:
   - accept
   - whine
   - whip
   - whirl
-  - whisper
   - whistle
   - wink
   - wipe