diff --git a/Content.Server/Actions/ActionsSystem.cs b/Content.Server/Actions/ActionsSystem.cs index 3f47f5f028..7d56cfd577 100644 --- a/Content.Server/Actions/ActionsSystem.cs +++ b/Content.Server/Actions/ActionsSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; @@ -9,7 +10,7 @@ namespace Content.Server.Actions [UsedImplicitly] public sealed class ActionsSystem : SharedActionsSystem { - [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly MetaDataSystem _metaSystem = default!; public override void Initialize() @@ -50,7 +51,7 @@ namespace Content.Server.Actions if (!string.IsNullOrWhiteSpace(action.Speech)) { - _chatMan.EntitySay(user, Loc.GetString(action.Speech)); + _chat.TrySendInGameICMessage(user, Loc.GetString(action.Speech), InGameICChatType.Speak, false); result = true; } diff --git a/Content.Server/Administration/Commands/DSay.cs b/Content.Server/Administration/Commands/DSay.cs index 8353bc40da..12680a5995 100644 --- a/Content.Server/Administration/Commands/DSay.cs +++ b/Content.Server/Administration/Commands/DSay.cs @@ -1,3 +1,4 @@ +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Shared.Administration; using Robust.Server.Player; @@ -25,6 +26,9 @@ namespace Content.Server.Administration.Commands return; } + if (player.AttachedEntity is not { Valid: true } entity) + return; + if (args.Length < 1) return; @@ -32,10 +36,8 @@ namespace Content.Server.Administration.Commands if (string.IsNullOrEmpty(message)) return; - var chat = IoCManager.Resolve(); - - chat.SendAdminDeadChat(player, message); - + var chat = EntitySystem.Get(); + chat.TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Dead, false, shell, player); } } } diff --git a/Content.Server/Advertise/AdvertiseSystem.cs b/Content.Server/Advertise/AdvertiseSystem.cs index b8467d1e7f..24c881988f 100644 --- a/Content.Server/Advertise/AdvertiseSystem.cs +++ b/Content.Server/Advertise/AdvertiseSystem.cs @@ -1,5 +1,6 @@ using System; using Content.Server.Advertisements; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Power.Components; using Content.Server.VendingMachines; @@ -16,8 +17,8 @@ namespace Content.Server.Advertise { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ChatSystem _chat = default!; private const float UpdateTimer = 5f; @@ -60,7 +61,7 @@ namespace Content.Server.Advertise return; if (_prototypeManager.TryIndex(advertise.PackPrototypeId, out AdvertisementsPackPrototype? advertisements)) - _chatManager.EntitySay(advertise.Owner, Loc.GetString(_random.Pick(advertisements.Advertisements)), hideChat: true); + _chat.TrySendInGameICMessage(advertise.Owner, Loc.GetString(_random.Pick(advertisements.Advertisements)), InGameICChatType.Speak, true); if(refresh) RefreshTimer(uid, true, advertise); diff --git a/Content.Server/Chat/ChatSystem.cs b/Content.Server/Chat/ChatSystem.cs new file mode 100644 index 0000000000..36a60b8a17 --- /dev/null +++ b/Content.Server/Chat/ChatSystem.cs @@ -0,0 +1,453 @@ +using System.Linq; +using System.Text; +using Content.Server.Administration.Logs; +using Content.Server.Administration.Managers; +using Content.Server.Chat.Managers; +using Content.Server.Disease; +using Content.Server.Disease.Components; +using Content.Server.Ghost.Components; +using Content.Server.Headset; +using Content.Server.Players; +using Content.Server.Popups; +using Content.Server.Radio.EntitySystems; +using Content.Shared.ActionBlocker; +using Content.Shared.CCVar; +using Content.Shared.Chat; +using Content.Shared.Database; +using Content.Shared.Disease.Components; +using Content.Shared.Inventory; +using Content.Shared.Popups; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Console; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Players; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.Chat; + +/// +/// ChatSystem is responsible for in-simulation chat handling, such as whispering, speaking, emoting, etc. +/// ChatSystem depends on ChatManager to actually send the messages. +/// +public sealed class ChatSystem : EntitySystem +{ + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IChatSanitizationManager _sanitizer = default!; + [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly AdminLogSystem _logs = default!; + [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!; + [Dependency] private readonly ListeningSystem _listener = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + private const int VoiceRange = 7; // how far voice goes in world units + private const int WhisperRange = 2; // how far whisper goes in world units + + private bool _loocEnabled = true; + private readonly bool _adminLoocEnabled = true; + + public override void Initialize() + { + _configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true); + } + + public override void Shutdown() + { + _configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged); + } + + private void OnLoocEnabledChanged(bool val) + { + if (_loocEnabled == val) return; + + _loocEnabled = val; + _chatManager.DispatchServerAnnouncement( + Loc.GetString(val ? "chat-manager-looc-chat-enabled-message" : "chat-manager-looc-chat-disabled-message")); + } + + // ReSharper disable once InconsistentNaming + public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, bool hideChat, + IConsoleShell? shell = null, IPlayerSession? player = null) + { + if (HasComp(source)) + { + // Ghosts can only send dead chat messages, so we'll forward it to InGame OOC. + TrySendInGameOOCMessage(source, message, InGameOOCChatType.Dead, hideChat, shell, player); + return; + } + + if (!CanSendInGame(message, shell, player)) + return; + + message = SanitizeInGameICMessage(source, message, out var emoteStr); + + // Is this -actually- an emote, and is a player sending it? + if (player != null && emoteStr != message && emoteStr != null) + { + SendEntityEmote(source, emoteStr, hideChat); + return; + } + + // Sus + if (player?.AttachedEntity is { Valid: true } entity && source != entity) + { + return; + } + + // Otherwise, send whatever type. + switch (desiredType) + { + case InGameICChatType.Speak: + SendEntitySpeak(source, message, hideChat); + break; + case InGameICChatType.Whisper: + SendEntityWhisper(source, message, hideChat); + break; + case InGameICChatType.Emote: + SendEntityEmote(source, message, hideChat); + break; + } + } + + public void TrySendInGameOOCMessage(EntityUid source, string message, InGameOOCChatType type, bool hideChat, + IConsoleShell? shell = null, IPlayerSession? player = null) + { + if (!CanSendInGame(message, shell, player)) + return; + + // It doesn't make any sense for a non-player to send in-game OOC messages, whereas non-players may be sending + // in-game IC messages. + if (player?.AttachedEntity is not { Valid: true } entity || source != entity) + return; + + message = SanitizeInGameOOCMessage(message); + + switch (type) + { + case InGameOOCChatType.Dead: + SendDeadChat(source, player, message, hideChat); + break; + case InGameOOCChatType.Looc: + SendLOOC(source, player, message, hideChat); + break; + } + } + + #region Private API + + private void SendEntitySpeak(EntityUid source, string message, bool hideChat = false) + { + if (!_actionBlocker.CanSpeak(source)) return; + message = TransformSpeech(source, message); + + _listener.PingListeners(source, message); + var messageWrap = Loc.GetString("chat-manager-entity-say-wrap-message", + ("entityName", Name(source))); + + SendInVoiceRange(ChatChannel.Local, message, messageWrap, source, hideChat); + + var ev = new EntitySpokeEvent(message); + RaiseLocalEvent(source, ev, false); + _logs.Add(LogType.Chat, LogImpact.Low, $"Say from {ToPrettyString(source):user}: {message}"); + } + + private void SendEntityWhisper(EntityUid source, string message, bool hideChat = false) + { + if (!_actionBlocker.CanSpeak(source)) return; + + message = TransformSpeech(source, message); + _listener.PingListeners(source, message); + var obfuscatedMessage = ObfuscateMessageReadability(message, 0.2f); + + var transformSource = Transform(source); + var sourceCoords = transformSource.Coordinates; + var messageWrap = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", Name(source))); + + var xforms = GetEntityQuery(); + var ghosts = GetEntityQuery(); + + var sessions = new List(); + ClientDistanceToList(source, VoiceRange, sessions); + + // Whisper needs these special calculations, since it can obfuscate the message. + foreach (var session in sessions) + { + if (session.AttachedEntity is not { Valid: true } playerEntity) + continue; + + var transformEntity = xforms.GetComponent(playerEntity); + + if (sourceCoords.InRange(EntityManager, transformEntity.Coordinates, WhisperRange) || + ghosts.HasComponent(playerEntity)) + { + _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, messageWrap, source, hideChat, session.ConnectedClient); + } + else + { + _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, messageWrap, source, hideChat, + session.ConnectedClient); + } + } + + var ev = new EntitySpokeEvent(message); + RaiseLocalEvent(source, ev, false); + _logs.Add(LogType.Chat, LogImpact.Low, $"Whisper from {ToPrettyString(source):user}: {message}"); + } + + private void SendEntityEmote(EntityUid source, string action, bool hideChat) + { + if (!_actionBlocker.CanEmote(source)) return; + + var messageWrap = Loc.GetString("chat-manager-entity-me-wrap-message", + ("entityName", Name(source))); + + SendInVoiceRange(ChatChannel.Emotes, action, messageWrap, source, hideChat); + _logs.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user}: {action}"); + } + + // ReSharper disable once InconsistentNaming + private void SendLOOC(EntityUid source, IPlayerSession player, string message, bool hideChat) + { + if (_adminManager.IsAdmin(player)) + { + if (!_adminLoocEnabled) return; + } + else if (!_loocEnabled) return; + var messageWrap = Loc.GetString("chat-manager-entity-looc-wrap-message", + ("entityName", Name(source))); + + SendInVoiceRange(ChatChannel.LOOC, message, messageWrap, source, hideChat); + _logs.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); + } + + private void SendDeadChat(EntityUid source, IPlayerSession player, string message, bool hideChat) + { + var clients = GetDeadChatClients(); + var playerName = Name(source); + string messageWrap; + if (_adminManager.IsAdmin(player)) + { + messageWrap = Loc.GetString("chat-manager-send-admin-dead-chat-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), + ("userName", player.ConnectedClient.UserName)); + _logs.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}"); + } + else + { + messageWrap = Loc.GetString("chat-manager-send-dead-chat-wrap-message", + ("deadChannelName", Loc.GetString("chat-manager-dead-channel-name")), + ("playerName", (playerName))); + _logs.Add(LogType.Chat, LogImpact.Low, $"Admin dead chat from {player:Player}: {message}"); + } + + _chatManager.ChatMessageToMany(ChatChannel.Dead, message, messageWrap, source, hideChat, clients.ToList()); + _logs.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}"); + } + #endregion + + #region Utility + + /// + /// Sends a chat message to the given players in range of the source entity. + /// + private void SendInVoiceRange(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat) + { + var sessions = new List(); + ClientDistanceToList(source, VoiceRange, sessions); + _chatManager.ChatMessageToMany(channel, message, messageWrap, source, hideChat, sessions.Select(s => s.ConnectedClient).ToList()); + } + + /// + /// Returns true if the given player is 'allowed' to send the given message, false otherwise. + /// + private bool CanSendInGame(string message, IConsoleShell? shell = null, IPlayerSession? player = null) + { + // Non-players don't have to worry about these restrictions. + if (player == null) + return true; + + var mindComponent = player.ContentData()?.Mind; + + if (mindComponent == null) + { + shell?.WriteError("You don't have a mind!"); + return false; + } + + if (player.AttachedEntity is not { Valid: true } _) + { + shell?.WriteError("You don't have an entity!"); + return false; + } + + return !_chatManager.MessageCharacterLimit(player, message); + } + + // ReSharper disable once InconsistentNaming + private string SanitizeInGameICMessage(EntityUid source, string message, out string? emoteStr) + { + var newMessage = message.Trim(); + newMessage = SanitizeMessageCapital(source, newMessage); + newMessage = FormattedMessage.EscapeText(newMessage); + + _sanitizer.TrySanitizeOutSmilies(newMessage, source, out newMessage, out emoteStr); + + return newMessage; + } + + private string SanitizeInGameOOCMessage(string message) + { + var newMessage = message.Trim(); + newMessage = FormattedMessage.EscapeText(newMessage); + + return newMessage; + } + + private string TransformSpeech(EntityUid sender, string message) + { + var ev = new TransformSpeechEvent(sender, message); + RaiseLocalEvent(ev); + + return ev.Message; + } + + private IEnumerable GetDeadChatClients() + { + return Filter.Empty() + .AddWhereAttachedEntity(uid => HasComp(uid)) + .Recipients + .Union(_adminManager.ActiveAdmins) + .Select(p => p.ConnectedClient); + } + + private string SanitizeMessageCapital(EntityUid source, string message) + { + if (message.StartsWith(';')) + { + // Remove semicolon + message = message.Substring(1).TrimStart(); + + // Capitalize first letter + message = message[0].ToString().ToUpper() + message.Remove(0, 1); + + if (_inventory.TryGetSlotEntity(source, "ears", out var entityUid) && + TryComp(entityUid, out HeadsetComponent? headset)) + { + headset.RadioRequested = true; + } + else + { + _popup.PopupEntity(Loc.GetString("chat-manager-no-headset-on-message"), source, Filter.Entities(source)); + } + } + else + { + // Capitalize first letter + message = message[0].ToString().ToUpper() + message.Remove(0, 1); + } + + return message; + } + + private void ClientDistanceToList(EntityUid source, int voiceRange, List playerSessions) + { + var ghosts = GetEntityQuery(); + var xforms = GetEntityQuery(); + + var transformSource = xforms.GetComponent(source); + var sourceMapId = transformSource.MapID; + var sourceCoords = transformSource.Coordinates; + + foreach (var player in _playerManager.Sessions) + { + if (player.AttachedEntity is not {Valid: true} playerEntity) + continue; + + var transformEntity = xforms.GetComponent(playerEntity); + + if (transformEntity.MapID != sourceMapId || + !ghosts.HasComponent(playerEntity) && + !sourceCoords.InRange(EntityManager, transformEntity.Coordinates, voiceRange)) + continue; + + playerSessions.Add(player); + } + } + + private string ObfuscateMessageReadability(string message, float chance) + { + var modifiedMessage = new StringBuilder(message); + + for (var i = 0; i < message.Length; i++) + { + if (char.IsWhiteSpace((modifiedMessage[i]))) + { + continue; + } + + if (_random.Prob(1 - chance)) + { + modifiedMessage[i] = '~'; + } + } + + return modifiedMessage.ToString(); + } + + #endregion +} + +/// +/// Raised broadcast in order to transform speech. +/// +public sealed class TransformSpeechEvent : EntityEventArgs +{ + public EntityUid Sender; + public string Message; + + public TransformSpeechEvent(EntityUid sender, string message) + { + Sender = sender; + Message = message; + } +} + +/// +/// Raised on an entity when it speaks, either through 'say' or 'whisper'. +/// +public sealed class EntitySpokeEvent : EntityEventArgs +{ + public string Message; + + public EntitySpokeEvent(string message) + { + Message = message; + } +} + +/// +/// 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 +} + +/// +/// 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 +} diff --git a/Content.Server/Chat/Commands/AdminChatCommand.cs b/Content.Server/Chat/Commands/AdminChatCommand.cs index 3f4398cf67..d189550462 100644 --- a/Content.Server/Chat/Commands/AdminChatCommand.cs +++ b/Content.Server/Chat/Commands/AdminChatCommand.cs @@ -31,8 +31,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - var chat = IoCManager.Resolve(); - chat.SendAdminChat(player, message); + IoCManager.Resolve().TrySendOOCMessage(player, message, OOCChatType.Admin); } } } diff --git a/Content.Server/Chat/Commands/LOOCCommand.cs b/Content.Server/Chat/Commands/LOOCCommand.cs index ca88e385ae..aedc240743 100644 --- a/Content.Server/Chat/Commands/LOOCCommand.cs +++ b/Content.Server/Chat/Commands/LOOCCommand.cs @@ -24,6 +24,9 @@ namespace Content.Server.Chat.Commands return; } + if (player.AttachedEntity is not { Valid: true } entity) + return; + if (player.Status != SessionStatus.InGame) return; @@ -34,7 +37,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - IoCManager.Resolve().SendLOOC(player, message); + EntitySystem.Get().TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Looc, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/MeCommand.cs b/Content.Server/Chat/Commands/MeCommand.cs index b1db3f9851..da8abfceea 100644 --- a/Content.Server/Chat/Commands/MeCommand.cs +++ b/Content.Server/Chat/Commands/MeCommand.cs @@ -40,7 +40,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - IoCManager.Resolve().TryEmote(playerEntity, message, shell, player); + EntitySystem.Get().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Emote, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/OOCCommand.cs b/Content.Server/Chat/Commands/OOCCommand.cs index 1f30bebae3..fe415cf9fc 100644 --- a/Content.Server/Chat/Commands/OOCCommand.cs +++ b/Content.Server/Chat/Commands/OOCCommand.cs @@ -29,7 +29,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - IoCManager.Resolve().SendOOC(player, message); + IoCManager.Resolve().TrySendOOCMessage(player, message, OOCChatType.OOC); } } } diff --git a/Content.Server/Chat/Commands/SayCommand.cs b/Content.Server/Chat/Commands/SayCommand.cs index 26afa8ffe4..1c3e0d6c1f 100644 --- a/Content.Server/Chat/Commands/SayCommand.cs +++ b/Content.Server/Chat/Commands/SayCommand.cs @@ -38,7 +38,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - IoCManager.Resolve().TrySpeak(playerEntity, message, false, shell, player); + EntitySystem.Get().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/WhisperCommand.cs b/Content.Server/Chat/Commands/WhisperCommand.cs index 4daca93ad9..246dd67066 100644 --- a/Content.Server/Chat/Commands/WhisperCommand.cs +++ b/Content.Server/Chat/Commands/WhisperCommand.cs @@ -38,7 +38,7 @@ namespace Content.Server.Chat.Commands if (string.IsNullOrEmpty(message)) return; - IoCManager.Resolve().TrySpeak(playerEntity, message, true, shell, player); + EntitySystem.Get().TrySendInGameICMessage(playerEntity, message, InGameICChatType.Whisper, false, shell, player); } } } diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index 1062314871..8e19289367 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -45,15 +45,11 @@ namespace Content.Server.Chat.Managers { "revolutionary", "#aa00ff" } }; - [Dependency] private readonly IChatSanitizationManager _sanitizer = default!; - [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IServerNetManager _netManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IMoMMILink _mommiLink = default!; [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; private AdminLogSystem _logs = default!; @@ -62,15 +58,8 @@ namespace Content.Server.Chat.Managers /// public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength); - private const int VoiceRange = 7; // how far voice goes in world units - private const int WhisperRange = 2; // how far whisper goes in world units - - //TODO: make prio based? - private readonly List _chatTransformHandlers = new(); private bool _oocEnabled = true; private bool _adminOocEnabled = true; - private bool _loocEnabled = true; - private bool _adminLoocEnabled = true; public void Initialize() { @@ -78,7 +67,6 @@ namespace Content.Server.Chat.Managers _netManager.RegisterNetMessage(); _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); - _configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true); _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); } @@ -90,14 +78,6 @@ namespace Content.Server.Chat.Managers DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message")); } - private void OnLoocEnabledChanged(bool val) - { - if (_loocEnabled == val) return; - - _loocEnabled = val; - DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-looc-chat-enabled-message" : "chat-manager-looc-chat-disabled-message")); - } - private void OnAdminOocEnabledChanged(bool val) { if (_adminOocEnabled == val) return; @@ -106,10 +86,12 @@ namespace Content.Server.Chat.Managers DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message")); } + #region Server Announcements + public void DispatchServerAnnouncement(string message, Color? colorOverride = null) { var messageWrap = Loc.GetString("chat-manager-server-wrap-message"); - NetMessageToAll(ChatChannel.Server, message, messageWrap, colorOverride); + ChatMessageToAll(ChatChannel.Server, message, messageWrap, colorOverride); Logger.InfoS("SERVER", message); _logs.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); @@ -118,7 +100,7 @@ namespace Content.Server.Chat.Managers public void DispatchStationAnnouncement(string message, string sender = "Central Command", bool playDefaultSound = true, Color? colorOverride = null) { var messageWrap = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender)); - NetMessageToAll(ChatChannel.Radio, message, messageWrap, colorOverride); + ChatMessageToAll(ChatChannel.Radio, message, messageWrap, colorOverride); if (playDefaultSound) { SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/announce.ogg", AudioParams.Default.WithVolume(-2f)); @@ -130,227 +112,43 @@ namespace Content.Server.Chat.Managers public void DispatchServerMessage(IPlayerSession player, string message) { var messageWrap = Loc.GetString("chat-manager-server-wrap-message"); - var msg = _netManager.CreateNetMessage(); - msg.Channel = ChatChannel.Server; - msg.Message = message; - msg.MessageWrap = messageWrap; - _netManager.ServerSendMessage(msg, player.ConnectedClient); + ChatMessageToOne(ChatChannel.Server, message, messageWrap, default, false, player.ConnectedClient); - _logs.Add(LogType.Chat, LogImpact.Low, $"Server message from {player:Player}: {message}"); + _logs.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}"); } - public void TrySpeak(EntityUid source, string message, bool isWhisper = false, IConsoleShell? shell = null, IPlayerSession? player = null) + public void SendAdminAnnouncement(string message) { - // Listen it avoids the 30 lines being copy-paste and means only 1 source needs updating if something changes. - if (_entManager.HasComponent(source)) - { - if (player == null) return; - SendDeadChat(player, message); - } - else - { - var mindComponent = player?.ContentData()?.Mind; - - if (mindComponent == null) - { - shell?.WriteError("You don't have a mind!"); - return; - } - - if (mindComponent.OwnedEntity is not {Valid: true} owned) - { - shell?.WriteError("You don't have an entity!"); - return; - } - - var isEmote = _sanitizer.TrySanitizeOutSmilies(message, owned, out var sanitized, out var emoteStr); - - if (sanitized.Length != 0) - SendEntityChatType(owned, sanitized, isWhisper); - - if (isEmote) - EntityMe(owned, emoteStr!); - } - } - - public void TryEmote(EntityUid source, string message, IConsoleShell? shell = null, IPlayerSession? player = null) - { - var mindComponent = player?.ContentData()?.Mind; - - if (mindComponent == null) - { - shell?.WriteError("You don't have a mind!"); - return; - } - - if (mindComponent.OwnedEntity is not {Valid: true} owned) - { - shell?.WriteError("You don't have an entity!"); - return; - } - - var isEmote = _sanitizer.TrySanitizeOutSmilies(message, mindComponent.OwnedEntity.Value, out var sanitized, out var emoteStr); - - if (sanitized.Length != 0) - EntityMe(mindComponent.OwnedEntity.Value, sanitized); - - if (isEmote) - EntityMe(mindComponent.OwnedEntity.Value, emoteStr!); - } - - public void EntitySay(EntityUid source, string message, bool hideChat=false) - { - if (!EntitySystem.Get().CanSpeak(source)) - { - return; - } - - if (_entManager.HasComponent(source) && _entManager.TryGetComponent(source,out var carrier)) - { - EntitySystem.Get().SneezeCough(source, _random.Pick(carrier.Diseases), string.Empty); - } - - if (MessageCharacterLimit(source, message)) - { - return; - } - - message = message.Trim(); - - message = SanitizeMessageCapital(source, message); - - foreach (var handler in _chatTransformHandlers) - { - //TODO: rather return a bool and use a out var? - message = handler(source, message); - } - - var listeners = EntitySystem.Get(); - listeners.PingListeners(source, message); - + var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); message = FormattedMessage.EscapeText(message); - var sessions = new List(); - ClientDistanceToList(source, VoiceRange, sessions); + var messageWrap = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", + ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name"))); - var messageWrap = Loc.GetString("chat-manager-entity-say-wrap-message",("entityName", _entManager.GetComponent(source).EntityName)); - - foreach (var session in sessions) - { - NetMessageToOne(ChatChannel.Local, message, messageWrap, source, hideChat, session.ConnectedClient); - } - - _logs.Add(LogType.Chat, LogImpact.Low, $"Say from {_entManager.ToPrettyString(source):user}: {message}"); + ChatMessageToMany(ChatChannel.Admin, message, messageWrap, default, false, clients.ToList()); + _logs.Add(LogType.Chat, LogImpact.Low, $"Admin announcement from {message}: {message}"); } - public void EntityWhisper(EntityUid source, string message, bool hideChat=false) + public void SendHookOOC(string sender, string message) { - if (!EntitySystem.Get().CanSpeak(source)) - { - return; - } - - if (MessageCharacterLimit(source, message)) - { - return; - } - - message = message.Trim(); - - message = SanitizeMessageCapital(source, message); - - foreach (var handler in _chatTransformHandlers) - { - //TODO: rather return a bool and use a out var? - message = handler(source, message); - } - - var listeners = EntitySystem.Get(); - listeners.PingListeners(source, message); - message = FormattedMessage.EscapeText(message); - - var obfuscatedMessage = ObfuscateMessageReadability(message, 0.2f); - - var sessions = new List(); - ClientDistanceToList(source, VoiceRange, sessions); - - var transformSource = _entManager.GetComponent(source); - var sourceCoords = transformSource.Coordinates; - var messageWrap = Loc.GetString("chat-manager-entity-whisper-wrap-message",("entityName", _entManager.GetComponent(source).EntityName)); - - var xforms = _entManager.GetEntityQuery(); - var ghosts = _entManager.GetEntityQuery(); - - foreach (var session in sessions) - { - if (session.AttachedEntity is not {Valid: true} playerEntity) - continue; - - var transformEntity = xforms.GetComponent(playerEntity); - - if (sourceCoords.InRange(_entManager, transformEntity.Coordinates, WhisperRange) || - ghosts.HasComponent(playerEntity)) - { - NetMessageToOne(ChatChannel.Whisper, message, messageWrap, source, hideChat, session.ConnectedClient); - } - else - { - NetMessageToOne(ChatChannel.Whisper, obfuscatedMessage, messageWrap, source, hideChat, session.ConnectedClient); - } - } - - _logs.Add(LogType.Chat, LogImpact.Low, $"Whisper from {_entManager.ToPrettyString(source):user}: {message}"); + var messageWrap = Loc.GetString("chat-manager-send-hook-ooc-wrap-message", ("senderName", sender)); + ChatMessageToAll(ChatChannel.OOC, message, messageWrap); + _logs.Add(LogType.Chat, LogImpact.Low, $"Hook OOC from {sender}: {message}"); } - public void EntityMe(EntityUid source, string action) + #endregion + + #region Public OOC Chat API + + /// + /// Called for a player to attempt sending an OOC, out-of-game. message. + /// + /// The player sending the message. + /// The message. + /// The type of message. + public void TrySendOOCMessage(IPlayerSession player, string message, OOCChatType type) { - if (!EntitySystem.Get().CanEmote(source)) - { - return; - } - - if (MessageCharacterLimit(source, action)) - { - return; - } - - action = FormattedMessage.EscapeText(action); - - var sessions = new List(); - - ClientDistanceToList(source, VoiceRange, sessions); - - var messageWrap = Loc.GetString("chat-manager-entity-me-wrap-message", ("entityName", _entManager.GetComponent(source).EntityName)); - - foreach (var session in sessions) - { - NetMessageToOne(ChatChannel.Emotes, action, messageWrap, source, true, session.ConnectedClient); - } - - _logs.Add(LogType.Chat, LogImpact.Low, $"Emote from {_entManager.ToPrettyString(source):user}: {action}"); - } - - public void SendLOOC(IPlayerSession player, string message) - { - if (_adminManager.IsAdmin(player)) - { - if (!_adminLoocEnabled) - { - return; - } - } - else if (!_loocEnabled) - { - return; - } - - // Check they're even attached to an entity before we potentially send a message length error. - if (player.AttachedEntity is not { } entity) - { - return; - } - // Check if message exceeds the character limit if (message.Length > MaxMessageLength) { @@ -359,21 +157,23 @@ namespace Content.Server.Chat.Managers } message = FormattedMessage.EscapeText(message); - var sessions = new List(); - ClientDistanceToList(entity, VoiceRange, sessions); - - var msg = _netManager.CreateNetMessage(); - msg.Channel = ChatChannel.LOOC; - msg.Message = message; - msg.MessageWrap = Loc.GetString("chat-manager-entity-looc-wrap-message", ("entityName", _entManager.GetComponent(entity).EntityName)); - - _netManager.ServerSendToMany(msg, sessions.Select(o => o.ConnectedClient).ToList()); - - _logs.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); + switch (type) + { + case OOCChatType.OOC: + SendOOC(player, message); + break; + case OOCChatType.Admin: + SendAdminChat(player, message); + break; + } } - public void SendOOC(IPlayerSession player, string message) + #endregion + + #region Private API + + private void SendOOC(IPlayerSession player, string message) { if (_adminManager.IsAdmin(player)) { @@ -387,207 +187,47 @@ namespace Content.Server.Chat.Managers return; } - // Check if message exceeds the character limit - if (message.Length > MaxMessageLength) - { - DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); - return; - } - - message = FormattedMessage.EscapeText(message); - - var msg = _netManager.CreateNetMessage(); - msg.Channel = ChatChannel.OOC; - msg.Message = message; - msg.MessageWrap = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name)); + Color? colorOverride = null; + var messageWrap = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name)); if (_adminManager.HasAdminFlag(player, AdminFlags.Admin)) { var prefs = _preferencesManager.GetPreferences(player.UserId); - msg.MessageColorOverride = prefs.AdminOOCColor; + colorOverride = prefs.AdminOOCColor; } if (player.ConnectedClient.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor)) { - msg.MessageWrap = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name)); + messageWrap = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name)); } //TODO: player.Name color, this will need to change the structure of the MsgChatMessage - _netManager.ServerSendToAll(msg); - + ChatMessageToAll(ChatChannel.OOC, message, messageWrap, colorOverride); _mommiLink.SendOOCMessage(player.Name, message); _logs.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}"); } - public void SendDeadChat(IPlayerSession player, string message) + private void SendAdminChat(IPlayerSession player, string message) { - // Check if message exceeds the character limit - if (message.Length > MaxMessageLength) + if (!_adminManager.IsAdmin(player)) { - DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message",("limit", MaxMessageLength))); + _logs.Add(LogType.Chat, LogImpact.Extreme, $"{player:Player} attempted to send admin message but was not admin"); return; } - message = FormattedMessage.EscapeText(message); - - var clients = GetDeadChatClients(); - - var msg = _netManager.CreateNetMessage(); - msg.Channel = ChatChannel.Dead; - msg.Message = message; - - var playerName = player.AttachedEntity is {Valid: true} playerEntity - ? _entManager.GetComponent(playerEntity).EntityName - : "???"; - msg.MessageWrap = Loc.GetString("chat-manager-send-dead-chat-wrap-message", - ("deadChannelName", Loc.GetString("chat-manager-dead-channel-name")), - ("playerName", (playerName))); - msg.SenderEntity = player.AttachedEntity.GetValueOrDefault(); - _netManager.ServerSendToMany(msg, clients.ToList()); - - _logs.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}"); - } - - public void SendAdminDeadChat(IPlayerSession player, string message) - { - // Check if message exceeds the character limit - if (message.Length > MaxMessageLength) - { - DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); - return; - } - - message = FormattedMessage.EscapeText(message); - - var clients = GetDeadChatClients(); - - var msg = _netManager.CreateNetMessage(); - msg.Channel = ChatChannel.Dead; - msg.Message = message; - msg.MessageWrap = Loc.GetString("chat-manager-send-admin-dead-chat-wrap-message", - ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), - ("userName", player.ConnectedClient.UserName)); - _netManager.ServerSendToMany(msg, clients.ToList()); - - _logs.Add(LogType.Chat, LogImpact.Low, $"Admin dead chat from {player:Player}: {message}"); - } - - private IEnumerable GetDeadChatClients() - { - return Filter.Empty() - .AddWhereAttachedEntity(uid => _entManager.HasComponent(uid)) - .Recipients - .Union(_adminManager.ActiveAdmins) - .Select(p => p.ConnectedClient); - } - - public void SendAdminChat(IPlayerSession player, string message) - { - // Check if message exceeds the character limit - if (message.Length > MaxMessageLength) - { - DispatchServerMessage(player, Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength))); - return; - } - - message = FormattedMessage.EscapeText(message); - var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); - - var msg = _netManager.CreateNetMessage(); - - msg.Channel = ChatChannel.Admin; - msg.Message = message; - msg.MessageWrap = Loc.GetString("chat-manager-send-admin-chat-wrap-message", + var messageWrap = Loc.GetString("chat-manager-send-admin-chat-wrap-message", ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("playerName", player.Name)); - _netManager.ServerSendToMany(msg, clients.ToList()); + ChatMessageToMany(ChatChannel.Admin, message, messageWrap, default, false, clients.ToList()); _logs.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}"); } - public void SendAdminAnnouncement(string message) - { - var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); + #endregion - message = FormattedMessage.EscapeText(message); + #region Utility - var msg = _netManager.CreateNetMessage(); - - msg.Channel = ChatChannel.Admin; - msg.Message = message; - msg.MessageWrap = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", - ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name"))); - - _netManager.ServerSendToMany(msg, clients.ToList()); - - _logs.Add(LogType.Chat, LogImpact.Low, $"Admin announcement from {message}: {message}"); - } - - public void SendHookOOC(string sender, string message) - { - message = FormattedMessage.EscapeText(message); - var messageWrap = Loc.GetString("chat-manager-send-hook-ooc-wrap-message", ("senderName", sender)); - NetMessageToAll(ChatChannel.OOC, message, messageWrap); - _logs.Add(LogType.Chat, LogImpact.Low, $"Hook OOC from {sender}: {message}"); - } - - public void RegisterChatTransform(TransformChat handler) - { - // TODO: Literally just make this an event... - _chatTransformHandlers.Add(handler); - } - - public void SendEntityChatType(EntityUid source, string message, bool isWhisper) - { - // I don't know why you're trying to smile over the radio... - // This filters out the players who just really want to try. - if (message.StartsWith(';') && message.Length <= 1) - { - return; - } - - // We check to see if message is a whisper or a standard say message. - if (isWhisper) - { - EntityWhisper(source, message); - } - else - { - EntitySay(source, message); - } - } - public string SanitizeMessageCapital(EntityUid source, string message) - { - if (message.StartsWith(';')) - { - // Remove semicolon - message = message.Substring(1).TrimStart(); - - // Capitalize first letter - message = message[0].ToString().ToUpper() + message.Remove(0, 1); - - var invSystem = EntitySystem.Get(); - - if (invSystem.TryGetSlotEntity(source, "ears", out var entityUid) && - _entManager.TryGetComponent(entityUid, out HeadsetComponent? headset)) - { - headset.RadioRequested = true; - } - else - { - source.PopupMessage(Loc.GetString("chat-manager-no-headset-on-message")); - } - } - else - { - // Capitalize first letter - message = message[0].ToString().ToUpper() + message.Remove(0, 1); - } - - return message; - } - - public void NetMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client) + public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client) { var msg = _netManager.CreateNetMessage(); msg.Channel = channel; @@ -598,7 +238,18 @@ namespace Content.Server.Chat.Managers _netManager.ServerSendMessage(msg, client); } - public void NetMessageToAll(ChatChannel channel, string message, string messageWrap, Color? colorOverride = null) + public void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, List clients) + { + var msg = _netManager.CreateNetMessage(); + msg.Channel = channel; + msg.Message = message; + msg.MessageWrap = messageWrap; + msg.SenderEntity = source; + msg.HideChat = hideChat; + _netManager.ServerSendToMany(msg, clients); + } + + public void ChatMessageToAll(ChatChannel channel, string message, string messageWrap, Color? colorOverride = null) { var msg = _netManager.CreateNetMessage(); msg.Channel = channel; @@ -611,17 +262,20 @@ namespace Content.Server.Chat.Managers _netManager.ServerSendToAll(msg); } - public bool MessageCharacterLimit(EntityUid source, string message) + public bool MessageCharacterLimit(IPlayerSession? player, string message) { var isOverLength = false; + // Non-players don't need to be checked. + if (player == null) + return false; + // Check if message exceeds the character limit if the sender is a player - if (_entManager.TryGetComponent(source, out ActorComponent? actor) && - message.Length > MaxMessageLength) + if (message.Length > MaxMessageLength) { var feedback = Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength)); - DispatchServerMessage(actor.PlayerSession, feedback); + DispatchServerMessage(player, feedback); isOverLength = true; } @@ -629,53 +283,12 @@ namespace Content.Server.Chat.Managers return isOverLength; } - public void ClientDistanceToList(EntityUid source, int voiceRange, List playerSessions) - { - var ghosts = _entManager.GetEntityQuery(); - var xforms = _entManager.GetEntityQuery(); + #endregion + } - var transformSource = xforms.GetComponent(source); - var sourceMapId = transformSource.MapID; - var sourceCoords = transformSource.Coordinates; - - foreach (var player in _playerManager.Sessions) - { - if (player.AttachedEntity is not {Valid: true} playerEntity) - continue; - - var transformEntity = xforms.GetComponent(playerEntity); - - if (transformEntity.MapID != sourceMapId || - !ghosts.HasComponent(playerEntity) && - !sourceCoords.InRange(_entManager, transformEntity.Coordinates, voiceRange)) - continue; - - playerSessions.Add(player); - } - } - - public string ObfuscateMessageReadability(string message, float chance) - { - var modifiedMessage = new StringBuilder(message); - - for (var i = 0; i < message.Length; i++) - { - if (char.IsWhiteSpace((modifiedMessage[i]))) - { - continue; - } - - if (_random.Prob(chance)) - { - modifiedMessage[i] = modifiedMessage[i]; - } - else - { - modifiedMessage[i] = '~'; - } - } - - return modifiedMessage.ToString(); - } + public enum OOCChatType : byte + { + OOC, + Admin } } diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index 3ad8fdef7c..c6f8c15b78 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -1,6 +1,8 @@ +using Content.Shared.Chat; using Robust.Server.Player; using Robust.Shared.Console; using Robust.Shared.GameObjects; +using Robust.Shared.Network; using Robust.Shared.Players; namespace Content.Server.Chat.Managers @@ -23,32 +25,22 @@ namespace Content.Server.Chat.Managers /// /// If the default 'PA' sound should be played. /// Override the color of the message being sent. - void DispatchStationAnnouncement(string message, string sender = "CentComm", bool playDefaultSound = true, Color? colorOverride = null); + void DispatchStationAnnouncement(string message, string sender = "CentComm", bool playDefaultSound = true, + Color? colorOverride = null); void DispatchServerMessage(IPlayerSession player, string message); - /// - /// Tries to use entity say or entity whisper to speak a message. - /// - void TrySpeak(EntityUid source, string message, bool whisper = false, IConsoleShell? shell = null, IPlayerSession? player = null); - - void TryEmote(EntityUid source, string message, IConsoleShell? shell = null, IPlayerSession? player = null); - - /// If true, message will not be logged to chat boxes but will still produce a speech bubble. - void EntitySay(EntityUid source, string message, bool hideChat=false); - void EntityWhisper(EntityUid source, string message, bool hideChat = false); - void EntityMe(EntityUid source, string action); - void SendLOOC(IPlayerSession player, string message); - - void SendOOC(IPlayerSession player, string message); - void SendAdminChat(IPlayerSession player, string message); - void SendDeadChat(IPlayerSession player, string message); - void SendAdminDeadChat(IPlayerSession player, string message); + void TrySendOOCMessage(IPlayerSession player, string message, OOCChatType type); void SendHookOOC(string sender, string message); - - delegate string TransformChat(EntityUid speaker, string message); - void RegisterChatTransform(TransformChat handler); void SendAdminAnnouncement(string message); + + void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, + INetChannel client); + void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, + List clients); + void ChatMessageToAll(ChatChannel channel, string message, string messageWrap, Color? colorOverride = null); + + bool MessageCharacterLimit(IPlayerSession player, string message); } } diff --git a/Content.Server/Disease/DiseaseSystem.cs b/Content.Server/Disease/DiseaseSystem.cs index ccb3a99328..17e8d27fcc 100644 --- a/Content.Server/Disease/DiseaseSystem.cs +++ b/Content.Server/Disease/DiseaseSystem.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Server.Chat; using Content.Shared.Disease; using Content.Shared.Disease.Components; using Content.Server.Disease.Components; @@ -40,6 +41,7 @@ namespace Content.Server.Disease SubscribeLocalEvent(OnTryCureDisease); SubscribeLocalEvent(OnInteractDiseasedHand); SubscribeLocalEvent(OnInteractDiseasedUsing); + SubscribeLocalEvent(OnEntitySpeak); SubscribeLocalEvent(OnEquipped); SubscribeLocalEvent(OnUnequipped); SubscribeLocalEvent(OnAfterInteract); @@ -204,6 +206,14 @@ namespace Content.Server.Disease InteractWithDiseased(args.Target, args.User); } + private void OnEntitySpeak(EntityUid uid, DiseasedComponent component, EntitySpokeEvent args) + { + if (TryComp(uid, out var carrier)) + { + SneezeCough(uid, _random.Pick(carrier.Diseases), string.Empty); + } + } + /// /// Called when a vaccine is used on someone /// to handle the vaccination doafter diff --git a/Content.Server/Radio/Components/HandheldRadioComponent.cs b/Content.Server/Radio/Components/HandheldRadioComponent.cs index d249b248c4..493a58f488 100644 --- a/Content.Server/Radio/Components/HandheldRadioComponent.cs +++ b/Content.Server/Radio/Components/HandheldRadioComponent.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Radio.EntitySystems; using Content.Shared.Examine; @@ -23,7 +24,7 @@ namespace Content.Server.Radio.Components public sealed class HandheldRadioComponent : Component, IListen, IRadio, IActivate, IExamine #pragma warning restore 618 { - [Dependency] private readonly IChatManager _chatManager = default!; + private ChatSystem _chatSystem = default!; private RadioSystem _radioSystem = default!; private bool _radioOn; @@ -54,13 +55,14 @@ namespace Content.Server.Radio.Components base.Initialize(); _radioSystem = EntitySystem.Get(); + _chatSystem = EntitySystem.Get(); RadioOn = false; } public void Speak(string message) { - _chatManager.EntitySay(Owner, message); + _chatSystem.TrySendInGameICMessage(Owner, message, InGameICChatType.Speak, false); } public bool Use(EntityUid user) diff --git a/Content.Server/Speech/AccentSystem.cs b/Content.Server/Speech/AccentSystem.cs index 0a4c18448b..76ca302650 100644 --- a/Content.Server/Speech/AccentSystem.cs +++ b/Content.Server/Speech/AccentSystem.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using Content.Server.Chat; using Content.Server.Chat.Managers; using Content.Server.Speech.Components; using Robust.Shared.GameObjects; @@ -8,22 +9,19 @@ namespace Content.Server.Speech { public sealed class AccentSystem : EntitySystem { - [Dependency] private readonly IChatManager _chatManager = default!; - public static readonly Regex SentenceRegex = new(@"(?<=[\.!\?])", RegexOptions.Compiled); public override void Initialize() { - _chatManager.RegisterChatTransform(AccentHandler); + SubscribeLocalEvent(AccentHandler); } - public string AccentHandler(EntityUid playerUid, string message) + private void AccentHandler(TransformSpeechEvent args) { - var accentEvent = new AccentGetEvent(playerUid, message); + var accentEvent = new AccentGetEvent(args.Sender, args.Message); - RaiseLocalEvent(playerUid, accentEvent); - - return accentEvent.Message; + RaiseLocalEvent(args.Sender, accentEvent); + args.Message = accentEvent.Message; } } diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 996653ad0c..2af5013a82 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -235,6 +235,7 @@ True True True + True True True True