Add option for character name colors in chat & move coloration to clientside (#24625)
* Adds option to disable character names in chat/speechbubbles * Moved the coloring of names to clientside * Move string functions to SharedChatSystem to avoid duplicate code in SpeechBubble.cs * Changed to be put under Accessibility section * Cache CVar
This commit is contained in:
@@ -182,20 +182,9 @@ namespace Content.Client.Chat.UI
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected string ExtractSpeechSubstring(ChatMessage message, string tag)
|
||||
{
|
||||
var rawmsg = message.WrappedMessage;
|
||||
var tagStart = rawmsg.IndexOf($"[{tag}]");
|
||||
var tagEnd = rawmsg.IndexOf($"[/{tag}]");
|
||||
if (tagStart < 0 || tagEnd < 0) //the above return -1 if the tag's not found, which in turn will cause the below to throw an exception. a blank speech bubble is far more noticeably broken than the bubble not appearing at all -bhijn
|
||||
return "";
|
||||
tagStart += tag.Length + 2;
|
||||
return rawmsg.Substring(tagStart, tagEnd - tagStart);
|
||||
}
|
||||
|
||||
protected FormattedMessage ExtractAndFormatSpeechSubstring(ChatMessage message, string tag, Color? fontColor = null)
|
||||
{
|
||||
return FormatSpeech(ExtractSpeechSubstring(message, tag), fontColor);
|
||||
return FormatSpeech(SharedChatSystem.GetStringInsideTag(message, tag), fontColor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
FontColorOverride="{xNamespace:Static s:StyleNano.NanoGold}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'ui-options-screen-shake-intensity'}" Margin="8 0" />
|
||||
<Slider Name="ScreenShakeIntensitySlider"
|
||||
|
||||
@@ -63,6 +63,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
|
||||
// ToggleWalk.OnToggled += OnCheckBoxToggled;
|
||||
@@ -76,6 +77,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
|
||||
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
|
||||
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
|
||||
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
|
||||
@@ -120,6 +122,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
|
||||
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
|
||||
@@ -145,6 +148,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
|
||||
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
|
||||
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
|
||||
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
|
||||
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
|
||||
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
|
||||
@@ -159,6 +163,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
isLoocShowSame &&
|
||||
isFancyChatSame &&
|
||||
isFancyBackgroundSame &&
|
||||
isEnableColorNameSame &&
|
||||
isReducedMotionSame &&
|
||||
isScreenShakeIntensitySame &&
|
||||
// isToggleWalkSame &&
|
||||
|
||||
@@ -15,10 +15,12 @@ using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Damage.ForceSay;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
@@ -27,9 +29,11 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -42,9 +46,11 @@ public sealed class ChatUIController : UIController
|
||||
[Dependency] private readonly IChatManager _manager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
@@ -55,6 +61,11 @@ public sealed class ChatUIController : UIController
|
||||
[UISystemDependency] private readonly TypingIndicatorSystem? _typingIndicator = default;
|
||||
[UISystemDependency] private readonly ChatSystem? _chatSys = default;
|
||||
|
||||
[ValidatePrototypeId<ColorPalettePrototype>]
|
||||
private const string ChatNamePalette = "ChatNames";
|
||||
private string[] _chatNameColors = default!;
|
||||
private bool _chatNameColorsEnabled;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public static readonly Dictionary<char, ChatSelectChannel> PrefixToChannel = new()
|
||||
@@ -168,6 +179,8 @@ public sealed class ChatUIController : UIController
|
||||
_net.RegisterNetMessage<MsgChatMessage>(OnChatMessage);
|
||||
_net.RegisterNetMessage<MsgDeleteChatMessagesBy>(OnDeleteChatMessagesBy);
|
||||
SubscribeNetworkEvent<DamageForceSayEvent>(OnDamageForceSay);
|
||||
_cfg.OnValueChanged(CCVars.ChatEnableColorName, (value) => { _chatNameColorsEnabled = value; });
|
||||
_chatNameColorsEnabled = _cfg.GetCVar(CCVars.ChatEnableColorName);
|
||||
|
||||
_speechBubbleRoot = new LayoutContainer();
|
||||
|
||||
@@ -212,6 +225,13 @@ public sealed class ChatUIController : UIController
|
||||
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
|
||||
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
|
||||
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
|
||||
|
||||
var nameColors = _prototypeManager.Index<ColorPalettePrototype>(ChatNamePalette).Colors.Values.ToArray();
|
||||
_chatNameColors = new string[nameColors.Length];
|
||||
for (var i = 0; i < nameColors.Length; i++)
|
||||
{
|
||||
_chatNameColors[i] = nameColors[i].ToHex();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnScreenLoad()
|
||||
@@ -757,6 +777,14 @@ public sealed class ChatUIController : UIController
|
||||
|
||||
public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
|
||||
{
|
||||
// color the name unless it's something like "the old man"
|
||||
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Whisper) && _chatNameColorsEnabled)
|
||||
{
|
||||
var grammar = _ent.GetComponentOrNull<GrammarComponent>(_ent.GetEntity(msg.SenderEntity));
|
||||
if (grammar != null && grammar.ProperNoun == true)
|
||||
msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name")));
|
||||
}
|
||||
|
||||
// Log all incoming chat to repopulate when filter is un-toggled
|
||||
if (!msg.HideChat)
|
||||
{
|
||||
@@ -845,6 +873,17 @@ public sealed class ChatUIController : UIController
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chat name color for a mob
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the mob</param>
|
||||
/// <returns>Hex value of the color</returns>
|
||||
public string GetNameColor(string name)
|
||||
{
|
||||
var colorIdx = Math.Abs(name.GetHashCode() % _chatNameColors.Length);
|
||||
return _chatNameColors[colorIdx];
|
||||
}
|
||||
|
||||
private readonly record struct SpeechBubbleData(ChatMessage Message, SpeechBubble.SpeechType Type);
|
||||
|
||||
private sealed class SpeechBubbleQueueData
|
||||
|
||||
@@ -13,7 +13,6 @@ using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -27,7 +26,6 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -70,10 +68,6 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
private bool _critLoocEnabled;
|
||||
private readonly bool _adminLoocEnabled = true;
|
||||
|
||||
[ValidatePrototypeId<ColorPalettePrototype>]
|
||||
private const string ChatNamePalette = "ChatNames";
|
||||
private string[] _chatNameColors = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -83,13 +77,6 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
_configurationManager.OnValueChanged(CCVars.CritLoocEnabled, OnCritLoocEnabledChanged, true);
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameChange);
|
||||
|
||||
var nameColors = _prototypeManager.Index<ColorPalettePrototype>(ChatNamePalette).Colors.Values.ToArray();
|
||||
_chatNameColors = new string[nameColors.Length];
|
||||
for (var i = 0; i < nameColors.Length; i++)
|
||||
{
|
||||
_chatNameColors[i] = nameColors[i].ToHex();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -424,13 +411,8 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
|
||||
name = FormattedMessage.EscapeText(name);
|
||||
|
||||
// color the name unless it's something like "the old man"
|
||||
string coloredName = name;
|
||||
if (!TryComp<GrammarComponent>(source, out var grammar) || grammar.ProperNoun == true)
|
||||
coloredName = $"[color={GetNameColor(name)}]{name}[/color]";
|
||||
|
||||
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message",
|
||||
("entityName", coloredName),
|
||||
("entityName", name),
|
||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||
("fontType", speech.FontId),
|
||||
("fontSize", speech.FontSize),
|
||||
@@ -499,10 +481,6 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
}
|
||||
name = FormattedMessage.EscapeText(name);
|
||||
|
||||
// color the name unless it's something like "the old man"
|
||||
if (!TryComp<GrammarComponent>(source, out var grammar) || grammar.ProperNoun == true)
|
||||
name = $"[color={GetNameColor(name)}]{name}[/color]";
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message",
|
||||
("entityName", name), ("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
@@ -643,17 +621,6 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chat name color for a mob
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the mob</param>
|
||||
/// <returns>Hex value of the color</returns>
|
||||
public string GetNameColor(string name)
|
||||
{
|
||||
var colorIdx = Math.Abs(name.GetHashCode() % _chatNameColors.Length);
|
||||
return _chatNameColors[colorIdx];
|
||||
}
|
||||
|
||||
private enum MessageRangeCheckResult
|
||||
{
|
||||
Disallowed,
|
||||
|
||||
@@ -1564,6 +1564,9 @@ namespace Content.Shared.CCVar
|
||||
public static readonly CVarDef<bool> ReducedMotion =
|
||||
CVarDef.Create("accessibility.reduced_motion", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<bool> ChatEnableColorName =
|
||||
CVarDef.Create("accessibility.enable_color_name", true, CVar.CLIENTONLY | CVar.ARCHIVE, "Toggles displaying names with individual colors.");
|
||||
|
||||
/// <summary>
|
||||
/// Screen shake intensity slider, controlling the intensity of the CameraRecoilSystem.
|
||||
/// Goes from 0 (no recoil at all) to 1 (regular amounts of recoil)
|
||||
|
||||
@@ -214,4 +214,31 @@ public abstract class SharedChatSystem : EntitySystem
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
public static string InjectTagInsideTag(ChatMessage message, string outerTag, string innerTag, string? tagParameter)
|
||||
{
|
||||
var rawmsg = message.WrappedMessage;
|
||||
var tagStart = rawmsg.IndexOf($"[{outerTag}]");
|
||||
var tagEnd = rawmsg.IndexOf($"[/{outerTag}]");
|
||||
if (tagStart < 0 || tagEnd < 0) //If the outer tag is not found, the injection is not performed
|
||||
return rawmsg;
|
||||
tagStart += outerTag.Length + 2;
|
||||
|
||||
string innerTagProcessed = tagParameter != null ? $"[{innerTag}={tagParameter}]" : $"[{innerTag}]";
|
||||
|
||||
rawmsg = rawmsg.Insert(tagEnd, $"[/{innerTag}]");
|
||||
rawmsg = rawmsg.Insert(tagStart, innerTagProcessed);
|
||||
|
||||
return rawmsg;
|
||||
}
|
||||
public static string GetStringInsideTag(ChatMessage message, string tag)
|
||||
{
|
||||
var rawmsg = message.WrappedMessage;
|
||||
var tagStart = rawmsg.IndexOf($"[{tag}]");
|
||||
var tagEnd = rawmsg.IndexOf($"[/{tag}]");
|
||||
if (tagStart < 0 || tagEnd < 0)
|
||||
return "";
|
||||
tagStart += tag.Length + 2;
|
||||
return rawmsg.Substring(tagStart, tagEnd - tagStart);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,10 @@ chat-manager-whisper-headset-on-message = You can't whisper on the radio!
|
||||
chat-manager-server-wrap-message = [bold]{$message}[/bold]
|
||||
chat-manager-sender-announcement-wrap-message = [font size=14][bold]{$sender} Announcement:[/font][font size=12]
|
||||
{$message}[/bold][/font]
|
||||
chat-manager-entity-say-wrap-message = [BubbleHeader][bold]{$entityName}[/bold][/BubbleHeader] {$verb}, [font={$fontType} size={$fontSize}]"[BubbleContent]{$message}[/BubbleContent]"[/font]
|
||||
chat-manager-entity-say-bold-wrap-message = [BubbleHeader][bold]{$entityName}[/bold][/BubbleHeader] {$verb}, [font={$fontType} size={$fontSize}]"[BubbleContent][bold]{$message}[/bold][/BubbleContent]"[/font]
|
||||
chat-manager-entity-say-wrap-message = [BubbleHeader][bold][Name]{$entityName}[/Name][/bold][/BubbleHeader] {$verb}, [font={$fontType} size={$fontSize}]"[BubbleContent]{$message}[/BubbleContent]"[/font]
|
||||
chat-manager-entity-say-bold-wrap-message = [BubbleHeader][bold][Name]{$entityName}[/Name][/bold][/BubbleHeader] {$verb}, [font={$fontType} size={$fontSize}]"[BubbleContent][bold]{$message}[/bold][/BubbleContent]"[/font]
|
||||
|
||||
chat-manager-entity-whisper-wrap-message = [font size=11][italic][BubbleHeader]{$entityName}[/BubbleHeader] whispers,"[BubbleContent]{$message}[/BubbleContent]"[/italic][/font]
|
||||
chat-manager-entity-whisper-wrap-message = [font size=11][italic][BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] whispers,"[BubbleContent]{$message}[/BubbleContent]"[/italic][/font]
|
||||
chat-manager-entity-whisper-unknown-wrap-message = [font size=11][italic][BubbleHeader]Someone[/BubbleHeader] whispers, "[BubbleContent]{$message}[/BubbleContent]"[/italic][/font]
|
||||
|
||||
# THE() is not used here because the entity and its name can technically be disconnected if a nameOverride is passed...
|
||||
|
||||
@@ -45,6 +45,7 @@ ui-options-opaque-storage-window = Opaque storage window
|
||||
ui-options-show-looc-on-head = Show LOOC chat above characters head
|
||||
ui-options-fancy-speech = Show names in speech bubbles
|
||||
ui-options-fancy-name-background = Add background to speech bubble names
|
||||
ui-options-enable-color-name = Add colors to character names
|
||||
ui-options-reduced-motion = Reduce motion of visual effects
|
||||
ui-options-screen-shake-intensity = Screen shake intensity
|
||||
ui-options-screen-shake-percent = { TOSTRING($intensity, "P0") }
|
||||
|
||||
Reference in New Issue
Block a user