diff --git a/Content.Client/CharacterInfo/CharacterInfoSystem.cs b/Content.Client/CharacterInfo/CharacterInfoSystem.cs
index aeaa48c6f2..2f06924edd 100644
--- a/Content.Client/CharacterInfo/CharacterInfoSystem.cs
+++ b/Content.Client/CharacterInfo/CharacterInfoSystem.cs
@@ -31,9 +31,11 @@ public sealed class CharacterInfoSystem : EntitySystem
private void OnCharacterInfoEvent(CharacterInfoEvent msg, EntitySessionEventArgs args)
{
- var entity = GetEntity(msg.NetEntity);
- var data = new CharacterData(entity, msg.JobTitle, msg.Objectives, msg.Briefing, Name(entity));
+ // DeltaV - Refactored to use TryGetEntity instead of GetEntity because of message highlighting which requests character info too soon and I don't know how to delay it properly
+ if (!TryGetEntity(msg.NetEntity, out var entity))
+ return;
+ var data = new CharacterData(entity.Value, msg.JobTitle, msg.Objectives, msg.Briefing, Name(entity.Value));
OnCharacterUpdate?.Invoke(data);
}
diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
index c02683898b..15e748e70e 100644
--- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
+++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs
@@ -44,7 +44,8 @@ using Content.Client.Nyanotrasen.Chat; //Nyano - Summary: chat namespace.
namespace Content.Client.UserInterface.Systems.Chat;
-public sealed class ChatUIController : UIController
+// DeltaV - Make partial to implement message highlighting
+public sealed partial class ChatUIController : UIController
{
[Dependency] private readonly IClientAdminManager _admin = default!;
[Dependency] private readonly IChatManager _manager = default!;
@@ -245,6 +246,7 @@ public sealed class ChatUIController : UIController
_config.OnValueChanged(CCVars.ChatWindowOpacity, OnChatWindowOpacityChanged);
+ InitializeChatHighlights(); // DeltaV - Message highlighting
}
public void OnScreenLoad()
@@ -839,6 +841,14 @@ public sealed class ChatUIController : UIController
msg.WrappedMessage = SharedChatSystem.InjectTagInsideTag(msg, "Name", "color", GetNameColor(SharedChatSystem.GetStringInsideTag(msg, "Name")));
}
+ // DeltaV - Message highlighting start
+ // Color any words choosen by the client.
+ foreach (var highlight in _highlights)
+ {
+ msg.WrappedMessage = SharedChatSystem.InjectTagAroundString(msg, highlight, "color", _highlightsColor);
+ }
+ // DeltaV - Message highlighting 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 459c44eee2..d7c485fcb2 100644
--- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml
+++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml
@@ -1,10 +1,34 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
index 1d2a431446..f651429689 100644
--- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
@@ -34,6 +34,7 @@ public sealed partial class ChannelFilterPopup : Popup
public ChannelFilterPopup()
{
RobustXamlLoader.Load(this);
+ InitializeChatHighlights(); // DeltaV - Message highlighting
}
public bool IsActive(ChatChannel channel)
diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
index 62b3b19e38..f6c4cf407d 100644
--- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
@@ -36,8 +36,9 @@ public partial class ChatBox : UIWidget
ChatInput.Input.OnTextChanged += OnTextChanged;
ChatInput.ChannelSelector.OnChannelSelect += OnChannelSelect;
ChatInput.FilterButton.Popup.OnChannelFilter += OnChannelFilter;
-
+ ChatInput.FilterButton.Popup.OnNewHighlights += OnNewHighlights; // DeltaV - Message highlighting
_controller = UserInterfaceManager.GetUIController();
+ _controller.OnAutoHighlightsUpdated += ChatInput.FilterButton.Popup.SetAutoHighlights; // DeltaV - Message highlighting
_controller.MessageAdded += OnMessageAdded;
_controller.RegisterChat(this);
}
diff --git a/Content.Client/_DV/Options/UI/OptionColorSlider.xaml b/Content.Client/_DV/Options/UI/OptionColorSlider.xaml
new file mode 100644
index 0000000000..4f5f082350
--- /dev/null
+++ b/Content.Client/_DV/Options/UI/OptionColorSlider.xaml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs b/Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs
new file mode 100644
index 0000000000..ec30ada818
--- /dev/null
+++ b/Content.Client/_DV/Options/UI/OptionColorSlider.xaml.cs
@@ -0,0 +1,30 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+
+namespace Content.Client._DV.Options.UI;
+
+///
+/// Standard UI control used for color sliders in the options menu. Intended for use with .
+///
+///
+[GenerateTypedNameReferences]
+public sealed partial class OptionColorSlider : Control
+{
+ ///
+ /// The text describing what this slider affects.
+ ///
+ public string? Title
+ {
+ get => TitleLabel.Text;
+ set => TitleLabel.Text = value;
+ }
+
+ ///
+ /// The example text showing the current color of the slider.
+ ///
+ public string? Example
+ {
+ get => ExampleLabel.Text;
+ set => ExampleLabel.Text = value;
+ }
+}
diff --git a/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs b/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs
new file mode 100644
index 0000000000..9e121c57a2
--- /dev/null
+++ b/Content.Client/_DV/Options/UI/OptionsTabControlRow.xaml.cs
@@ -0,0 +1,20 @@
+using Content.Client._DV.Options.UI;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.Options.UI;
+
+public sealed partial class OptionsTabControlRow
+{
+ ///
+ /// 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));
+ }
+}
diff --git a/Content.Client/_DV/Options/UI/OptionsTabControls.cs b/Content.Client/_DV/Options/UI/OptionsTabControls.cs
new file mode 100644
index 0000000000..f3d9fd3c7f
--- /dev/null
+++ b/Content.Client/_DV/Options/UI/OptionsTabControls.cs
@@ -0,0 +1,56 @@
+using Content.Client.Options.UI;
+using Robust.Shared.Configuration;
+
+namespace Content.Client._DV.Options.UI;
+
+///
+/// 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();
+ }
+ }
+
+ ///
+ /// 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();
+ };
+ }
+
+ private void UpdateLabelColor()
+ {
+ _slider.ExampleLabel.FontColorOverride = Color.FromHex(Value);
+ }
+}
diff --git a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
index d291272076..1050a08084 100644
--- a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
+++ b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml
@@ -1,9 +1,10 @@
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ xmlns:tabs="clr-namespace:Content.Client._DV.Options.UI.Tabs"
+ xmlns:xNamespace="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:s="clr-namespace:Content.Client.Stylesheets"
+ xmlns:ui="clr-namespace:Content.Client.Options.UI"
+ xmlns:dvui="clr-namespace:Content.Client._DV.Options.UI">
+
+
diff --git a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
index 9d88997798..648391bbbb 100644
--- a/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
+++ b/Content.Client/_DV/Options/UI/Tabs/DeltaTab.xaml.cs
@@ -1,22 +1,22 @@
using Content.Shared._DV.CCVars;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
namespace Content.Client._DV.Options.UI.Tabs;
[GenerateTypedNameReferences]
public sealed partial class DeltaTab : Control
{
- [Dependency] private readonly IConfigurationManager _cfg = default!;
-
public DeltaTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
Control.AddOptionCheckBox(DCCVars.NoVisionFilters, DisableFiltersCheckBox);
+ Control.AddOptionCheckBox(DCCVars.ChatAutoFillHighlights, AutoFillHighlightsCheckBox);
+ Control.AddOptionColorSlider(DCCVars.ChatHighlightsColor, HighlightsColorSlider);
+
+ Control.Initialize();
}
}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
new file mode 100644
index 0000000000..80a213802a
--- /dev/null
+++ b/Content.Client/_DV/UserInterfaces/Systems/Chat/ChatUIController.cs
@@ -0,0 +1,118 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Content.Client.CharacterInfo;
+using Content.Shared._DV.CCVars;
+using Content.Shared.Dataset;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using static Content.Client.CharacterInfo.CharacterInfoSystem;
+
+namespace Content.Client.UserInterface.Systems.Chat;
+
+public sealed partial class ChatUIController : IOnSystemChanged
+{
+ ///
+ /// Gets Invoked whenever the autofilled highlights have changed.
+ /// Used to populate the preview in the channel selector window.
+ ///
+ public event Action? OnAutoHighlightsUpdated;
+
+ [UISystemDependency] private readonly CharacterInfoSystem _characterInfo = default!;
+
+ ///
+ /// A list of words to be highlighted in the chatbox.
+ /// User-specified.
+ ///
+ private readonly List _highlights = [];
+
+ ///
+ /// A list of words to be highlighted in the chatbox.
+ /// Auto-generated from users's character information.
+ ///
+ private readonly List _autoHighlights = [];
+
+ ///
+ /// The color (hex) in witch the words will be highlighted as.
+ ///
+ private string? _highlightsColor;
+
+ private bool _autoFillHighlightsEnabled;
+
+ private void InitializeChatHighlights()
+ {
+
+ _player.LocalPlayerAttached += _ => _characterInfo.RequestCharacterInfo();
+ _player.LocalPlayerDetached += _ => _characterInfo.RequestCharacterInfo();
+
+ _config.OnValueChanged(DCCVars.ChatAutoFillHighlights, value => { _autoFillHighlightsEnabled = value; UpdateHighlights(); });
+ _autoFillHighlightsEnabled = _config.GetCVar(DCCVars.ChatAutoFillHighlights);
+
+ _config.OnValueChanged(DCCVars.ChatHighlightsColor, value => _highlightsColor = value);
+ _highlightsColor = _config.GetCVar(DCCVars.ChatHighlightsColor);
+
+ _config.OnValueChanged(DCCVars.ChatHighlights, UpdateHighlights);
+ UpdateHighlights(_config.GetCVar(DCCVars.ChatHighlights));
+ }
+
+
+ public void OnSystemLoaded(CharacterInfoSystem system)
+ {
+ system.OnCharacterUpdate += UpdateAutoHighlights;
+ }
+
+ public void OnSystemUnloaded(CharacterInfoSystem system)
+ {
+ system.OnCharacterUpdate -= UpdateAutoHighlights;
+ }
+
+ private void UpdateAutoHighlights(CharacterData data)
+ {
+ var (_, job, _, _, entityName) = data;
+
+ _autoHighlights.Clear();
+
+ // 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)
+ _autoHighlights.AddRange(entityName.Split(' '));
+ _autoHighlights.Add(entityName);
+
+ var jobKey = "ChatHighlight" + job.Replace(" ", "");
+ if (_prototypeManager.TryIndex(jobKey, out var jobMatches))
+ _autoHighlights.AddRange(jobMatches.Values.Select(Loc.GetString));
+ else
+ _sawmill.Debug("Missing LocalizedDataset for Job: " + jobKey);
+ UpdateHighlights();
+ }
+
+ public void UpdateHighlights(string? newHighlights = null)
+ {
+ var configuredHighlights = _config.GetCVar(DCCVars.ChatHighlights);
+ var highlights = newHighlights ?? configuredHighlights;
+ // Save the newly provided list of highlights if different.
+ if (newHighlights is not null && !string.Equals(configuredHighlights, highlights, StringComparison.CurrentCultureIgnoreCase))
+ {
+ _config.SetCVar(DCCVars.ChatHighlights, highlights);
+ _config.SaveToFile();
+ }
+
+ var effectiveAutoHighlights = _autoFillHighlightsEnabled
+ ? string.Join("\n", _autoHighlights)
+ : string.Empty;
+ OnAutoHighlightsUpdated?.Invoke(effectiveAutoHighlights);
+
+ var allHighlights = _autoFillHighlightsEnabled
+ ? highlights.Split("\n").Concat(_autoHighlights)
+ : highlights.Split("\n");
+
+ _highlights.Clear();
+ // Use `"` as layman symbol for Regex `\b`, ignore all other special sequences
+ // (Without that escape, a name like `Robert'); DROP TABLE users; --` breaks all messsages)
+ // Turn `\` into `\\` or else it'll escape the tags inside the actual chat message for reasons I can barely intuit but not explain.
+ _highlights.AddRange(allHighlights.Select(highlight => Regex.Escape(highlight.Replace(@"\", @"\\")).Replace("\"", "\\b")));
+
+ // 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));
+ }
+}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
new file mode 100644
index 0000000000..a76c4e7dc4
--- /dev/null
+++ b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs
@@ -0,0 +1,40 @@
+using Content.Shared._DV.CCVars;
+using Robust.Shared.Configuration;
+using Robust.Shared.Utility;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+
+namespace Content.Client.UserInterface.Systems.Chat.Controls;
+
+public sealed partial class ChannelFilterPopup
+{
+ public event Action? OnNewHighlights;
+
+ private void InitializeChatHighlights()
+ {
+ HighlightButton.OnPressed += HighlightsEntered;
+
+ HighlightEdit.Placeholder = new Rope.Leaf(Loc.GetString("hud-chatbox-highlights-placeholder"));
+
+ // Load highlights if any were saved.
+ var cfg = IoCManager.Resolve();
+ var highlights = cfg.GetCVar(DCCVars.ChatHighlights);
+
+ if (!string.IsNullOrEmpty(highlights))
+ {
+ HighlightEdit.TextRope = new Rope.Leaf(highlights);
+ }
+ }
+
+ public void SetAutoHighlights(string autoHighlights)
+ {
+ var anyAutohighlights = !string.IsNullOrWhiteSpace(autoHighlights);
+ AutoHighlightEdit.Visible = anyAutohighlights;
+ AutoHighlightLabel.Visible = anyAutohighlights;
+ AutoHighlightEdit.TextRope = new Rope.Leaf(autoHighlights);
+ }
+
+ private void HighlightsEntered(ButtonEventArgs args)
+ {
+ OnNewHighlights?.Invoke(Rope.Collapse(HighlightEdit.TextRope));
+ }
+}
diff --git a/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs
new file mode 100644
index 0000000000..fe8608ded4
--- /dev/null
+++ b/Content.Client/_DV/UserInterfaces/Systems/Chat/Controls/Widgets/ChatBox.xaml.cs
@@ -0,0 +1,9 @@
+namespace Content.Client.UserInterface.Systems.Chat.Widgets;
+
+public partial class ChatBox
+{
+ private void OnNewHighlights(string highlights)
+ {
+ _controller.UpdateHighlights(highlights);
+ }
+}
diff --git a/Content.Shared/_DV/CCVars/DCCVars.cs b/Content.Shared/_DV/CCVars/DCCVars.cs
index 2538c54ebf..3b16aab11f 100644
--- a/Content.Shared/_DV/CCVars/DCCVars.cs
+++ b/Content.Shared/_DV/CCVars/DCCVars.cs
@@ -162,6 +162,33 @@ public sealed class DCCVars
public static readonly CVarDef EnableBacktoBack =
CVarDef.Create("game.disable_preset_test", false, CVar.SERVERONLY);
+ ///
+ /// A string containing a list of newline-separated strings to be highlighted in the chat.
+ ///
+ public static readonly CVarDef ChatHighlights =
+ CVarDef.Create("deltav.chat.highlights",
+ "",
+ CVar.CLIENTONLY | CVar.ARCHIVE,
+ "A list of newline-separated strings to be highlighted in the chat.");
+
+ ///
+ /// An option to toggle the automatic filling of the highlights with the character's info, if available.
+ ///
+ public static readonly CVarDef ChatAutoFillHighlights =
+ CVarDef.Create("deltav.chat.auto_fill_highlights",
+ false,
+ CVar.CLIENTONLY | CVar.ARCHIVE,
+ "Toggles automatically filling the highlights with the character's information.");
+
+ ///
+ /// The color in which the highlights will be displayed.
+ ///
+ public static readonly CVarDef ChatHighlightsColor =
+ CVarDef.Create("deltav.chat.highlights_color",
+ "#17FFC1FF",
+ CVar.CLIENTONLY | CVar.ARCHIVE,
+ "The color in which the highlights will be displayed.");
+
/* Laying down combat */
///
diff --git a/Resources/Locale/en-US/_DV/chat/highlights.ftl b/Resources/Locale/en-US/_DV/chat/highlights.ftl
new file mode 100644
index 0000000000..ad6e79e94c
--- /dev/null
+++ b/Resources/Locale/en-US/_DV/chat/highlights.ftl
@@ -0,0 +1,341 @@
+# These IDs are generated from job IDs
+# The corresponding prototypes are located in Resources/Prototypes/Catalog/_DV/Chat/highlights.yml
+# if you add or remove a string here, update the protoype
+
+###### Centcomm
+highlight-centcomm-agent-1 = Centcomm
+highlight-centcomm-agent-2 = Central Command
+highlight-centcomm-agent-3 = Death Squad
+highlight-centcomm-agent-4 = DeathSquad
+
+highlight-centcomm-official-1 = Centcomm
+highlight-centcomm-official-2 = Central Command
+
+highlight-centcomm-quarantine-officer-1 = Centcomm
+highlight-centcomm-quarantine-officer-2 = Central Command
+highlight-centcomm-quarantine-officer-3 = "CBURN"
+
+
+###### ERT
+highlight-ert-leader-1 = "ERT Leader
+highlight-ert-leader-2 = Leader
+highlight-ert-leader-3 = "ERT"
+
+highlight-ert-chaplain-1 = Chaplain
+highlight-ert-chaplain-2 = "Chap"
+highlight-ert-chaplain-3 = Chapel
+highlight-ert-chaplain-4 = "ERT"
+
+highlight-ert-engineer-1 = Station Engineer
+highlight-ert-engineer-2 = Engineering
+highlight-ert-engineer-3 = Engineer
+highlight-ert-engineer-4 = "Engi"
+highlight-ert-engineer-5 = "ERT"
+
+highlight-ert-janitor-1 = Janitorial
+highlight-ert-janitor-2 = Janitor
+highlight-ert-janitor-3 = "Jani"
+highlight-ert-janitor-4 = "ERT"
+
+highlight-ert-medic-1 = Medical Doctor
+highlight-ert-medic-2 = Doctor
+highlight-ert-medic-3 = "Doc"
+highlight-ert-medic-4 = MedBay
+highlight-ert-medic-5 = "Med"
+highlight-ert-medic-6 = Medic
+highlight-ert-medic-7 = "ERT"
+
+highlight-ert-security-1 = Security Officer
+highlight-ert-security-2 = Secoff
+highlight-ert-security-3 = Officer
+highlight-ert-security-4 = Security
+highlight-ert-security-5 = "Sec"
+highlight-ert-security-6 = "ERT"
+
+
+###### Command
+highlight-captain-1 = Captain
+highlight-captain-2 = "Cap"
+highlight-captain-3 = Command
+
+highlight-chief-justice-1 = Chief justice
+highlight-chief-justice-2 = "CJ"
+highlight-chief-justice-3 = Justice
+highlight-chief-justice-4 = Command
+
+highlight-chief-engineer-1 = Chief Engineer
+highlight-chief-engineer-2 = "CE"
+highlight-chief-engineer-3 = Engineering
+highlight-chief-engineer-4 = Engineer
+highlight-chief-engineer-5 = "Engi"
+highlight-chief-engineer-6 = Command
+
+highlight-chief-medical-officer-1 = Chief Medical Officer
+highlight-chief-medical-officer-2 = "CMO"
+highlight-chief-medical-officer-3 = MedBay
+highlight-chief-medical-officer-4 = "Med"
+highlight-chief-medical-officer-5 = Command
+
+highlight-head-of-personnel-1 = Head Of Personnel
+highlight-head-of-personnel-2 = "HoP"
+highlight-head-of-personnel-3 = Service
+highlight-head-of-personnel-4 = Command
+
+highlight-head-of-security-1 = Head of Security
+highlight-head-of-security-2 = "HoS"
+highlight-head-of-security-3 = Security
+highlight-head-of-security-4 = "Sec"
+highlight-head-of-security-5 = Command
+
+highlight-logistics-officer-1 = Logistics Officer
+highlight-logistics-officer-2 = "LO"
+highlight-logistics-officer-3 = Logistics
+highlight-logistics-officer-4 = Logi
+highlight-logistics-officer-5 = Command
+
+highlight-mystagogue-1 = Mystagogue
+highlight-mystagogue-2 = Mysta
+highlight-mystagogue-3 = "MG"
+highlight-mystagogue-4 = Epistemics
+highlight-mystagogue-5 = "Epi"
+highlight-mystagogue-6 = Command
+
+highlight-administrative-assistant-1 = Administrative Assistant
+highlight-administrative-assistant-2 = "AA"
+highlight-administrative-assistant-3 = Command
+
+
+###### Security
+highlight-detective-1 = Detective
+highlight-detective-2 = "Det"
+highlight-detective-3 = Security
+highlight-detective-4 = "Sec"
+
+highlight-security-cadet-1 = Security Cadet
+highlight-security-cadet-2 = Cadet
+highlight-security-cadet-3 = Security
+highlight-security-cadet-4 = "Sec"
+
+highlight-security-officer-1 = Security Officer
+highlight-security-officer-2 = Secoff
+highlight-security-officer-3 = Officer
+highlight-security-officer-4 = Security
+highlight-security-officer-5 = "Sec"
+
+highlight-warden-1 = Warden
+highlight-warden-2 = "Ward"
+highlight-warden-3 = Security
+highlight-warden-4 = "Sec"
+highlight-warden-5 = Brig
+
+highlight-prison-guard-1 = Prison Guard
+highlight-prison-guard-2 = Prison
+highlight-prison-guard-3 = Guard
+highlight-prison-guard-4 = Security
+highlight-prison-guard-5 = "Sec"
+highlight-prison-guard-6 = Brig
+
+highlight-corpsman-1 = Corpsman
+highlight-corpsman-2 = "Corps"
+highlight-corpsman-3 = Brigmed
+highlight-corpsman-4 = "Sec"
+highlight-corpsman-5 = Security
+
+
+###### Station-specific
+highlight-boxer-1 = Boxer
+
+highlight-gladiator-1 = Gladiator
+
+highlight-martial-artist-1 = Martial Artist
+highlight-martial-artist-2 = Martial
+
+highlight-prisoner-1 = Prisoner
+
+highlight-psychologist-1 = Psychologist
+highlight-psychologist-2 = Psychology
+highlight-psychologist-3 = Psychodoc
+highlight-psychologist-4 = "Psych"
+
+highlight-reporter-1 = Reporter
+highlight-reporter-2 = Journalist
+
+highlight-zookeeper-1 = Zookeeper
+highlight-zookeeper-2 = Zookeep
+
+
+###### Engineering
+highlight-atmospheric-technician-1 = Atmospheric Technician
+highlight-atmospheric-technician-2 = Atmos
+highlight-atmospheric-technician-3 = Atmospheric
+highlight-atmospheric-technician-4 = Atmosian
+
+highlight-station-engineer-1 = Station Engineer
+highlight-station-engineer-2 = Engineering
+highlight-station-engineer-3 = Engineer
+highlight-station-engineer-4 = "Engi"
+
+highlight-technical-assistant-1 = Technical Assistant
+highlight-technical-assistant-2 = Tech Assistant
+highlight-technical-assistant-3 = Engineering
+highlight-technical-assistant-4 = Engineer
+highlight-technical-assistant-5 = "Engi"
+
+
+###### Epistemics
+highlight-scientist-1 = Scientist
+highlight-scientist-2 = Epistemics
+highlight-scientist-3 = "Epi"
+
+highlight-roboticist-1 = Roboticist
+highlight-roboticist-2 = Epistemics
+highlight-roboticist-3 = "Epi"
+
+highlight-research-assistant-1 = Research Assistant
+highlight-research-assistant-2 = Epistemics
+highlight-research-assistant-3 = "Epi"
+
+highlight-psionic-mantis-1 = "Mantis"
+highlight-psionic-mantis-2 = Psionic Mantis
+
+
+###### Justice
+highlight-clerk-1 = Clerk
+
+highlight-attorney-1 = Lawyer
+highlight-attorney-2 = Attorney
+highlight-attorney-3 = Defense
+
+highlight-prosecutor-1 = Lawyer
+highlight-prosecutor-2 = Prosecutor
+highlight-prosecutor-3 = Prosecution
+highlight-prosecutor-4 = "Prosec"
+
+
+###### Logistics
+highlight-cargo-technician-1 = Cargo Technician
+highlight-cargo-technician-2 = Cargo Tech
+highlight-cargo-technician-3 = Cargo
+highlight-cargo-technician-4 = "CT"
+highlight-cargo-technician-5 = "Logi"
+highlight-cargo-technician-6 = Logistics
+
+highlight-cargo-assistant-1 = Cargo Assistant
+highlight-cargo-assistant-2 = "CA"
+highlight-cargo-assistant-3 = "Logi"
+highlight-cargo-assistant-4 = Logistics
+
+highlight-salvage-specialist-1 = Salvage Specialist
+highlight-salvage-specialist-2 = Salvager
+highlight-salvage-specialist-3 = Salvage
+highlight-salvage-specialist-4 = "Salv"
+highlight-salvage-specialist-5 = Miner
+highlight-salvage-specialist-6 = "Logi"
+highlight-salvage-specialist-7 = Logistics
+
+highlight-courier-1 = Courier
+highlight-courier-2 = Mailman
+highlight-courier-3 = Logistics
+highlight-courier-4 = "Logi"
+
+
+###### Medical
+highlight-chemist-1 = Chemist
+highlight-chemist-2 = Chemistry
+highlight-chemist-3 = MedBay
+highlight-chemist-4 = "Chem"
+highlight-chemist-5 = "Medical"
+
+highlight-medical-doctor-1 = Medical Doctor
+highlight-medical-doctor-2 = Doctor
+highlight-medical-doctor-3 = "Doc"
+highlight-medical-doctor-4 = MedBay
+highlight-medical-doctor-5 = "Med"
+highlight-medical-doctor-6 = "Medical"
+
+highlight-medical-intern-1 = Medical Intern
+highlight-medical-intern-2 = Intern
+highlight-medical-intern-3 = MedBay
+highlight-medical-intern-4 = "Med"
+highlight-medical-intern-5 = "Medical"
+
+highlight-paramedic-1 = Paramedic
+highlight-paramedic-2 = "Para"
+highlight-paramedic-3 = MedBay
+highlight-paramedic-4 = "Med"
+highlight-paramedic-5 = "Medical"
+
+highlight-surgeon-1 = Surgeon
+highlight-surgeon-2 = "Med"
+highlight-surgeon-3 = MedBay
+highlight-surgeon-4 = Medical
+
+
+###### Civilian
+highlight-bartender-1 = Bartender
+highlight-bartender-2 = Barkeeper
+highlight-bartender-3 = Barkeep
+highlight-bartender-4 = "Bar"
+highlight-bartender-5 = Tender
+
+highlight-botanist-1 = Botanist
+highlight-botanist-2 = Botany
+highlight-botanist-3 = Hydroponics
+
+highlight-chaplain-1 = Chaplain
+highlight-chaplain-2 = "Chap"
+highlight-chaplain-3 = Chapel
+
+highlight-chef-1 = Chef
+highlight-chef-2 = "Cook"
+highlight-chef-3 = Kitchen
+
+highlight-clown-1 = Clown
+highlight-clown-2 = Jester
+
+highlight-janitor-1 = Janitorial
+highlight-janitor-2 = Janitor
+highlight-janitor-3 = "Jani"
+
+highlight-lawyer-1 = Lawyer
+highlight-lawyer-2 = Attorney
+
+highlight-librarian-1 = Librarian
+highlight-librarian-2 = Library
+
+highlight-mime-1 = Mime
+
+highlight-musician-1 = Musician
+highlight-musician-2 = Music
+
+highlight-passenger-1 = Passenger
+
+highlight-visitor-1 = Visitor
+
+highlight-service-worker-1 = Service Worker
+
+
+###### Silicon
+highlight-personal-ai-1 = Personal AI
+highlight-personal-ai-2 = "pAI"
+
+highlight-cyborg-1 = Cyborg
+highlight-cyborg-2 = Borgie
+highlight-cyborg-3 = Borg
+
+highlight-medical-cyborg-1 = Cyborg
+highlight-medical-cyborg-2 = Borgie
+highlight-medical-cyborg-3 = Borg
+highlight-medical-cyborg-4 = Medical
+highlight-medical-cyborg-5 = Medborg
+highlight-medical-cyborg-6 = Medical Cyborg
+
+highlight-security-cyborg-1 = Cyborg
+highlight-security-cyborg-2 = Borgie
+highlight-security-cyborg-3 = Borg
+highlight-security-cyborg-4 = Security
+highlight-security-cyborg-5 = Secborg
+highlight-security-cyborg-6 = Security Cyborg
+
+highlight-station-ai-1 = Station AI
+highlight-station-ai-2 = "AI"
diff --git a/Resources/Locale/en-US/_DV/chat/ui/chat-box.ftl b/Resources/Locale/en-US/_DV/chat/ui/chat-box.ftl
new file mode 100644
index 0000000000..b45826ceec
--- /dev/null
+++ b/Resources/Locale/en-US/_DV/chat/ui/chat-box.ftl
@@ -0,0 +1,11 @@
+hud-chatbox-select-channels = Channels:
+
+hud-chatbox-auto-highlights = Automatic Highlights:
+hud-chatbox-highlights = Highlights:
+hud-chatbox-highlights-button = Submit
+hud-chatbox-highlights-tooltip = The words need to be separated by a newline,
+ if wrapped around " they will be highlighted
+ only if separated by spaces.
+hud-chatbox-highlights-placeholder = Urist
+ "Para"
+ Bridge
diff --git a/Resources/Locale/en-US/_DV/escape-menu/options-menu.ftl b/Resources/Locale/en-US/_DV/escape-menu/options-menu.ftl
index 494ad3f9db..e6ace8afed 100644
--- a/Resources/Locale/en-US/_DV/escape-menu/options-menu.ftl
+++ b/Resources/Locale/en-US/_DV/escape-menu/options-menu.ftl
@@ -13,3 +13,8 @@ ui-options-function-nano-chat-navigate-down-unread = Navigate down to next unrea
## DeltaV staff chats
ui-options-function-open-staff-chats-deltav = Open admin help and curator chat
+
+## DeltaV implementation of message highlights
+ui-options-auto-fill-highlights = Auto-fill the highlights with the character's information
+ui-options-highlights-color = Highlights color:
+ui-options-highlights-color-example = This is an highlighted text.
diff --git a/Resources/Prototypes/_DV/Catalog/Chat/highlights.yml b/Resources/Prototypes/_DV/Catalog/Chat/highlights.yml
new file mode 100644
index 0000000000..0c637f4015
--- /dev/null
+++ b/Resources/Prototypes/_DV/Catalog/Chat/highlights.yml
@@ -0,0 +1,334 @@
+# The IDs are generated from job names
+# the Prefixes are generated job IDs
+# The corresponding strings are located in Resources/Locale/en-US/_DV/chat/highlights.ftl
+
+- type: localizedDataset
+ id: ChatHighlightAdministrativeAssistant
+ values:
+ prefix: highlight-administrative-assistant-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightAtmosphericTechnician
+ values:
+ prefix: highlight-atmospheric-technician-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightBartender
+ values:
+ prefix: highlight-bartender-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightBorg
+ values:
+ prefix: highlight-cyborg-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightBotanist
+ values:
+ prefix: highlight-botanist-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightBoxer
+ values:
+ prefix: highlight-boxer-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightBrigmedic
+ values:
+ prefix: highlight-corpsman-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightCaptain
+ values:
+ prefix: highlight-captain-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightCargoAssistant
+ values:
+ prefix: highlight-cargo-assistant-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightCargoTechnician
+ values:
+ prefix: highlight-cargo-technician-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightCBURN
+ values:
+ prefix: highlight-centcomm-quarantine-officer-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightCentralCommandOfficial
+ values:
+ prefix: highlight-centcomm-official-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightChaplain
+ values:
+ prefix: highlight-chaplain-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightChef
+ values:
+ prefix: highlight-chef-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightChemist
+ values:
+ prefix: highlight-chemist-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightChiefEngineer
+ values:
+ prefix: highlight-chief-engineer-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightChiefJustice
+ values:
+ prefix: highlight-chief-justice-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightChiefMedicalOfficer
+ values:
+ prefix: highlight-chief-medical-officer-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightClerk
+ values:
+ prefix: highlight-clerk-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightClown
+ values:
+ prefix: highlight-clown-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightCourier
+ values:
+ prefix: highlight-courier-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightDeathSquad
+ values:
+ prefix: highlight-centcomm-agent-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightDetective
+ values:
+ prefix: highlight-detective-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightERTChaplain
+ values:
+ prefix: highlight-ert-chaplain-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightERTEngineer
+ values:
+ prefix: highlight-ert-engineer-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightERTJanitor
+ values:
+ prefix: highlight-ert-janitor-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightERTLeader
+ values:
+ prefix: highlight-ert-leader-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightERTMedical
+ values:
+ prefix: highlight-ert-medic-
+ count: 7
+- type: localizedDataset
+ id: ChatHighlightERTSecurity
+ values:
+ prefix: highlight-ert-security-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightForensicMantis
+ values:
+ prefix: highlight-psionic-mantis-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightGladiator
+ values:
+ prefix: highlight-gladiator-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightHeadOfPersonnel
+ values:
+ prefix: highlight-head-of-personnel-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightHeadOfSecurity
+ values:
+ prefix: highlight-head-of-security-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightJanitor
+ values:
+ prefix: highlight-janitor-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightLawyer
+ values:
+ prefix: highlight-attorney-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightLibrarian
+ values:
+ prefix: highlight-librarian-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightMartialArtist
+ values:
+ prefix: highlight-martial-artist-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightMedicalBorg
+ values:
+ prefix: highlight-medical-cyborg-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightMedicalDoctor
+ values:
+ prefix: highlight-medical-doctor-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightMedicalIntern
+ values:
+ prefix: highlight-medical-intern-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightMime
+ values:
+ prefix: highlight-mime-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightMusician
+ values:
+ prefix: highlight-musician-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightParamedic
+ values:
+ prefix: highlight-paramedic-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightPassenger
+ values:
+ prefix: highlight-passenger-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightPrisoner
+ values:
+ prefix: highlight-prisoner-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightPrisonGuard
+ values:
+ prefix: highlight-prison-guard-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightProsecutor
+ values:
+ prefix: highlight-prosecutor-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightPsychologist
+ values:
+ prefix: highlight-psychologist-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightQuartermaster
+ values:
+ prefix: highlight-logistics-officer-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightReporter
+ values:
+ prefix: highlight-reporter-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightResearchAssistant
+ values:
+ prefix: highlight-research-assistant-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightResearchDirector
+ values:
+ prefix: highlight-mystagogue-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightRoboticist
+ values:
+ prefix: highlight-roboticist-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightSalvageSpecialist
+ values:
+ prefix: highlight-salvage-specialist-
+ count: 7
+- type: localizedDataset
+ id: ChatHighlightScientist
+ values:
+ prefix: highlight-scientist-
+ count: 3
+- type: localizedDataset
+ id: ChatHighlightSecurityBorg
+ values:
+ prefix: highlight-security-cyborg-
+ count: 6
+- type: localizedDataset
+ id: ChatHighlightSecurityCadet
+ values:
+ prefix: highlight-security-cadet-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightSecurityOfficer
+ values:
+ prefix: highlight-security-officer-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightServiceWorker
+ values:
+ prefix: highlight-service-worker-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightStationAi
+ values:
+ prefix: highlight-station-ai-
+ count: 2
+- type: localizedDataset
+ id: ChatHighlightStationEngineer
+ values:
+ prefix: highlight-station-engineer-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightSurgeon
+ values:
+ prefix: highlight-surgeon-
+ count: 4
+- type: localizedDataset
+ id: ChatHighlightTechnicalAssistant
+ values:
+ prefix: highlight-technical-assistant-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightVisitor
+ values:
+ prefix: highlight-visitor-
+ count: 1
+- type: localizedDataset
+ id: ChatHighlightWarden
+ values:
+ prefix: highlight-warden-
+ count: 5
+- type: localizedDataset
+ id: ChatHighlightZookeeper
+ values:
+ prefix: highlight-zookeeper-
+ count: 2