diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 18f03cd7db0..8f1261034c8 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -47,6 +47,10 @@ public void SendMessage(string text, ChatSelectChannel channel) _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\""); break; + case ChatSelectChannel.HiddenEmotes: + _consoleHost.ExecuteCommand($"hme \"{CommandParsing.Escape(str)}\""); + break; + case ChatSelectChannel.Dead: if (_systems.GetEntitySystemOrNull() is {IsGhost: true}) goto case ChatSelectChannel.Local; diff --git a/Content.Client/Chat/UI/SpeechBubble.cs b/Content.Client/Chat/UI/SpeechBubble.cs index 68c937a7885..e1127307e30 100644 --- a/Content.Client/Chat/UI/SpeechBubble.cs +++ b/Content.Client/Chat/UI/SpeechBubble.cs @@ -20,6 +20,7 @@ public abstract class SpeechBubble : Control public enum SpeechType : byte { Emote, + HiddenEmote, Say, Whisper, Looc @@ -65,6 +66,9 @@ public static SpeechBubble CreateSpeechBubble(SpeechType type, ChatMessage messa case SpeechType.Emote: return new TextSpeechBubble(message, senderEntity, "emoteBox"); + case SpeechType.HiddenEmote: + return new TextSpeechBubble(message, senderEntity, "emoteBox", Color.FromHex("#ffd29e")); + case SpeechType.Say: return new FancyTextSpeechBubble(message, senderEntity, "sayBox"); diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 9a49c4e28c1..675330b0ebc 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -15,6 +15,7 @@ public static void SetupContexts(IInputContextContainer contexts) common.AddFunction(ContentKeyFunctions.FocusChat); common.AddFunction(ContentKeyFunctions.FocusLocalChat); common.AddFunction(ContentKeyFunctions.FocusEmote); + common.AddFunction(ContentKeyFunctions.FocusHiddenEmote); common.AddFunction(ContentKeyFunctions.FocusWhisperChat); common.AddFunction(ContentKeyFunctions.FocusRadio); common.AddFunction(ContentKeyFunctions.FocusLOOC); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index cb76a7d8912..165085657a9 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -232,6 +232,7 @@ void AddCheckBox(string checkBoxName, bool currentState, Action FocusChannel(ChatSelectChannel.Emotes))); + _input.SetInputCommand(ContentKeyFunctions.FocusHiddenEmote, + InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.HiddenEmotes))); + _input.SetInputCommand(ContentKeyFunctions.FocusWhisperChat, InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Whisper))); @@ -529,6 +534,7 @@ private void UpdateChannelPermissions() FilterableChannels |= ChatChannel.Whisper; FilterableChannels |= ChatChannel.Radio; FilterableChannels |= ChatChannel.Emotes; + FilterableChannels |= ChatChannel.HiddenEmotes; FilterableChannels |= ChatChannel.Notifications; // Can only send local / radio / emote when attached to a non-ghost entity. @@ -539,6 +545,7 @@ private void UpdateChannelPermissions() CanSendChannels |= ChatSelectChannel.Whisper; CanSendChannels |= ChatSelectChannel.Radio; CanSendChannels |= ChatSelectChannel.Emotes; + CanSendChannels |= ChatSelectChannel.HiddenEmotes; } } @@ -875,6 +882,10 @@ public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true) AddSpeechBubble(msg, SpeechBubble.SpeechType.Emote); break; + case ChatChannel.HiddenEmotes: + AddSpeechBubble(msg, SpeechBubble.SpeechType.HiddenEmote); + break; + case ChatChannel.LOOC: if (_config.GetCVar(CCVars.LoocAboveHeadShow)) AddSpeechBubble(msg, SpeechBubble.SpeechType.Looc); diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs index 1d2a4314462..5e451260d19 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs @@ -15,6 +15,7 @@ public sealed partial class ChannelFilterPopup : Popup ChatChannel.Local, ChatChannel.Whisper, ChatChannel.Emotes, + ChatChannel.HiddenEmotes, ChatChannel.Radio, ChatChannel.Telepathic, //Nyano - Summary: adds telepathic chat to where it belongs in order in the chat. ChatChannel.Notifications, diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs index c1f3559d793..74c35354459 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs @@ -12,6 +12,7 @@ public sealed class ChannelSelectorPopup : Popup ChatSelectChannel.Local, ChatSelectChannel.Whisper, ChatSelectChannel.Emotes, + ChatSelectChannel.HiddenEmotes, ChatSelectChannel.Radio, ChatSelectChannel.Telepathic, //Nyano - Summary: determines the order in which telepathic shows. ChatSelectChannel.LOOC, diff --git a/Content.Server/Chat/Commands/HiddenMeCommand.cs b/Content.Server/Chat/Commands/HiddenMeCommand.cs new file mode 100644 index 00000000000..43434a79b55 --- /dev/null +++ b/Content.Server/Chat/Commands/HiddenMeCommand.cs @@ -0,0 +1,44 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Content.Shared.Chat; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Chat.Commands +{ + [AnyCommand] + internal sealed class HiddenMeCommand : IConsoleCommand + { + public string Command => "hme"; + public string Description => "Perform an action."; + public string Help => "hme "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not {} playerEntity) + { + shell.WriteError("You don't have an entity!"); + return; + } + + if (args.Length < 1) + return; + + var message = string.Join(" ", args).Trim(); + if (string.IsNullOrEmpty(message)) + return; + + IoCManager.Resolve().GetEntitySystem() + .TrySendInGameICMessage(playerEntity, message, InGameICChatType.HiddenEmote, ChatTransmitRange.GhostRangeLimit, false, shell, player); + } + } +} diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 73490afd26d..fb2720d2f4d 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -229,7 +229,7 @@ public void TrySendInGameICMessage( var language = languageOverride ?? _language.GetLanguage(source); - bool shouldCapitalize = (desiredType != InGameICChatType.Emote); + bool shouldCapitalize = (desiredType != InGameICChatType.Emote && desiredType != InGameICChatType.HiddenEmote); bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation); // Capitalizing the word I only happens in English, so we check language here bool shouldCapitalizeTheWordI = (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") @@ -273,6 +273,9 @@ public void TrySendInGameICMessage( case InGameICChatType.Emote: SendEntityEmote(source, message, range, nameOverride, language, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); break; + case InGameICChatType.HiddenEmote: + SendHiddenEntityEmote(source, message, range, nameOverride, language, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); + break; //Nyano - Summary: case adds the telepathic chat sending ability. case InGameICChatType.Telepathic: _telepath.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat); @@ -601,6 +604,62 @@ private void SendEntityEmote( _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user}: {action}"); } + + private void SendHiddenEntityEmote( + EntityUid source, + string action, + ChatTransmitRange range, + string? nameOverride, + LanguagePrototype language, + bool hideLog = false, + bool checkEmote = true, + bool ignoreActionBlocker = false, + NetUserId? author = null + ) + { + if (!_actionBlocker.CanEmote(source) && !ignoreActionBlocker) + return; + + var ent = Identity.Entity(source, EntityManager); + string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent)); + + var coloredName = $"[color=#FFD29E]{name}[/color]"; + var coloredAction = $"[color=#FFD29E]{FormattedMessage.RemoveMarkup(action)}[/color]"; + + var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message", + ("entityName", coloredName), + ("entity", ent), + ("message", coloredAction)); + + if (checkEmote) + TryEmoteChatInput(source, action); + + float hiddenEmoteRange = 0.3f; + + foreach (var (session, data) in GetRecipients(source, hiddenEmoteRange)) + { + if (session.AttachedEntity is not { Valid: true } listener) + continue; + + if (Transform(session.AttachedEntity.Value).GridUid != Transform(source).GridUid + && !CheckAttachedGrids(source, session.AttachedEntity.Value)) + continue; + + if (data.Range <= hiddenEmoteRange) + { + _chatManager.ChatMessageToOne(ChatChannel.HiddenEmotes, action, wrappedMessage, source, false, session.Channel); + } + } + + if (!hideLog) + if (name != Name(source)) + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Hidden emote from {ToPrettyString(source):user} as {name}: {action}"); + else + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Hidden emote from {ToPrettyString(source):user}: {action}"); + } + + + // ReSharper disable once InconsistentNaming private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat) { diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index e3810342b65..801f72130d3 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -4,7 +4,7 @@ namespace Content.Shared.Chat /// Represents chat channels that the player can filter chat tabs by. /// [Flags, Serializable] - public enum ChatChannel : ushort + public enum ChatChannel : uint { None = 0, @@ -60,40 +60,45 @@ public enum ChatChannel : ushort /// Emotes = 1 << 9, + /// + /// HiddenEmotes + /// + HiddenEmotes = 1 << 10, + /// /// Deadchat /// - Dead = 1 << 10, + Dead = 1 << 11, /// /// Misc admin messages /// - Admin = 1 << 11, + Admin = 1 << 12, /// /// Admin alerts, messages likely of elevated importance to admins /// - AdminAlert = 1 << 12, + AdminAlert = 1 << 13, /// /// Admin chat /// - AdminChat = 1 << 13, + AdminChat = 1 << 14, /// /// Unspecified. /// - Unspecified = 1 << 14, + Unspecified = 1 << 15, /// /// Nyano - Summary:: Telepathic channel for all psionic entities. /// - Telepathic = 1 << 15, + Telepathic = 1 << 16, /// /// Channels considered to be IC. /// - IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Telepathic | Notifications, //Nyano - Summary: Adds telepathic as an 'IC' labelled chat.. + IC = Local | Whisper | Radio | Dead | Emotes | HiddenEmotes | Damage | Visual | Telepathic | Notifications, //Nyano - Summary: Adds telepathic as an 'IC' labelled chat.. AdminRelated = Admin | AdminAlert | AdminChat, } diff --git a/Content.Shared/Chat/ChatSelectChannel.cs b/Content.Shared/Chat/ChatSelectChannel.cs index 5104bbc3068..972d588598d 100644 --- a/Content.Shared/Chat/ChatSelectChannel.cs +++ b/Content.Shared/Chat/ChatSelectChannel.cs @@ -7,7 +7,7 @@ /// Maps to , giving better names. /// [Flags] - public enum ChatSelectChannel : ushort + public enum ChatSelectChannel : uint { None = 0, @@ -41,6 +41,11 @@ public enum ChatSelectChannel : ushort /// Emotes = ChatChannel.Emotes, + /// + /// HiddenEmotes + /// + HiddenEmotes = ChatChannel.HiddenEmotes, + /// /// Deadchat /// diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index 3ea02a30f7f..bb61f920ec6 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -20,6 +20,7 @@ public abstract class SharedChatSystem : EntitySystem public const char OOCPrefix = '['; public const char EmotesPrefix = '%'; // Corvax-Localization public const char EmotesAltPrefix = '*'; + public const char HiddenEmotesPrefix = '+'; public const char AdminPrefix = ']'; public const char WhisperPrefix = ','; public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix. @@ -265,6 +266,7 @@ public enum InGameICChatType : byte { Speak, Emote, + HiddenEmote, Whisper, Telepathic } diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 79bc474189c..416fa900abc 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -14,6 +14,7 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction FocusChat = "FocusChatInputWindow"; public static readonly BoundKeyFunction FocusLocalChat = "FocusLocalChatWindow"; public static readonly BoundKeyFunction FocusEmote = "FocusEmote"; + public static readonly BoundKeyFunction FocusHiddenEmote = "FocusHiddenEmote"; public static readonly BoundKeyFunction FocusWhisperChat = "FocusWhisperChatWindow"; public static readonly BoundKeyFunction FocusRadio = "FocusRadioWindow"; public static readonly BoundKeyFunction FocusLOOC = "FocusLOOCWindow"; diff --git a/Resources/Locale/en-US/chat/ui/chat-box.ftl b/Resources/Locale/en-US/chat/ui/chat-box.ftl index 720f0d15ab4..c2feb3e3a8e 100644 --- a/Resources/Locale/en-US/chat/ui/chat-box.ftl +++ b/Resources/Locale/en-US/chat/ui/chat-box.ftl @@ -8,6 +8,7 @@ hud-chatbox-select-channel-Admin = Admin hud-chatbox-select-channel-Console = Console hud-chatbox-select-channel-Dead = Dead hud-chatbox-select-channel-Emotes = Emotes +hud-chatbox-select-channel-HiddenEmotes = Tet-a-tet hud-chatbox-select-channel-Local = Local hud-chatbox-select-channel-Whisper = Whisper hud-chatbox-select-channel-LOOC = LOOC @@ -21,6 +22,7 @@ hud-chatbox-channel-AdminAlert = Admin Alert hud-chatbox-channel-AdminChat = Admin Chat hud-chatbox-channel-Dead = Dead hud-chatbox-channel-Emotes = Emotes +hud-chatbox-channel-HiddenEmotes = Tet-a-tet hud-chatbox-channel-Local = Local hud-chatbox-channel-Whisper = Whisper hud-chatbox-channel-LOOC = LOOC diff --git a/Resources/Locale/ru-RU/chat/ui/chat-box.ftl b/Resources/Locale/ru-RU/chat/ui/chat-box.ftl index dda8ff1aec2..98028ab871d 100644 --- a/Resources/Locale/ru-RU/chat/ui/chat-box.ftl +++ b/Resources/Locale/ru-RU/chat/ui/chat-box.ftl @@ -7,6 +7,7 @@ hud-chatbox-select-channel-Admin = Админ hud-chatbox-select-channel-Console = Консоль hud-chatbox-select-channel-Dead = Мёртвые hud-chatbox-select-channel-Emotes = Эмоции +hud-chatbox-select-channel-HiddenEmotes = Тет-а-тет hud-chatbox-select-channel-Local = Рядом hud-chatbox-select-channel-Whisper = Шёпот hud-chatbox-select-channel-LOOC = LOOC @@ -19,6 +20,7 @@ hud-chatbox-channel-AdminAlert = Админ Уведомления hud-chatbox-channel-AdminChat = Админ Чат hud-chatbox-channel-Dead = Мёртвые hud-chatbox-channel-Emotes = Эмоции +hud-chatbox-channel-HiddenEmotes = Тет-а-тет hud-chatbox-channel-Local = Рядом hud-chatbox-channel-Whisper = Шёпот hud-chatbox-channel-LOOC = LOOC