diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 0a58f27818..6b984bf31a 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -1,3 +1,4 @@ +using Content.Client._White.Chat; using Content.Client._White.JoinQueue; using Content.Client._White.Jukebox; using Content.Client._White.Overlays; @@ -86,6 +87,7 @@ namespace Content.Client.Entry [Dependency] private readonly ClientJukeboxSongsSyncManager _jukeboxSyncManager = default!; [Dependency] private readonly TTSManager _ttsManager = default!; [Dependency] private readonly ReputationManager _reputationManager = default!; + [Dependency] private readonly IChatAbbreviationManager _chatAbbreviationManager = default!; //WD-EDIT public override void Init() @@ -154,6 +156,7 @@ namespace Content.Client.Entry //WD-EDIT _stalinManager.Initialize(); + _chatAbbreviationManager.Initialize(); //WD-EDIT //AUTOSCALING default Setup! diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 2b17dc1310..a2778afa92 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -1,3 +1,4 @@ +using Content.Client._White.Chat; using Content.Client._White.JoinQueue; using Content.Client._White.Jukebox; using Content.Client._White.Reputation; @@ -61,6 +62,7 @@ namespace Content.Client.IoC IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); //WD-EDIT } } diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs index c70e10cda1..1626adda6e 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs @@ -41,6 +41,8 @@ public partial class ChatBox : UIWidget _controller = UserInterfaceManager.GetUIController(); _controller.MessageAdded += OnMessageAdded; _controller.RegisterChat(this); + + InitializeExtension(); //WD EDIT } private void OnTextEntered(LineEditEventArgs args) diff --git a/Content.Client/_White/Chat/ChatAbbreviationManager.cs b/Content.Client/_White/Chat/ChatAbbreviationManager.cs new file mode 100644 index 0000000000..b3f74a17f0 --- /dev/null +++ b/Content.Client/_White/Chat/ChatAbbreviationManager.cs @@ -0,0 +1,61 @@ +using System.Linq; +using Robust.Client.ResourceManagement; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Mapping; +using Robust.Shared.Utility; + +namespace Content.Client._White.Chat; + +public sealed class ChatAbbreviationManager : IChatAbbreviationManager +{ + [Dependency] private readonly IResourceCache _resources = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + + public readonly string SuggestSeparator = "%"; + public List Worlds = new List(); + + public void Initialize() + { + LoadWords(new ResPath("/White/ChatFilters/slang.yml")); + } + + public void LoadWords(ResPath resPath) + { + try + { + var yaml = _resources.ContentFileReadYaml(resPath); + var node = yaml.Documents[0].RootNode.ToDataNodeCast(); + var data = _serializationManager.Read>(node, notNullableOverride : false); + Worlds = data.Select(s => new AbbreviatedWord(s.Key, s.Value)).ToList(); + } + catch (Exception e) + { + Logger.Error($"Shit happened!: {e}"); + } + } + + public (List, string) GetSuggestWord(string input) + { + var splited = input.Split(SuggestSeparator); + if (splited.Length <= 1) + return (new List(), string.Empty); + + splited = splited.Last().Split(" "); + if (splited.Length > 1) + return (new List(), string.Empty); + + var currentAbbreviation = splited[0]; + Logger.Debug($"Current shit: {currentAbbreviation}"); + return (Worlds.Where(a => a.Short.StartsWith(SuggestSeparator+currentAbbreviation)).ToList(),currentAbbreviation); + } +} + +public interface IChatAbbreviationManager +{ + public void Initialize(); + public void LoadWords(ResPath resPath); + public (List, string) GetSuggestWord(string input); +} + +public record struct AbbreviatedWord(string Short, string Word); diff --git a/Content.Client/_White/Chat/ChatBoxExtend.cs b/Content.Client/_White/Chat/ChatBoxExtend.cs new file mode 100644 index 0000000000..0fd026ad60 --- /dev/null +++ b/Content.Client/_White/Chat/ChatBoxExtend.cs @@ -0,0 +1,131 @@ +using System.Linq; +using System.Numerics; +using System.Text; +using Content.Client._White.Chat; +using Robust.Client.Graphics; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Utility; + +namespace Content.Client.UserInterface.Systems.Chat.Widgets; // НЕ ТРОГАТЬ НАХУЙ! ФИЧА БЛЯТЬ! + +#pragma warning disable RA0003 +public partial class ChatBox +#pragma warning restore RA0003 +{ + [Dependency] private readonly IChatAbbreviationManager _chatAbbreviationManager = default!; + + private readonly RichTextLabel _currentLabel = new(); + private readonly FormattedMessage _currentMessage = new(); + + private readonly RichTextLabel _fullLabel = new(); + private readonly FormattedMessage _fullMessage = new(); + + public void InitializeExtension() + { + ChatInput.Input.OnTextChanged += InputTextChanged; + ChatInput.Input.OnFocusEnter += OnEnter; + ChatInput.Input.OnFocusExit += OnExit; + ChatInput.Input.OnTextEntered += InputTextEntered; + + _currentLabel.SetMessage(_currentMessage); + _fullLabel.SetMessage(_fullMessage); + + ChatInput.AddChild(_currentLabel); + ChatInput.AddChild(_fullLabel); + } + + private void InputTextEntered(LineEdit.LineEditEventArgs obj) + { + _currentMessage.Clear(); + _fullMessage.Clear(); + } + + private void OnEnter(LineEdit.LineEditEventArgs obj) + { + _currentLabel.Visible = true; + _fullLabel.Visible = true; + } + + private void OnExit(LineEdit.LineEditEventArgs obj) + { + _currentLabel.Visible = false; + _fullLabel.Visible = false; + } + + private void InputTextChanged(LineEdit.LineEditEventArgs args) + { + _currentMessage.Clear(); + _fullMessage.Clear(); + _currentLabel.Visible = false; + _fullLabel.Visible = false; + + var font = GetFont(args.Control); + + var cursorPosShift = UIScale * 0f; + + var runes = args.Text.EnumerateRunes().ToArray(); + var (words,suggest) = _chatAbbreviationManager.GetSuggestWord(args.Text); + + if (words.Count == 0) + return; + + var count = 0; + var posX = 0; + + foreach (var rune in runes) + { + if (!font.TryGetCharMetrics(rune, UIScale, out var metrics)) + { + count += 1; + continue; + } + + posX += metrics.Advance; + count += rune.Utf16SequenceLength; + + if (count == args.Control.CursorPosition) + { + cursorPosShift = posX; + } + } + + _currentLabel.Margin = new Thickness(ChatInput.Input.Position.X + cursorPosShift,0,0,0); + _currentLabel.InvalidateMeasure(); + _fullLabel.Margin = new Thickness(ChatInput.Input.Position.X + cursorPosShift,0,0,0); + _fullLabel.InvalidateMeasure(); + + var limit = 0; + + foreach (var word in words) + { + limit++; + + if(limit > 4) continue; + + if (!_currentLabel.Visible) + _currentLabel.Visible = true; + if (!_fullLabel.Visible) + _fullLabel.Visible = true; + + _currentMessage.AddMarkup($"[color=#aaaaaa]{word.Short.Substring(suggest.Length+1)}[/color]\r"); + } + + if (words.Count > 0) + { + _fullMessage.PushNewline(); + _fullMessage.AddMarkup($"[font size=8][color=#aaaaaa]{words[0].Word}[/color][/font]\r"); + } + + } + + private Font GetFont(Control element) + { + if (element.TryGetStyleProperty("font", out var font)) + { + return font; + } + + return UserInterfaceManager.ThemeDefaults.DefaultFont; + } +} diff --git a/Resources/White/ChatFilters/slang.yml b/Resources/White/ChatFilters/slang.yml new file mode 100644 index 0000000000..1dc355be8f --- /dev/null +++ b/Resources/White/ChatFilters/slang.yml @@ -0,0 +1,55 @@ +"%срп": стандартные рабочие процедуры +"%дек": детектив +"%деку": детективу +"%дека": детектива +"%дэк": детектив +"%дэку": детективу +"%мед": медицинский +"%дэка": детектива +"%инжи": инженеры +"%инж": инженер +"%инжам": инженерам +"%инжы": инженеры +"%инжу": инженеру +"%таблы": таблетки +"%мш": имплант защиты разума +"%мщ": имплант защиты разума +"%разгерм": разгерметизация +"%разгерма": разгерметизация +"%разгерму": разгерметизацию +"%разгерме": разгерметизации +"%разгермы": разгерметизации +"%крит": критическое состояние +"%крите": критическом состоянии +"%рева": революция +"%рёва": революция +"%рево": революция +"%рев": революционер +"%хз": я не знаю +"%магмы": магнитные ботинки +"%изоли": изолирующие перчатки +"%изольки": изолирующие перчатки +"%изолек": изолирующих перчаток +"%кз": космический закон +"%синга": сингулярность +"%синг": сингулярность +"%сингу": сингулярность +"%синги": сингулярности +"%яо": ядерные оперативники +"%яой": ядерные оперативники +"%яошники": ядерные оперативники +"%яойшики": ядерные оперативники +"%яойники": ядерные оперативники +"%уч": ускоритель частиц +"%спс": спасибо +"%плиз": пожалуйста +"%эвак": эвакуацию +"%вв": высоковольтные +"%св": средневольтные +"%нв": низковольтные +"%кк": красный код +"%зк": зеленый код +"%ск": синий код +"%жк": желтый код +"%стим": стимулятор +"%стимы": стимуляторы