diff --git a/Content.Server/Administration/Commands/DSay.cs b/Content.Server/Administration/Commands/DSay.cs index 61b47d78567..935387d24f1 100644 --- a/Content.Server/Administration/Commands/DSay.cs +++ b/Content.Server/Administration/Commands/DSay.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; namespace Content.Server.Administration.Commands diff --git a/Content.Server/Administration/Commands/OSay.cs b/Content.Server/Administration/Commands/OSay.cs index 2f17bd9d70a..9c5a20ef693 100644 --- a/Content.Server/Administration/Commands/OSay.cs +++ b/Content.Server/Administration/Commands/OSay.cs @@ -2,6 +2,7 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Content.Shared.Database; using Robust.Shared.Console; diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs index 28fa01628f4..e34506deac4 100644 --- a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Advertise.Components; using Content.Server.Chat.Systems; using Content.Server.Power.Components; +using Content.Shared.Chat; using Content.Shared.VendingMachines; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs index 048f59b8d33..939fc9a2dbb 100644 --- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Chat.Systems; using Content.Server.UserInterface; using Content.Shared.Advertise; +using Content.Shared.Chat; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/Chat/Commands/LOOCCommand.cs b/Content.Server/Chat/Commands/LOOCCommand.cs index 9e16193fc38..c347f14bd1c 100644 --- a/Content.Server/Chat/Commands/LOOCCommand.cs +++ b/Content.Server/Chat/Commands/LOOCCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Chat/Commands/MeCommand.cs b/Content.Server/Chat/Commands/MeCommand.cs index e763d5656e1..9dff32b6579 100644 --- a/Content.Server/Chat/Commands/MeCommand.cs +++ b/Content.Server/Chat/Commands/MeCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Chat/Commands/SayCommand.cs b/Content.Server/Chat/Commands/SayCommand.cs index 273f908c9ab..a31ddbdb7a2 100644 --- a/Content.Server/Chat/Commands/SayCommand.cs +++ b/Content.Server/Chat/Commands/SayCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Chat/Commands/WhisperCommand.cs b/Content.Server/Chat/Commands/WhisperCommand.cs index c88e2519ee6..db967dbbb2d 100644 --- a/Content.Server/Chat/Commands/WhisperCommand.cs +++ b/Content.Server/Chat/Commands/WhisperCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Chat/Systems/AutoEmoteSystem.cs b/Content.Server/Chat/Systems/AutoEmoteSystem.cs index 3d6bd535401..8e02f7f53f1 100644 --- a/Content.Server/Chat/Systems/AutoEmoteSystem.cs +++ b/Content.Server/Chat/Systems/AutoEmoteSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Random; diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index bd1b944db30..c18b945ec6f 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -1,5 +1,6 @@ using System.Collections.Frozen; using System.Linq; +using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -87,7 +88,8 @@ public void TryEmoteWithChat( { // not all emotes are loc'd, but for the ones that are we pass in entity var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source)); - SendEntityEmote(source, action, range, nameOverride, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); + var language = _language.GetLanguage(source); + SendEntityEmote(source, action, range, nameOverride, language, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); } // do the rest of emote event logic here diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 05342dbe3c0..022520abeb0 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -6,12 +6,9 @@ using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Server.Language; -using Content.Server.Speech; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; using Content.Server.Nyanotrasen.Chat; -using Content.Server.Speech.Components; -using Content.Server.Speech.EntitySystems; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared.ActionBlocker; @@ -20,9 +17,9 @@ using Content.Shared.Database; using Content.Shared.Ghost; using Content.Shared.Language; -using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; using Content.Shared.Interaction; +using Content.Shared.Language.Systems; using Content.Shared.Mobs.Systems; using Content.Shared.Players; using Content.Shared.Radio; @@ -45,6 +42,10 @@ namespace Content.Server.Chat.Systems; +// Dear contributor. When I was introducing changes to this system only god and I knew what I was doing. +// Now only god knows. Please don't touch this code ever again. If you do have to, increment this counter as a warning for others: +// TOTAL_HOURS_WASTED_HERE_EE = 17 + // TODO refactor whatever active warzone this class and chatmanager have become /// /// ChatSystem is responsible for in-simulation chat handling, such as whispering, speaking, emoting, etc. @@ -227,6 +228,8 @@ public void TrySendInGameICMessage( message = message[1..]; } + var language = languageOverride ?? _language.GetLanguage(source); + bool shouldCapitalize = (desiredType != InGameICChatType.Emote); bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation); // Capitalizing the word I only happens in English, so we check language here @@ -238,30 +241,23 @@ public void TrySendInGameICMessage( // Was there an emote in the message? If so, send it. if (player != null && emoteStr != message && emoteStr != null) { - SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker); + SendEntityEmote(source, emoteStr, range, nameOverride, language, ignoreActionBlocker); } // This can happen if the entire string is sanitized out. if (string.IsNullOrEmpty(message)) return; - // Check if the message is in sign language - if (desiredType == InGameICChatType.Speak || desiredType == InGameICChatType.Whisper) - { - var language = languageOverride ?? _language.GetLanguage(source); - if (language.SignLanguage ?? false) - { - SendEntityEmote(source, message, range, nameOverride, ignoreActionBlocker, signLanguage: true, languageOverride: languageOverride); - return; - } - } + // This is really terrible. I hate myself for doing this. + if (language.SpeechOverride.ChatTypeOverride is { } chatTypeOverride) + desiredType = chatTypeOverride; // This message may have a radio prefix, and should then be whispered to the resolved radio channel if (checkRadioPrefix) { if (TryProccessRadioMessage(source, message, out var modMessage, out var channel)) { - SendEntityWhisper(source, modMessage, range, channel, nameOverride, hideLog, ignoreActionBlocker); + SendEntityWhisper(source, modMessage, range, channel, nameOverride, language, hideLog, ignoreActionBlocker); return; } } @@ -270,13 +266,13 @@ public void TrySendInGameICMessage( switch (desiredType) { case InGameICChatType.Speak: - SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); + SendEntitySpeak(source, message, range, nameOverride, language, hideLog, ignoreActionBlocker); break; case InGameICChatType.Whisper: - SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); + SendEntityWhisper(source, message, range, null, nameOverride, language, hideLog, ignoreActionBlocker); break; case InGameICChatType.Emote: - SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); + SendEntityEmote(source, message, range, nameOverride, language, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); break; //Nyano - Summary: case adds the telepathic chat sending ability. case InGameICChatType.Telepathic: @@ -402,16 +398,16 @@ private void SendEntitySpeak( string originalMessage, ChatTransmitRange range, string? nameOverride, + LanguagePrototype language, bool hideLog = false, - bool ignoreActionBlocker = false, - LanguagePrototype? languageOverride = null + bool ignoreActionBlocker = false ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; // The original message - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage)); + var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); if (message.Length == 0) return; @@ -434,15 +430,13 @@ private void SendEntitySpeak( speech = proto; } - var language = languageOverride ?? _language.GetLanguage(source); - name = FormattedMessage.EscapeText(name); // The chat message wrapped in a "x says y" string - var wrappedMessage = WrapPublicMessage(source, name, message, languageOverride: language); + var wrappedMessage = WrapPublicMessage(source, name, message, language: language); // The chat message obfuscated via language obfuscation var obfuscated = SanitizeInGameICMessage(source, _language.ObfuscateSpeech(message, language), out var emoteStr, true, _configurationManager.GetCVar(CCVars.ChatPunctuation), (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); // The language-obfuscated message wrapped in a "x says y" string - var wrappedObfuscated = WrapPublicMessage(source, name, obfuscated, languageOverride: language); + var wrappedObfuscated = WrapPublicMessage(source, name, obfuscated, language: language); SendInVoiceRange(ChatChannel.Local, name, message, wrappedMessage, obfuscated, wrappedObfuscated, source, range, languageOverride: language); @@ -478,15 +472,15 @@ private void SendEntityWhisper( ChatTransmitRange range, RadioChannelPrototype? channel, string? nameOverride, + LanguagePrototype language, bool hideLog = false, - bool ignoreActionBlocker = false, - LanguagePrototype? languageOverride = null + bool ignoreActionBlocker = false ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage)); + var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); if (message.Length == 0) return; @@ -506,7 +500,6 @@ private void SendEntityWhisper( } name = FormattedMessage.EscapeText(name); - var language = languageOverride ?? _language.GetLanguage(source); var languageObfuscatedMessage = SanitizeInGameICMessage(source, _language.ObfuscateSpeech(message, language), out var emoteStr, true, _configurationManager.GetCVar(CCVars.ChatPunctuation), (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); foreach (var (session, data) in GetRecipients(source, Transform(source).GridUid == null ? 0.3f : WhisperMuffledRange)) @@ -523,7 +516,7 @@ private void SendEntityWhisper( var canUnderstandLanguage = _language.CanUnderstand(listener, language.ID); // How the entity perceives the message depends on whether it can understand its language - var perceivedMessage = canUnderstandLanguage ? message : languageObfuscatedMessage; + var perceivedMessage = FormattedMessage.EscapeText(canUnderstandLanguage ? message : languageObfuscatedMessage); // Result is the intermediate message derived from the perceived one via obfuscation // Wrapped message is the result wrapped in an "x says y" string @@ -532,33 +525,26 @@ private void SendEntityWhisper( { // Scenario 1: the listener can clearly understand the message result = perceivedMessage; - wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("color", language.Color ?? Color.Gray), - ("entityName", name), - ("message", FormattedMessage.EscapeText(result))); + wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", name, perceivedMessage, language); } else if (_interactionSystem.InRangeUnobstructed(source, listener, WhisperMuffledRange, Shared.Physics.CollisionGroup.Opaque)) { // Scenerio 2: if the listener is too far, they only hear fragments of the message - // Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind result = ObfuscateMessageReadability(perceivedMessage); - wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("entityName", nameIdentity), ("color", language.Color ?? Color.Gray), ("message", FormattedMessage.EscapeText(result))); + wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", nameIdentity, perceivedMessage, language); } else { // Scenario 3: If listener is too far and has no line of sight, they can't identify the whisperer's identity result = ObfuscateMessageReadability(perceivedMessage); - wrappedMessage = Loc.GetString("chat-manager-entity-whisper-unknown-wrap-message", - ("color", language.Color ?? Color.Gray), - ("message", FormattedMessage.EscapeText(result))); + wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-unknown-wrap-message", string.Empty, perceivedMessage, language); } _chatManager.ChatMessageToOne(ChatChannel.Whisper, result, wrappedMessage, source, false, session.Channel); } var replayWrap = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("color", language.Color ?? Color.Gray), + ("color", language.SpeechOverride.Color), ("entityName", name), ("message", FormattedMessage.EscapeText(message))); _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, replayWrap, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); @@ -589,12 +575,11 @@ private void SendEntityEmote( string action, ChatTransmitRange range, string? nameOverride, + LanguagePrototype language, bool hideLog = false, bool checkEmote = true, bool ignoreActionBlocker = false, - NetUserId? author = null, - LanguagePrototype? languageOverride = null, - bool? signLanguage = false + NetUserId? author = null ) { if (!_actionBlocker.CanEmote(source) && !ignoreActionBlocker) @@ -604,32 +589,15 @@ private void SendEntityEmote( var ent = Identity.Entity(source, EntityManager); string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent)); - var language = languageOverride ?? _language.GetLanguage(source); - // Emotes use Identity.Name, since it doesn't actually involve your voice at all. - var wrappedMessage = ""; - var obfuscatedWrappedMessage = ""; - if (signLanguage == true) - { - wrappedMessage = Loc.GetString("entity-signlanguage-message", - ("entityName", name), - ("message", FormattedMessage.EscapeText(action))); - - obfuscatedWrappedMessage = Loc.GetString(_language.ObfuscateSpeech(action, language), - ("entityName", name)); - } - else - { - wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message", - ("entityName", name), - ("entity", ent), - ("message", FormattedMessage.RemoveMarkup(action))); - - } + var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message", + ("entityName", name), + ("entity", ent), + ("message", FormattedMessage.RemoveMarkup(action))); if (checkEmote) TryEmoteChatInput(source, action); - SendInVoiceRange(ChatChannel.Emotes, name, action, wrappedMessage, obfuscated: "", obfuscatedWrappedMessage, source, range, author, signLanguage: true); + SendInVoiceRange(ChatChannel.Emotes, name, action, wrappedMessage, obfuscated: "", obfuscatedWrappedMessage: "", source, range, author); if (!hideLog) if (name != Name(source)) _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}"); @@ -743,7 +711,7 @@ private MessageRangeCheckResult MessageRangeCheck(ICommonSession session, ICChat /// /// Sends a chat message to the given players in range of the source entity. /// - private void SendInVoiceRange(ChatChannel channel, string name, string message, string wrappedMessage, string obfuscated, string obfuscatedWrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null, LanguagePrototype? languageOverride = null, bool? signLanguage = false) + private void SendInVoiceRange(ChatChannel channel, string name, string message, string wrappedMessage, string obfuscated, string obfuscatedWrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null, LanguagePrototype? languageOverride = null) { var language = languageOverride ?? _language.GetLanguage(source); foreach (var (session, data) in GetRecipients(source, Transform(source).GridUid == null ? 0.3f : VoiceRange)) @@ -761,17 +729,9 @@ private void SendInVoiceRange(ChatChannel channel, string name, string message, continue; EntityUid listener = session.AttachedEntity.Value; - // Quickly Checking if the Emote is a real one or Sign Language. - var notSignLanguage = false; - if (channel == ChatChannel.Emotes) - { - notSignLanguage = true; - if (signLanguage == true) - notSignLanguage = false; - } // If the channel does not support languages, or the entity can understand the message, send the original message, otherwise send the obfuscated version - if (channel == ChatChannel.LOOC || notSignLanguage || _language.CanUnderstand(listener, language.ID)) + if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language.ID)) { _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author); } @@ -836,8 +796,11 @@ private string SanitizeInGameOOCMessage(string message) return newMessage; } - public string TransformSpeech(EntityUid sender, string message) + public string TransformSpeech(EntityUid sender, string message, LanguagePrototype language) { + if (!language.SpeechOverride.RequireSpeech) + return message; // Do not apply speech accents if there's no speech involved. + var ev = new TransformSpeechEvent(sender, message); RaiseLocalEvent(ev); @@ -891,16 +854,40 @@ public string SanitizeMessageReplaceWords(string message) /// /// Wraps a message sent by the specified entity into an "x says y" string. /// - public string WrapPublicMessage(EntityUid source, string name, string message, LanguagePrototype? languageOverride = null) + public string WrapPublicMessage(EntityUid source, string name, string message, LanguagePrototype? language = null) { - var language = languageOverride ?? _language.GetLanguage(source); + var wrapId = GetSpeechVerb(source, message).Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message"; + return WrapMessage(wrapId, InGameICChatType.Speak, source, name, message, language); + } + + /// + /// Wraps a message whispered by the specified entity into an "x whispers y" string. + /// + public string WrapWhisperMessage(EntityUid source, LocId defaultWrap, string entityName, string message, LanguagePrototype? language = null) + { + return WrapMessage(defaultWrap, InGameICChatType.Whisper, source, entityName, message, language); + } + + /// + /// Wraps a message sent by the specified entity into the specified wrap string. + /// + public string WrapMessage(LocId wrapId, InGameICChatType chatType, EntityUid source, string entityName, string message, LanguagePrototype? language) + { + language ??= _language.GetLanguage(source); + if (language.SpeechOverride.MessageWrapOverrides.TryGetValue(chatType, out var wrapOverride)) + wrapId = wrapOverride; + var speech = GetSpeechVerb(source, message); - return Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message", - ("color", language.Color ?? Color.White), - ("entityName", name), - ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), - ("fontType", language.FontId ?? speech.FontId), - ("fontSize", language.FontSize ?? speech.FontSize), + var verbId = language.SpeechOverride.SpeechVerbOverrides is { } verbsOverride + ? _random.Pick(verbsOverride).ToString() + : _random.Pick(speech.SpeechVerbStrings); + + return Loc.GetString(wrapId, + ("color", language.SpeechOverride.Color), + ("entityName", entityName), + ("verb", Loc.GetString(verbId)), + ("fontType", language.SpeechOverride.FontId ?? speech.FontId), + ("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize), ("message", message)); } @@ -1070,39 +1057,3 @@ public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? Language = language; } } - -/// -/// InGame IC chat is for chat that is specifically ingame (not lobby) but is also in character, i.e. speaking. -/// -// ReSharper disable once InconsistentNaming -public enum InGameICChatType : byte -{ - Speak, - Emote, - Whisper, - Telepathic //Nyano - Summary: adds telepathic as a type of message users can receive. -} - -/// -/// InGame OOC chat is for chat that is specifically ingame (not lobby) but is OOC, like deadchat or LOOC. -/// -public enum InGameOOCChatType : byte -{ - Looc, - Dead -} - -/// -/// Controls transmission of chat. -/// -public enum ChatTransmitRange : byte -{ - /// Acts normal, ghosts can hear across the map, etc. - Normal, - /// Normal but ghosts are still range-limited. - GhostRangeLimit, - /// Hidden from the chat window. - HideChat, - /// Ghosts can't hear or see it at all. Regular players can if in-range. - NoGhosts -} diff --git a/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs b/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs index 878c517d924..98ba415b968 100644 --- a/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs +++ b/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs @@ -1,3 +1,5 @@ +using Content.Shared.Chat; + namespace Content.Server.Chat.Systems; using Content.Shared.Chat.Prototypes; diff --git a/Content.Server/Chemistry/ReagentEffects/Emote.cs b/Content.Server/Chemistry/ReagentEffects/Emote.cs index a4d49e4ad1c..729f0671217 100644 --- a/Content.Server/Chemistry/ReagentEffects/Emote.cs +++ b/Content.Server/Chemistry/ReagentEffects/Emote.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Systems; +using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.Chemistry.Reagent; using JetBrains.Annotations; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 00612833676..92e658591a0 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -49,6 +49,7 @@ using Content.Shared.SSDIndicator; using Content.Shared.Damage.ForceSay; using Content.Server.Polymorph.Components; +using Content.Shared.Chat; namespace Content.Server.Cloning { @@ -248,7 +249,7 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(mob); diff --git a/Content.Server/Cluwne/CluwneSystem.cs b/Content.Server/Cluwne/CluwneSystem.cs index c170886a803..bd7b7a66201 100644 --- a/Content.Server/Cluwne/CluwneSystem.cs +++ b/Content.Server/Cluwne/CluwneSystem.cs @@ -12,6 +12,7 @@ using Robust.Shared.Prototypes; using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; +using Content.Shared.Chat; using Content.Shared.Cluwne; using Content.Shared.Interaction.Components; using Robust.Shared.Audio; diff --git a/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs index 69bd37e674d..b4e99e6199d 100644 --- a/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs +++ b/Content.Server/DeltaV/NPC/Roboisseur/RoboisseurSystem.cs @@ -7,6 +7,7 @@ using Content.Shared.Kitchen; using Robust.Server.GameObjects; using Content.Server.Materials; +using Content.Shared.Chat; using Robust.Shared.Player; using Robust.Shared.Timing; diff --git a/Content.Server/Language/Commands/SayLanguageCommand.cs b/Content.Server/Language/Commands/SayLanguageCommand.cs index 2304781fa04..caca2f41cf8 100644 --- a/Content.Server/Language/Commands/SayLanguageCommand.cs +++ b/Content.Server/Language/Commands/SayLanguageCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index 86555924712..18602c3de8b 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Weapons.Ranged.Systems; using Content.Shared.Actions; using Content.Shared.Body.Components; +using Content.Shared.Chat; using Content.Shared.Coordinates.Helpers; using Content.Shared.DoAfter; using Content.Shared.Doors.Components; diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index bf2f8805326..7b0b4110201 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -6,6 +6,7 @@ using Content.Server.Ghost; using Content.Server.Popups; using Content.Server.PowerCell; +using Content.Shared.Chat; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Interaction; diff --git a/Content.Server/Mobs/CritMobActionsSystem.cs b/Content.Server/Mobs/CritMobActionsSystem.cs index c897102dca7..963bd98d0f4 100644 --- a/Content.Server/Mobs/CritMobActionsSystem.cs +++ b/Content.Server/Mobs/CritMobActionsSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Chat.Systems; using Content.Server.Popups; using Content.Server.Speech.Muting; +using Content.Shared.Chat; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs index cf07831959b..7163af5f651 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Systems; +using Content.Shared.Chat; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs index 01cfbd113d9..467da4739b7 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs @@ -1,6 +1,7 @@ using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.NPC.Components; +using Content.Shared.Chat; using Content.Shared.Damage; using Content.Shared.Emag.Components; using Content.Shared.Interaction; diff --git a/Content.Server/Nyanotrasen/Chat/TSayCommand.cs b/Content.Server/Nyanotrasen/Chat/TSayCommand.cs index 9ba27b65d71..cae234a9934 100644 --- a/Content.Server/Nyanotrasen/Chat/TSayCommand.cs +++ b/Content.Server/Nyanotrasen/Chat/TSayCommand.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; +using Content.Shared.Chat; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.Enums; diff --git a/Content.Server/Nyanotrasen/Mail/MailSystem.cs b/Content.Server/Nyanotrasen/Mail/MailSystem.cs index ac73388d6c7..05cd0c88a72 100644 --- a/Content.Server/Nyanotrasen/Mail/MailSystem.cs +++ b/Content.Server/Nyanotrasen/Mail/MailSystem.cs @@ -29,6 +29,7 @@ using Content.Shared.Access; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.Chat; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; using Content.Shared.Emag.Components; diff --git a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs index bc3c22cc350..d29a191f88a 100644 --- a/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs +++ b/Content.Server/Nyanotrasen/Research/SophicScribe/SophicScribeSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Radio.Components; using Content.Server.Radio.EntitySystems; using Content.Server.StationEvents.Events; +using Content.Shared.Chat; using Content.Shared.Interaction; using Content.Shared.Psionics.Glimmer; using Content.Shared.Radio; diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index bfdd49213ee..7232a23d2c8 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -85,6 +85,9 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann if (language == null) language = _language.GetLanguage(messageSource); + if (!language.SpeechOverride.AllowRadio) + return; + // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this. if (!_messages.Add(message)) return; @@ -176,11 +179,14 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, string name, string message, LanguagePrototype language) { var speech = _chat.GetSpeechVerb(source, message); + // TODO this is done just to preserve the old look of radio, perhaps we can change it as well? + var languageColor = language.SpeechOverride.Color == Color.White ? channel.Color : language.SpeechOverride.Color; + return Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", ("color", channel.Color), - ("languageColor", language.Color ?? channel.Color), - ("fontType", language.FontId ?? speech.FontId), - ("fontSize", language.FontSize ?? speech.FontSize), + ("languageColor", languageColor), + ("fontType", language.SpeechOverride.FontId ?? speech.FontId), + ("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize), ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), ("channel", $"\\[{channel.LocalizedName}\\]"), ("name", name), diff --git a/Content.Server/RatKing/RatKingSystem.cs b/Content.Server/RatKing/RatKingSystem.cs index 4b82dba3359..c52b596bcc5 100644 --- a/Content.Server/RatKing/RatKingSystem.cs +++ b/Content.Server/RatKing/RatKingSystem.cs @@ -6,6 +6,7 @@ using Content.Server.NPC.Systems; using Content.Server.Popups; using Content.Shared.Atmos; +using Content.Shared.Chat; using Content.Shared.Dataset; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; diff --git a/Content.Server/Speech/Muting/MutingSystem.cs b/Content.Server/Speech/Muting/MutingSystem.cs index 238d501e249..fc3349a9387 100644 --- a/Content.Server/Speech/Muting/MutingSystem.cs +++ b/Content.Server/Speech/Muting/MutingSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Abilities.Mime; using Content.Server.Chat.Systems; +using Content.Server.Language; using Content.Server.Popups; using Content.Server.Speech.Components; using Content.Server.Speech.EntitySystems; @@ -12,7 +13,9 @@ namespace Content.Server.Speech.Muting { public sealed class MutingSystem : EntitySystem { + [Dependency] private readonly LanguageSystem _languages = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; + public override void Initialize() { base.Initialize(); @@ -47,7 +50,9 @@ private void OnScreamAction(EntityUid uid, MutedComponent component, ScreamActio private void OnSpeakAttempt(EntityUid uid, MutedComponent component, SpeakAttemptEvent args) { - // TODO something better than this. + var language = _languages.GetLanguage(uid); + if (!language.SpeechOverride.RequireSpeech) + return; // Cannot mute if there's no speech involved if (HasComp(uid)) _popupSystem.PopupEntity(Loc.GetString("mime-cant-speak"), uid, uid); diff --git a/Content.Server/Speech/Systems/RandomBarkSystem.cs b/Content.Server/Speech/Systems/RandomBarkSystem.cs index 4fc9dd420d2..863ea798433 100644 --- a/Content.Server/Speech/Systems/RandomBarkSystem.cs +++ b/Content.Server/Speech/Systems/RandomBarkSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Mind.Components; using Robust.Shared.Random; using Content.Server.Speech.Components; +using Content.Shared.Chat; namespace Content.Server.Speech.Systems; diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs index 0e694a801eb..206c0800f0a 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chat.Systems; using Content.Server.Speech; +using Content.Shared.Chat; using Content.Shared.Speech; using Robust.Shared.Audio.Systems; using Robust.Shared.Timing; diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 82249bb0ccb..22840cde8f5 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -24,6 +24,7 @@ using Robust.Shared.Random; using System.Linq; using System.Numerics; +using Content.Shared.Chat; namespace Content.Server.Weapons.Melee; diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index 8d14adcc24b..c48d70405f2 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Radio; using Content.Shared.Speech; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Chat; @@ -21,7 +22,7 @@ public abstract class SharedChatSystem : EntitySystem public const char EmotesAltPrefix = '*'; public const char AdminPrefix = ']'; public const char WhisperPrefix = ','; - public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix. + public const char TelepathicPrefix = '='; //Nyano - Summary: Adds the telepathic channel's prefix. public const char DefaultChannelKey = 'h'; [ValidatePrototypeId] @@ -243,3 +244,42 @@ public static string GetStringInsideTag(ChatMessage message, string tag) return rawmsg.Substring(tagStart, tagEnd - tagStart); } } + +/// +/// InGame IC chat is for chat that is specifically ingame (not lobby) but is also in character, i.e. speaking. +/// +// ReSharper disable once InconsistentNaming +[Serializable, NetSerializable] +public enum InGameICChatType : byte +{ + Speak, + Emote, + Whisper, + Telepathic +} + +/// +/// InGame OOC chat is for chat that is specifically ingame (not lobby) but is OOC, like deadchat or LOOC. +/// +[Serializable, NetSerializable] +public enum InGameOOCChatType : byte +{ + Looc, + Dead +} + +/// +/// Controls transmission of chat. +/// +[Serializable, NetSerializable] +public enum ChatTransmitRange : byte +{ + /// Acts normal, ghosts can hear across the map, etc. + Normal, + /// Normal but ghosts are still range-limited. + GhostRangeLimit, + /// Hidden from the chat window. + HideChat, + /// Ghosts can't hear or see it at all. Regular players can if in-range. + NoGhosts +} diff --git a/Content.Shared/Language/LanguagePrototype.cs b/Content.Shared/Language/LanguagePrototype.cs index 79c17daccd8..d3a977202be 100644 --- a/Content.Shared/Language/LanguagePrototype.cs +++ b/Content.Shared/Language/LanguagePrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared.Chat; using Robust.Shared.Prototypes; namespace Content.Shared.Language; @@ -8,27 +9,18 @@ public sealed class LanguagePrototype : IPrototype [IdDataField] public string ID { get; private set; } = default!; - [DataField("color")] - public Color? Color; - - [DataField("fontId")] - public string? FontId; - - [DataField("fontSize")] - public int? FontSize; - - /// - /// If true, will mark the language as a SignLanguage and will be handled as such. - /// - [DataField("signLanguage")] - public bool? SignLanguage; - /// /// Obfuscation method used by this language. By default, uses /// [DataField("obfuscation")] public ObfuscationMethod Obfuscation = ObfuscationMethod.Default; + /// + /// Speech overrides used for messages sent in this language. + /// + [DataField("speech")] + public SpeechOverrideInfo SpeechOverride = new(); + #region utility /// /// The in-world name of this language, localized. @@ -41,3 +33,48 @@ public sealed class LanguagePrototype : IPrototype public string Description => Loc.GetString($"language-{ID}-description"); #endregion utility } + +[DataDefinition] +public sealed partial class SpeechOverrideInfo +{ + [DataField] + public Color Color = Color.White; + + [DataField] + public string? FontId; + + [DataField] + public int? FontSize; + + [DataField] + public bool AllowRadio = true; + + /// + /// If false, the entity can use this language even when it's unable to speak (i.e. muffled or muted), + /// and accents are not applied to messages in this language. + /// + [DataField] + public bool RequireSpeech = true; + + /// + /// If not null, all messages in this language will be forced to be spoken in this chat type. + /// + [DataField] + public InGameICChatType? ChatTypeOverride; + + /// + /// Speech verb overrides. If not provided, the default ones for the entity are used. + /// + [DataField] + public List? SpeechVerbOverrides; + + /// + /// Overrides for different kinds chat message wraps. If not provided, the default ones are used. + /// + /// + /// Currently, only local chat and whispers support this. Radio and emotes are unaffected. + /// This is horrible. + /// + [DataField] + public Dictionary MessageWrapOverrides = new(); +} diff --git a/Resources/Locale/en-US/language/languages-sign.ftl b/Resources/Locale/en-US/language/languages-sign.ftl new file mode 100644 index 00000000000..a431a0637f4 --- /dev/null +++ b/Resources/Locale/en-US/language/languages-sign.ftl @@ -0,0 +1,6 @@ +chat-sign-language-message-wrap = [italic][BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] [BubbleContent]{$verb} [font="{$fontType}" size={$fontSize}][color={$color}]{$message}[/color][/font][/italic][/BubbleContent] +chat-sign-language-whisper-wrap = [italic][BubbleHeader][Name]{$entityName}[/Name][/BubbleHeader] [BubbleContent]subtly gestures [font="{$fontType}" size={$fontSize}][color={$color}]{$message}[/color][/font][/italic][/BubbleContent] + +chat-speech-verb-sign-1 = gestures +chat-speech-verb-sign-2 = signs +chat-speech-verb-sign-3 = waves diff --git a/Resources/Locale/en-US/language/languages.ftl b/Resources/Locale/en-US/language/languages.ftl index 209daf92de8..56dbe04f462 100644 --- a/Resources/Locale/en-US/language/languages.ftl +++ b/Resources/Locale/en-US/language/languages.ftl @@ -4,9 +4,6 @@ language-Universal-description = What are you? language-GalacticCommon-name = Galactic common language-GalacticCommon-description = The standard Galatic language, most commonly used for inter-species communications and legal work. -language-SignLanguage-name = Sign Language -language-SignLanguage-description = The standard Galactic sign language, used by those that are unable to speak Galactic Common or at all. - language-Bubblish-name = Bubblish language-Bubblish-description = The language of Slimes. Being a mixture of bubbling noises and pops it's very difficult to speak for humans without the use of mechanical aids. @@ -72,3 +69,6 @@ language-Crab-description = Click! language-Kobold-name = Kobold language-Kobold-description = Hiss! + +language-Sign-name = Sign Language +language-Sign-description = The standard Galactic sign language, used by those that are unable to speak Galactic Common or at all. diff --git a/Resources/Locale/en-US/language/sign-language.ftl b/Resources/Locale/en-US/language/sign-language.ftl deleted file mode 100644 index f4548f995c5..00000000000 --- a/Resources/Locale/en-US/language/sign-language.ftl +++ /dev/null @@ -1,4 +0,0 @@ -entity-signlanguage-message = [italic]{$entityName} signs "[BubbleContent]{$message}[/BubbleContent]"[/italic] - -language-signlanguage-1 = [italic]{$entityName} signs something.[/italic] -language-signlanguage-2 = [italic]{$entityName} makes weird hand gestures.[/italic] diff --git a/Resources/Prototypes/Language/languages.yml b/Resources/Prototypes/Language/languages.yml index bec1884fe5c..566ee082914 100644 --- a/Resources/Prototypes/Language/languages.yml +++ b/Resources/Prototypes/Language/languages.yml @@ -35,20 +35,12 @@ - nah - wah -- type: language - id: SignLanguage - signLanguage: true - obfuscation: - !type:ReplacementObfuscation - replacement: - - "language-signlanguage-1" - - "language-signlanguage-2" - # Spoken by slimes. - type: language id: Bubblish - color: "#0077aa" - fontId: RubikBubbles + speech: + color: "#0077aa" + fontId: RubikBubbles obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -63,8 +55,9 @@ # Spoken by moths. - type: language id: Moffic - color: "#869b29" - fontId: Copperplate + speech: + color: "#869b29" + fontId: Copperplate obfuscation: !type:SyllableObfuscation minSyllables: 2 # Replacements are really short @@ -131,8 +124,9 @@ # Spoken by dionas. - type: language id: RootSpeak - color: "#804000" - fontId: Noganas + speech: + color: "#804000" + fontId: Noganas obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -147,8 +141,9 @@ # A mess of broken Japanese, spoken by Felinds and Oni - type: language id: Nekomimetic - color: "#803B56" - fontId: Manga + speech: + color: "#803B56" + fontId: Manga obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -206,7 +201,8 @@ # Spoken by the Lizard race. - type: language id: Draconic - color: "#228b22" + speech: + color: "#228b22" obfuscation: !type:SyllableObfuscation minSyllables: 2 @@ -300,7 +296,8 @@ # Spoken by the Vulpkanin race. - type: language id: Canilunzt - color: "#b97a57" + speech: + color: "#b97a57" obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -367,7 +364,8 @@ # The common language of the Sol system. - type: language id: SolCommon - color: "#8282fb" + speech: + color: "#8282fb" obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -393,7 +391,8 @@ - type: language id: RobotTalk - fontId: Monospace + speech: + fontId: Monospace obfuscation: !type:SyllableObfuscation minSyllables: 1 @@ -576,3 +575,28 @@ - hiss - ss - ee + +# Example of a sign language. Not currently used anyhow. +- type: language + id: Sign + speech: + allowRadio: false + requireSpeech: false + color: "#dddddd" + messageWrapOverrides: + Speak: chat-sign-language-message-wrap + Whisper: chat-sign-language-whisper-wrap + speechVerbOverrides: + - chat-speech-verb-sign-1 + - chat-speech-verb-sign-2 + - chat-speech-verb-sign-3 + obfuscation: + !type:ReplacementObfuscation + replacement: + - something + - a cryptic message to you + - a signal to you + - a message + - a rude expression to you + - a sad expression to you + - a happy expression to you