diff --git a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs index 31dd9897f4e..00b191cc60c 100644 --- a/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs +++ b/Content.Client/Options/UI/OptionsTabControlRow.xaml.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Client._CorvaxNext.Options.UI; using Content.Client.Stylesheets; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; @@ -120,6 +121,21 @@ public OptionSliderFloatCVar AddOptionPercentSlider( { return AddOption(new OptionSliderFloatCVar(this, _cfg, cVar, slider, min, max, scale, FormatPercent)); } + + // Corvax-Highlights-Start + /// + /// Add a color slider option, backed by a simple string CVar. + /// + /// The CVar represented by the slider. + /// The UI control for the option. + /// The option instance backing the added option. + public OptionColorSliderCVar AddOptionColorSlider( + CVarDef cVar, + OptionColorSlider slider) + { + return AddOption(new OptionColorSliderCVar(this, _cfg, cVar, slider)); + } + // Corvax-Highlights-End /// /// Add a slider option, backed by a simple integer CVar. @@ -518,6 +534,60 @@ private void UpdateLabelValue() } } +/// +/// Implementation of a CVar option that simply corresponds with a string . +/// +/// +public sealed class OptionColorSliderCVar : BaseOptionCVar +{ + private readonly OptionColorSlider _slider; + + protected override string Value + { + get => _slider.Slider.Color.ToHex(); + set + { + _slider.Slider.Color = Color.FromHex(value); + UpdateLabelColor(); + } + } + + // Corvax-Highlights-Start + /// + /// Creates a new instance of this type. + /// + /// + /// + /// It is generally more convenient to call overloads on + /// such as instead of instantiating this type directly. + /// + /// + /// The control row that owns this option. + /// The configuration manager to get and set values from. + /// The CVar that is being controlled by this option. + /// The UI control for the option. + public OptionColorSliderCVar( + OptionsTabControlRow controller, + IConfigurationManager cfg, + CVarDef cVar, + OptionColorSlider slider) : base(controller, cfg, cVar) + { + _slider = slider; + + slider.Slider.OnColorChanged += _ => + { + ValueChanged(); + UpdateLabelColor(); + }; + } + // Corvax-Highlights-End + + private void UpdateLabelColor() + { + _slider.ExampleLabel.FontColorOverride = Color.FromHex(Value); + } +} + /// /// Implementation of a CVar option that simply corresponds with an integer . /// diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml index 54d92b2b11c..9edb23d0a80 100644 --- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml +++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml @@ -1,6 +1,7 @@ + xmlns:ui="clr-namespace:Content.Client.Options.UI" + xmlns:cnui="clr-namespace:Content.Client._CorvaxNext.Options.UI"> @@ -9,6 +10,12 @@ + + + + diff --git a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs index 15182fbf126..5ab3d38b77e 100644 --- a/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/AccessibilityTab.xaml.cs @@ -2,6 +2,9 @@ using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; using Robust.Client.UserInterface.XAML; +// Corvax-Highlights-Start +using Content.Shared.Corvax.CCCVars; +// Corvax-Highlights-End namespace Content.Client.Options.UI.Tabs; @@ -17,6 +20,10 @@ public AccessibilityTab() Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox); Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider); Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider); + // Corvax-Highlights-Start + Control.AddOptionCheckBox(CCCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox); + Control.AddOptionColorSlider(CCCVars.ChatHighlightsColor, HighlightsColorSlider); + // Corvax-Highlights-End Control.Initialize(); } diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 005a9d1a2ec..6af9a77e148 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -41,9 +41,16 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; +// Corvax-Highlights-Start +using Content.Client.CharacterInfo; +using static Content.Client.CharacterInfo.CharacterInfoSystem; +using Content.Shared.Corvax.CCCVars; +// Corvax-Highlights-End + namespace Content.Client.UserInterface.Systems.Chat; -public sealed class ChatUIController : UIController +// Corvax-Highlights +public sealed class ChatUIController : UIController, IOnSystemChanged { [Dependency] private readonly IClientAdminManager _admin = default!; [Dependency] private readonly IChatManager _manager = default!; @@ -65,6 +72,9 @@ public sealed class ChatUIController : UIController [UISystemDependency] private readonly TransformSystem? _transform = default; [UISystemDependency] private readonly MindSystem? _mindSystem = default!; [UISystemDependency] private readonly RoleCodewordSystem? _roleCodewordSystem = default!; + // Corvax-Highlights-Start + [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!; + // Corvax-Highlights-End [ValidatePrototypeId] private const string ChatNamePalette = "ChatNames"; @@ -149,6 +159,25 @@ private readonly Dictionary _queuedSpeechBubbl /// private readonly Dictionary _unreadMessages = new(); + // Corvax-Highlights-Start + /// + /// A list of words to be highlighted in the chatbox. + /// + private List _highlights = []; + + /// + /// The color (hex) in witch the words will be highlighted as. + /// + private string? _highlightsColor; + + private bool _autoFillHighlightsEnabled; + + /// + /// A bool to keep track if the 'CharacterUpdated' event is a new player attaching or the opening of the character info panel. + /// + private bool _charInfoIsAttach = false; + // Corvax-Highlights-End + // TODO add a cap for this for non-replays public readonly List<(GameTick Tick, ChatMessage Msg)> History = new(); @@ -172,6 +201,9 @@ private readonly Dictionary _queuedSpeechBubbl public event Action? SelectableChannelsChanged; public event Action? UnreadMessageCountsUpdated; public event Action? MessageAdded; + // Corvax-Highlights-Start + public event Action? HighlightsUpdated; + // Corvax-Highlights-End public override void Initialize() { @@ -240,6 +272,21 @@ public override void Initialize() _config.OnValueChanged(CCVars.ChatWindowOpacity, OnChatWindowOpacityChanged); + // Corvax-Highlights-Start + _config.OnValueChanged(CCCVars.ChatAutoFillHighlights, (value) => { _autoFillHighlightsEnabled = value; }); + _autoFillHighlightsEnabled = _config.GetCVar(CCCVars.ChatAutoFillHighlights); + + _config.OnValueChanged(CCCVars.ChatHighlightsColor, (value) => { _highlightsColor = value; }); + _highlightsColor = _config.GetCVar(CCCVars.ChatHighlightsColor); + + // Load highlights if any were saved. + string highlights = _config.GetCVar(CCCVars.ChatHighlights); + + if (!string.IsNullOrEmpty(highlights)) + { + UpdateHighlights(highlights); + } + // Corvax-Highlights-End } public void OnScreenLoad() @@ -257,6 +304,69 @@ public void OnScreenUnload() SetMainChat(false); } + // Corvax-Highlights-Start + public void OnSystemLoaded(CharacterInfoSystem system) + { + system.OnCharacterUpdate += CharacterUpdated; + } + + public void OnSystemUnloaded(CharacterInfoSystem system) + { + system.OnCharacterUpdate -= CharacterUpdated; + } + + private void CharacterUpdated(CharacterData data) + { + // If the _charInfoIsAttach is false then the character panel created the event, dismiss. + if (!_charInfoIsAttach) + return; + + var (_, job, _, _, entityName) = data; + + // If the character has a normal name (eg. "Name Surname" and not "Name Initial Surname" or a particular species name) + // subdivide it so that the name and surname individually get highlighted. + if (entityName.Count(c => c == ' ') == 1) + entityName = entityName.Replace(' ', '\n'); + + string newHighlights = entityName; + + // Convert the job title to kebab-case and use it as a key for the loc file. + string jobKey = job.Replace(' ', '-').ToLower(); + + if (Loc.TryGetString($"highlights-{jobKey}", out var jobMatches)) + newHighlights += '\n' + jobMatches.Replace(", ", "\n"); + + UpdateHighlights(newHighlights); + HighlightsUpdated?.Invoke(newHighlights); + _charInfoIsAttach = false; + } + + public void UpdateHighlights(string highlights) + { + // Save the newly provided list of highlighs if different. + if (!_config.GetCVar(CCCVars.ChatHighlights).Equals(highlights, StringComparison.CurrentCultureIgnoreCase)) + { + _config.SetCVar(CCCVars.ChatHighlights, highlights); + _config.SaveToFile(); + } + + // If the word is surrounded by "" we replace them with a whole-word regex tag. + highlights = highlights.Replace("\"", "\\b"); + + // Fill the array with the highlights separated by newlines, disregarding empty entries. + string[] arrHighlights = highlights.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + _highlights.Clear(); + foreach (var keyword in arrHighlights) + { + _highlights.Add(keyword); + } + + // Arrange the list in descending order so that when highlighting, + // the full word (eg. "Security") appears before the abbreviation (eg. "Sec"). + _highlights.Sort((x, y) => y.Length.CompareTo(x.Length)); + } + // Corvax-Highlights-End + private void OnChatWindowOpacityChanged(float opacity) { SetChatWindowOpacity(opacity); @@ -426,6 +536,14 @@ public void SetSpeechBubbleRoot(LayoutContainer root) private void OnAttachedChanged(EntityUid uid) { UpdateChannelPermissions(); + + // Corvax-Highlights-Start + if (_autoFillHighlightsEnabled) + { + _charInfoIsAttach = true; + _characterInfo.RequestCharacterInfo(); + } + // Corvax-Highlights-End } private void AddSpeechBubble(ChatMessage msg, SpeechBubble.SpeechType speechType) @@ -824,6 +942,14 @@ public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true) msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name"))); } + // Corvax-Highlights-Start + // Color any words choosen by the client. + foreach (var highlight in _highlights) + { + msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, highlight, "color", _highlightsColor); + } + // Corvax-Highlights-End + // Color any codewords for minds that have roles that use them if (_player.LocalUser != null && _mindSystem != null && _roleCodewordSystem != null) { diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml index 459c44eee26..69cfc4218b7 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml @@ -1,10 +1,23 @@ - - - + + + + +