--- /dev/null
+using Content.Shared.Roles.RoleCodeword;
+
+namespace Content.Client.Roles;
+
+public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
+{
+
+}
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;
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;
[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";
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)
{
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;
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!;
[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()
{
_antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code, issuer), null, component.GreetSoundNotification);
+
component.TraitorMinds.Add(mindId);
// Assign briefing
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);
--- /dev/null
+using Content.Shared.Roles.RoleCodeword;
+
+namespace Content.Server.Roles.RoleCodeword;
+
+public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
+{
+
+}
using System.Collections.Frozen;
+using System.Text.RegularExpressions;
using Content.Shared.Popups;
using Content.Shared.Radio;
using Content.Shared.Speech;
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;
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
- slow
- swift
- young
- - Taste/Touch
- bitter
- delicious
- fresh
-- type: dataset
+- type: dataset
id: verbs
values:
- accept
- whine
- whip
- whirl
- - whisper
- whistle
- wink
- wipe