using System.Linq; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; using Content.Server.MoMMI; using Content.Server.Preferences.Managers; using Content.Shared.Administration; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Database; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.Network; using Robust.Shared.Player; using Robust.Shared.Utility; namespace Content.Server.Chat.Managers { /// /// Dispatches chat messages to clients. /// internal sealed class ChatManager : IChatManager { private static readonly Dictionary PatronOocColors = new() { // I had plans for multiple colors and those went nowhere so... { "nuclear_operative", "#aa00ff" }, { "syndicate_agent", "#aa00ff" }, { "revolutionary", "#aa00ff" } }; [Dependency] private readonly IServerNetManager _netManager = 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!; private AdminLogSystem _logs = default!; /// /// The maximum length a player-sent message can be sent /// public int MaxMessageLength => _configurationManager.GetCVar(CCVars.ChatMaxMessageLength); private bool _oocEnabled = true; private bool _adminOocEnabled = true; public void Initialize() { _logs = EntitySystem.Get(); _netManager.RegisterNetMessage(); _configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true); _configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true); } private void OnOocEnabledChanged(bool val) { if (_oocEnabled == val) return; _oocEnabled = val; DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message")); } private void OnAdminOocEnabledChanged(bool val) { if (_adminOocEnabled == val) return; _adminOocEnabled = val; 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"); ChatMessageToAll(ChatChannel.Server, message, messageWrap, colorOverride); Logger.InfoS("SERVER", message); _logs.Add(LogType.Chat, LogImpact.Low, $"Server announcement: {message}"); } 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)); ChatMessageToAll(ChatChannel.Radio, message, messageWrap, colorOverride); if (playDefaultSound) { SoundSystem.Play(Filter.Broadcast(), "/Audio/Announcements/announce.ogg", AudioParams.Default.WithVolume(-2f)); } _logs.Add(LogType.Chat, LogImpact.Low, $"Station Announcement from {sender}: {message}"); } public void DispatchServerMessage(IPlayerSession player, string message) { var messageWrap = Loc.GetString("chat-manager-server-wrap-message"); ChatMessageToOne(ChatChannel.Server, message, messageWrap, default, false, player.ConnectedClient); _logs.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}"); } public void SendAdminAnnouncement(string message) { var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); message = FormattedMessage.EscapeText(message); var messageWrap = Loc.GetString("chat-manager-send-admin-announcement-wrap-message", ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name"))); ChatMessageToMany(ChatChannel.Admin, message, messageWrap, default, false, clients.ToList()); _logs.Add(LogType.Chat, LogImpact.Low, $"Admin announcement from {message}: {message}"); } public void SendHookOOC(string sender, string message) { if (!_oocEnabled && _configurationManager.GetCVar(CCVars.DisablingOOCDisablesRelay)) { return; } message = FormattedMessage.EscapeText(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}"); } #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) { // 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); switch (type) { case OOCChatType.OOC: SendOOC(player, message); break; case OOCChatType.Admin: SendAdminChat(player, message); break; } } #endregion #region Private API private void SendOOC(IPlayerSession player, string message) { if (_adminManager.IsAdmin(player)) { if (!_adminOocEnabled) { return; } } else if (!_oocEnabled) { return; } 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); colorOverride = prefs.AdminOOCColor; } if (player.ConnectedClient.UserData.PatronTier is { } patron && PatronOocColors.TryGetValue(patron, out var patronColor)) { 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 ChatMessageToAll(ChatChannel.OOC, message, messageWrap, colorOverride); _mommiLink.SendOOCMessage(player.Name, message); _logs.Add(LogType.Chat, LogImpact.Low, $"OOC from {player:Player}: {message}"); } private void SendAdminChat(IPlayerSession player, string message) { if (!_adminManager.IsAdmin(player)) { _logs.Add(LogType.Chat, LogImpact.Extreme, $"{player:Player} attempted to send admin message but was not admin"); return; } var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient); var messageWrap = Loc.GetString("chat-manager-send-admin-chat-wrap-message", ("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("playerName", player.Name)); ChatMessageToMany(ChatChannel.Admin, message, messageWrap, default, false, clients.ToList()); _logs.Add(LogType.Chat, $"Admin chat from {player:Player}: {message}"); } #endregion #region Utility public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client) { var msg = new MsgChatMessage(); msg.Channel = channel; msg.Message = message; msg.MessageWrap = messageWrap; msg.SenderEntity = source; msg.HideChat = hideChat; _netManager.ServerSendMessage(msg, client); } public void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, List clients) { var msg = new MsgChatMessage(); 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 = new MsgChatMessage(); msg.Channel = channel; msg.Message = message; msg.MessageWrap = messageWrap; if (colorOverride != null) { msg.MessageColorOverride = colorOverride.Value; } _netManager.ServerSendToAll(msg); } 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 (message.Length > MaxMessageLength) { var feedback = Loc.GetString("chat-manager-max-message-length-exceeded-message", ("limit", MaxMessageLength)); DispatchServerMessage(player, feedback); isOverLength = true; } return isOverLength; } #endregion } public enum OOCChatType : byte { OOC, Admin } }