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
This commit is contained in:
8
Content.Client/Roles/RoleCodewordSystem.cs
Normal file
8
Content.Client/Roles/RoleCodewordSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
|
||||
namespace Content.Client.Roles;
|
||||
|
||||
public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
8
Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs
Normal file
8
Content.Server/Roles/RoleCodeword/RoleCodewordSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
|
||||
namespace Content.Server.Roles.RoleCodeword;
|
||||
|
||||
public sealed class RoleCodewordSystem : SharedRoleCodewordSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
37
Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs
Normal file
37
Content.Shared/Roles/RoleCodeword/RoleCodewordComponent.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -314,7 +314,6 @@
|
||||
- slow
|
||||
- swift
|
||||
- young
|
||||
- Taste/Touch
|
||||
- bitter
|
||||
- delicious
|
||||
- fresh
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: dataset
|
||||
- type: dataset
|
||||
id: verbs
|
||||
values:
|
||||
- accept
|
||||
@@ -616,7 +616,6 @@
|
||||
- whine
|
||||
- whip
|
||||
- whirl
|
||||
- whisper
|
||||
- whistle
|
||||
- wink
|
||||
- wipe
|
||||
|
||||
Reference in New Issue
Block a user