public void UpdateClientChat(string message, ChatChannel channels, bool isOriginator, GameObject recipient) { if (string.IsNullOrEmpty(message)) { return; } trySendingTTS(message); if (PlayerManager.LocalPlayerScript == null) { channels = ChatChannel.OOC; } if (channels != ChatChannel.None) { // replace action messages with chat bubble if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Examine)) { if (isOriginator) { ChatBubbleManager.Instance.ShowAction(Regex.Replace(message, "<.*?>", string.Empty), recipient); } } ChatUI.Instance.AddChatEntry(message); } }
private void UpdateClientChat(string message, ChatChannel channels) { if (string.IsNullOrEmpty(message)) { return; } trySendingTTS(message); if (PlayerManager.LocalPlayerScript == null) { channels = ChatChannel.OOC; } if (channels != ChatChannel.None) { // replace action messages with chat bubble if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Examine)) { string cleanMessage = Regex.Replace(message, "<.*?>", string.Empty); if (cleanMessage.StartsWith("You")) { ChatBubbleManager.ShowAChatBubble(PlayerManager.LocalPlayerScript.transform, Regex.Replace(message, "<.*?>", string.Empty)); return; } } ChatUI.Instance.AddChatEntry(message); } }
private void UpdateClientChat(string message, ChatChannel channels) { if (string.IsNullOrEmpty(message)) { return; } trySendingTTS(message); if (PlayerManager.LocalPlayerScript == null) { channels = ChatChannel.OOC; } if (channels != ChatChannel.None) { // TODO: remove hardcoded "You" check; chat bubbles should be on their own channel or similar - see issue #5775. // replace action messages with chat bubble if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Examine)) { string cleanMessage = Regex.Replace(message, "<.*?>", string.Empty); if (cleanMessage.StartsWith("You")) { ChatBubbleManager.ShowAction(Regex.Replace(message, "<.*?>", string.Empty)); return; } } ChatUI.Instance.AddChatEntry(message); } }
/// <summary> /// Sends a player message to the server. /// The message is omitted if too many messages have been sent recently. /// The message is shortened if the player has send too many characters recently. /// In either case the player will see a warning in their chat log. /// </summary> /// <param name="message">The player's message.</param> /// <param name="selectedChannels">The selected channels, which are simply passed along.</param> public void Send(string message, ChatChannel selectedChannels) { print(numMessages); DecayFiltersOverTime(); // Decrease cpm and messages since last having spoken // Limit number of messages if (numMessages + 1 > numMessageMax || cpm + 1 > cpmMax) { if (selectedChannels.HasFlag(ChatChannel.OOC) || selectedChannels.HasFlag(ChatChannel.Ghost)) { Chat.AddExamineMsgToClient(numMessagesWarningOOC); } else { Chat.AddExamineMsgToClient(numMessagesWarning); } return; } // Users message will (at least partiall) be spoken, so count it. numMessages++; cpm += message.Length; // Limit characters per minute int numCharsOverLimit = 0; if (cpm > cpmMax) { // Too many characters, calculate how many need to be removed. float cpmOver = cpm - cpmMax; cpm = cpmMax; // Characters will be removed, so cpm must be lowered again. numCharsOverLimit = (int)Math.Floor(cpmOver); message = message.Remove(message.Length - numCharsOverLimit) + "..."; } // Don't send message if it got shortened below the limit. if (0 < numCharsOverLimit && numCharsOverLimit < cpmMinCharacters) { return; } // Send message, which might have been shortened because of the character limit per minute. PostToChatMessage.Send(message, selectedChannels); // Notify player that their message got cut short. if (numCharsOverLimit > 0) { if (selectedChannels.HasFlag(ChatChannel.OOC) || selectedChannels.HasFlag(ChatChannel.Ghost)) { Chat.AddExamineMsgToClient(cpmWarningOOC); } else { Chat.AddExamineMsgToClient(cpmWarning); } } }
private static bool IsNamelessChan(ChatChannel channel) { if (channel.HasFlag(ChatChannel.System) || channel.HasFlag(ChatChannel.Combat) || channel.HasFlag(ChatChannel.Action) || channel.HasFlag(ChatChannel.Examine)) { return(true); } return(false); }
private void OnChatInputChanged(string newMsg, ChatChannel selectedChannels) { if (string.IsNullOrEmpty(newMsg)) { return; } var hasBlacklistedChannel = BlacklistChannels.HasFlag(selectedChannels); if (isPlayerTyping) { // update last typing time lastTypingTime = Time.time; // if player was typing before, but changed channel to blacklist channel if (hasBlacklistedChannel) { StopTyping(); } } else { // if player stared typing in allowed channel if (!hasBlacklistedChannel) { StartTyping(); } } }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { var player = sentByPlayer.Script; var chatEvent = new ChatEvent { message = message, modifiers = (player == null) ? ChatModifier.None : player.GetCurrentChatModifiers(), speaker = player.name, position = ((player == null) ? Vector2.zero : (Vector2)player.gameObject.transform.position), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; Instance.addChatLogServer.Invoke(chatEvent); return; } // There could be multiple channels we need to send a message for each. // We do this on the server side that local chans can be determined correctly foreach (Enum value in Enum.GetValues(channels.GetType())) { if (channels.HasFlag((ChatChannel)value)) { //Using HasFlag will always return true for flag at value 0 so skip it if ((ChatChannel)value == ChatChannel.None) { continue; } if (IsNamelessChan((ChatChannel)value)) { continue; } chatEvent.channels = (ChatChannel)value; Instance.addChatLogServer.Invoke(chatEvent); } } }
public static List <ChatChannel> getChannelsByMask(ChatChannel channelMask) { List <ChatChannel> channelsByMask = Enum.GetValues(typeof(ChatChannel)) .Cast <ChatChannel>() .Where(value => channelMask.HasFlag(value)) .ToList(); channelsByMask.Remove(ChatChannel.None); return(channelsByMask); }
private static bool IsOnCorrectChannels(ChatChannel channels) { if (channels.HasFlag(ChatChannel.Common) || channels.HasFlag(ChatChannel.Command) || channels.HasFlag(ChatChannel.Security) || channels.HasFlag(ChatChannel.Engineering) || channels.HasFlag(ChatChannel.Medical) || channels.HasFlag(ChatChannel.Science) || channels.HasFlag(ChatChannel.Syndicate) || channels.HasFlag(ChatChannel.Supply)) { return(true); } return(false); }
private void UpdatePossibleChannels() { // get available channels ChatChannel chatChannels = ChatUI.Instance.GetAvailableChannels(); // get all chat channels ChatChannel[] allChannels = (ChatChannel[])Enum.GetValues(typeof(ChatChannel)); StringBuilder newContent = new StringBuilder(); // start from 4 to skip 'None', 'Examine', 'Local', 'OOC' for (int i = 4; i < allChannels.Length; i++) { ChatChannel channel = allChannels[i]; // player have access to this channel if (chatChannels.HasFlag(channel)) { newContent.Append(GetChannelEntry(channel)); } } channelsText.text = newContent.ToString(); }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels, Loudness loudness = Loudness.NORMAL) { message = AutoMod.ProcessChatServer(sentByPlayer, message); if (string.IsNullOrWhiteSpace(message)) { return; } //Sanity check for null username if (string.IsNullOrWhiteSpace(sentByPlayer.Username)) { Logger.Log($"Null/empty Username, Details: Username: {sentByPlayer.Username}, ClientID: {sentByPlayer.ClientId}, IP: {sentByPlayer.Connection.address}", Category.Admin); return; } var player = sentByPlayer.Script; //Check to see whether this player is allowed to send on the chosen channels if (player != null) { channels &= player.GetAvailableChannelsMask(true); } else { //If player is null, must be in lobby therefore lock to OOC channels = ChatChannel.OOC; } if (channels == ChatChannel.None) { return; } // The exact words that leave the player's mouth (or that are narrated). Already includes HONKs, stutters, etc. // This step is skipped when speaking in the OOC channel. (string message, ChatModifier chatModifiers)processedMessage = (string.Empty, ChatModifier.None); // Placeholder values bool isOOC = channels.HasFlag(ChatChannel.OOC); if (!isOOC) { processedMessage = ProcessMessage(sentByPlayer, message); if (string.IsNullOrWhiteSpace(processedMessage.message)) { return; } } var chatEvent = new ChatEvent { message = isOOC ? message : processedMessage.message, modifiers = (player == null) ? ChatModifier.None : processedMessage.chatModifiers, speaker = (player == null) ? sentByPlayer.Username : player.playerName, position = (player == null) ? TransformState.HiddenPos : player.PlayerChatLocation.AssumedWorldPosServer(), channels = channels, originator = (player == null) ? sentByPlayer.GameObject : player.PlayerChatLocation, VoiceLevel = loudness }; //This is to make sure OOC doesn't break if (sentByPlayer.Job != JobType.NULL) { CheckVoiceLevel(sentByPlayer.Script, chatEvent.channels); } if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = StripAll(sentByPlayer.Username); var isAdmin = PlayerList.Instance.IsAdmin(sentByPlayer.UserId); if (isAdmin) { chatEvent.speaker = "<color=red>[A]</color> " + chatEvent.speaker; } else if (PlayerList.Instance.IsMentor(sentByPlayer.UserId)) { chatEvent.speaker = "<color=#6400ff>[M]</color> " + chatEvent.speaker; } if (Instance.OOCMute && !isAdmin) { return; } //http/https links in OOC chat if (isAdmin || !GameManager.Instance.AdminOnlyHtml) { if (htmlRegex.IsMatch(chatEvent.message)) { var messageParts = chatEvent.message.Split(' '); var builder = new StringBuilder(); foreach (var part in messageParts) { if (!htmlRegex.IsMatch(part)) { builder.Append(part); builder.Append(" "); continue; } builder.Append($"<link={part}><color=blue>{part}</color></link> "); } chatEvent.message = builder.ToString(); //TODO have a config file available to whitelist/blacklist links if all players are allowed to post links //disables client side tag protection to allow <link=></link> tag chatEvent.stripTags = false; } } ChatRelay.Instance.PropagateChatToClients(chatEvent); var strippedSpeaker = StripTags(chatEvent.speaker); //Sends OOC message to a discord webhook DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookOOCURL, message, strippedSpeaker, ServerData.ServerConfig.DiscordWebhookOOCMentionsID); if (!ServerData.ServerConfig.DiscordWebhookSendOOCToAllChat) { return; } //Send it to All chat DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookAllChatURL, $"[{ChatChannel.OOC}] {message}\n", strippedSpeaker); return; } // TODO the following code uses player.playerHealth, but ConciousState would be more appropriate. // Check if the player is allowed to talk: if (player != null) { if (player.playerHealth != null) { if (!player.IsDeadOrGhost && player.mind.IsMiming && !processedMessage.chatModifiers.HasFlag(ChatModifier.Emote)) { AddWarningMsgFromServer(sentByPlayer.GameObject, "You can't talk because you made a vow of silence."); return; } if (player.playerHealth.IsCrit) { if (!player.playerHealth.IsDead) { return; } else { chatEvent.channels = ChatChannel.Ghost; } } else if (!player.playerHealth.IsDead && !player.IsGhost) { //Control the chat bubble player.playerNetworkActions.ServerToggleChatIcon(true, processedMessage.message, channels, processedMessage.chatModifiers); } } } InvokeChatEvent(chatEvent); }
/// <summary> /// Processes message further for the chat log. /// Adds text styling, color and channel prefixes depending on the message and its modifiers. /// </summary> /// <returns>The chat message, formatted to suit the chat log.</returns> public static string ProcessMessageFurther(string message, string speaker, ChatChannel channels, ChatModifier modifiers, bool stripTags = true) { playedSound = false; //Highlight in game name by bolding and underlining if possible //Dont play sound here as it could be examine or action, we only play sound for someone speaking message = HighlightInGameName(message, false); //Skip everything if system message if (channels.HasFlag(ChatChannel.System)) { return(message); } //Skip everything in case of combat channel if (channels.HasFlag(ChatChannel.Combat)) { return(AddMsgColor(channels, $"<i>{message}</i>")); //POC } //Skip everything if it is an action or examine message or if it is a local message //without a speaker (which is used by machines) if (channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Local) && string.IsNullOrEmpty(speaker)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } // Skip everything if the message is a local warning if (channels.HasFlag(ChatChannel.Warning)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } if (stripTags) { message = StripTags(message); //Bold names again after tag stripping message = HighlightInGameName(message); } //Check for emote. If found skip chat modifiers, make sure emote is only in Local channel if ((modifiers & ChatModifier.Emote) == ChatModifier.Emote) { // /me message channels = ChatChannel.Local; if (playerGameObject != null) { DoEmoteAction(message, playerGameObject, Instance.emoteActionManager); playerGameObject = null; return(""); } message = AddMsgColor(channels, $"<i><b>{speaker}</b> {message}</i>"); return(message); } //Check for OOC. If selected, remove all other channels and modifiers (could happen if UI f***s up or someone tampers with it) if (channels.HasFlag(ChatChannel.OOC)) { //ooc name quick fix var name = Regex.Replace(speaker, @"\t\n\r", ""); if (string.IsNullOrWhiteSpace(name)) { name = "nerd"; } //highlight OOC name by bolding and underlining if possible message = HighlightName(message, ServerData.Auth.CurrentUser.DisplayName); message = AddMsgColor(channels, $"[ooc] <b>{name}: {message}</b>"); return(message); } //Ghosts don't get modifiers if (channels.HasFlag(ChatChannel.Ghost)) { string[] _ghostVerbs = { "cries", "moans" }; return(AddMsgColor(channels, $"[dead] <b>{speaker}</b> {_ghostVerbs.PickRandom()}: {message}")); } string verb = "says,"; if ((modifiers & ChatModifier.Mute) == ChatModifier.Mute) { return(""); } if ((modifiers & ChatModifier.Whisper) == ChatModifier.Whisper) { verb = "whispers,"; message = $"<i>{message}</i>"; } else if ((modifiers & ChatModifier.Sing) == ChatModifier.Sing) { verb = "sings,"; message += " ♫"; } else if ((modifiers & ChatModifier.Yell) == ChatModifier.Yell) { verb = "yells,"; message = $"<b>{message}</b>"; } else if ((modifiers & ChatModifier.State) == ChatModifier.State) { verb = "states,"; } else if ((modifiers & ChatModifier.ColdlyState) == ChatModifier.ColdlyState) { verb = " coldly states,"; } else if (message.EndsWith("!")) { verb = "exclaims,"; } else if (message.EndsWith("?")) { verb = "asks,"; } var chan = $"[{channels.ToString().ToLower().Substring(0, 3)}] "; if (channels.HasFlag(ChatChannel.Command)) { chan = "[cmd] "; } if (channels.HasFlag(ChatChannel.Local)) { chan = ""; } return(AddMsgColor(channels, $"{chan}<b>{speaker}</b> {verb}" // [cmd] Username says, + " " // Two hair spaces. This triggers Text-to-Speech. + "\"" + message + "\"")); // "This text will be spoken by TTS!" }
private void PropagateChatToClients(ChatEvent chatEvent) { List <ConnectedPlayer> players = PlayerList.Instance.AllPlayers; //Local chat range checks: if (chatEvent.channels.HasFlag(ChatChannel.Local) || chatEvent.channels.HasFlag(ChatChannel.Combat) || chatEvent.channels.HasFlag(ChatChannel.Action)) { for (int i = players.Count - 1; i >= 0; i--) { if (players[i].Script == null) { //joined viewer, don't message them players.RemoveAt(i); continue; } if (players[i].Script.IsGhost) { //send all to ghosts continue; } if (chatEvent.position == TransformState.HiddenPos) { //show messages with no provided position to everyone continue; } var playerPosition = players[i].GameObject.AssumedWorldPosServer(); if (Vector2.Distance(chatEvent.position, playerPosition) > 14f) { //Player in the list is too far away for local chat, remove them: players.RemoveAt(i); } else { //within range, but check if they are in another room or hiding behind a wall if (MatrixManager.Linecast(chatEvent.position, LayerTypeSelection.Walls , layerMask, playerPosition).ItHit) { //if it hit a wall remove that player players.RemoveAt(i); } } } //Get NPCs in vicinity var npcs = Physics2D.OverlapCircleAll(chatEvent.position, 14f, npcMask); foreach (Collider2D coll in npcs) { var npcPosition = coll.gameObject.AssumedWorldPosServer(); if (MatrixManager.Linecast(chatEvent.position, LayerTypeSelection.Walls, layerMask, npcPosition).ItHit == false) { //NPC is in hearing range, pass the message on: var mobAi = coll.GetComponent <MobAI>(); if (mobAi != null) { mobAi.LocalChatReceived(chatEvent); } } } } for (var i = 0; i < players.Count; i++) { ChatChannel channels = chatEvent.channels; if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Local) || channels.HasFlag(ChatChannel.System) || channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action)) { if (!channels.HasFlag(ChatChannel.Binary) || players[i].Script.IsGhost) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker); continue; } } if (players[i].Script == null) { channels &= ChatChannel.OOC; } else { channels &= players[i].Script.GetAvailableChannelsMask(false); } //if the mask ends up being a big fat 0 then don't do anything if (channels != ChatChannel.None) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker); } } if (rconManager != null) { string name = ""; if ((namelessChannels & chatEvent.channels) != chatEvent.channels) { name = "<b>[" + chatEvent.channels + "]</b> "; } RconManager.AddChatLog(name + chatEvent.message); } }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { var player = sentByPlayer.Script; var chatEvent = new ChatEvent { message = message, modifiers = (player == null) ? ChatModifier.None : player.GetCurrentChatModifiers(), speaker = (player == null) ? sentByPlayer.Username : player.name, position = ((player == null) ? Vector2.zero : (Vector2)player.gameObject.transform.position), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; Instance.addChatLogServer.Invoke(chatEvent); return; } //Check if the player is allowed to talk: if (player.playerHealth != null) { if (player.playerHealth.IsCrit || player.playerHealth.IsCardiacArrest) { if (!player.playerHealth.IsDead) { return; } else { channels = ChatChannel.Ghost; } } else { if (!player.playerHealth.IsDead && !player.IsGhost) { { //Control the chat bubble player.playerNetworkActions.CmdToggleChatIcon(true, message, channels); } } } } // There could be multiple channels we need to send a message for each. // We do this on the server side that local chans can be determined correctly foreach (Enum value in Enum.GetValues(channels.GetType())) { if (channels.HasFlag((ChatChannel)value)) { //Using HasFlag will always return true for flag at value 0 so skip it if ((ChatChannel)value == ChatChannel.None) { continue; } if (IsNamelessChan((ChatChannel)value)) { continue; } chatEvent.channels = (ChatChannel)value; Instance.addChatLogServer.Invoke(chatEvent); } } }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { var player = sentByPlayer.Script; // The exact words that leave the player's mouth (or that are narrated). Already includes HONKs, stutters, etc. // This step is skipped when speaking in the OOC channel. (string message, ChatModifier chatModifiers)processedMessage = (string.Empty, ChatModifier.None); // Placeholder values bool isOOC = channels.HasFlag(ChatChannel.OOC); if (!isOOC) { processedMessage = ProcessMessage(sentByPlayer, message); } var chatEvent = new ChatEvent { message = isOOC ? message : processedMessage.message, modifiers = (player == null) ? ChatModifier.None : processedMessage.chatModifiers, speaker = (player == null) ? sentByPlayer.Username : player.name, position = ((player == null) ? TransformState.HiddenPos : player.WorldPos), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; Instance.addChatLogServer.Invoke(chatEvent); return; } // TODO the following code uses player.playerHealth, but ConciousState would be more appropriate. // Check if the player is allowed to talk: if (player != null && player.playerHealth != null) { if (player.playerHealth.IsCrit || player.playerHealth.IsCardiacArrest) { if (!player.playerHealth.IsDead) { return; } else { channels = ChatChannel.Ghost; } } else if (!player.playerHealth.IsDead && !player.IsGhost) { //Control the chat bubble player.playerNetworkActions.CmdToggleChatIcon(true, processedMessage.message, channels, processedMessage.chatModifiers); } } // There could be multiple channels we need to send a message for each. // We do this on the server side that local chans can be determined correctly foreach (Enum value in Enum.GetValues(channels.GetType())) { if (channels.HasFlag((ChatChannel)value)) { //Using HasFlag will always return true for flag at value 0 so skip it if ((ChatChannel)value == ChatChannel.None) { continue; } if (IsNamelessChan((ChatChannel)value)) { continue; } chatEvent.channels = (ChatChannel)value; Instance.addChatLogServer.Invoke(chatEvent); } } }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { message = AutoMod.ProcessChatServer(sentByPlayer, message); if (string.IsNullOrWhiteSpace(message)) { return; } var player = sentByPlayer.Script; // The exact words that leave the player's mouth (or that are narrated). Already includes HONKs, stutters, etc. // This step is skipped when speaking in the OOC channel. (string message, ChatModifier chatModifiers)processedMessage = (string.Empty, ChatModifier.None); // Placeholder values bool isOOC = channels.HasFlag(ChatChannel.OOC); if (!isOOC) { processedMessage = ProcessMessage(sentByPlayer, message); } var chatEvent = new ChatEvent { message = isOOC ? message : processedMessage.message, modifiers = (player == null) ? ChatModifier.None : processedMessage.chatModifiers, speaker = (player == null) ? sentByPlayer.Username : player.name, position = (player == null) ? TransformState.HiddenPos : player.gameObject.AssumedWorldPosServer(), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; var isAdmin = PlayerList.Instance.IsAdmin(sentByPlayer.UserId); if (isAdmin) { chatEvent.speaker = "[Admin] " + chatEvent.speaker; } if (Instance.OOCMute && !isAdmin) { return; } Instance.addChatLogServer.Invoke(chatEvent); //Sends OOC message to a discord webhook DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookOOCURL, message, chatEvent.speaker, ServerData.ServerConfig.DiscordWebhookOOCMentionsID); if (!ServerData.ServerConfig.DiscordWebhookSendOOCToAllChat) { return; } //Send it to All chat DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookAllChatURL, $"[{ChatChannel.OOC}] {message}\n", chatEvent.speaker); return; } // TODO the following code uses player.playerHealth, but ConciousState would be more appropriate. // Check if the player is allowed to talk: if (player != null && player.playerHealth != null) { if (!player.IsDeadOrGhost && player.mind.IsMiming && !processedMessage.chatModifiers.HasFlag(ChatModifier.Emote)) { AddWarningMsgFromServer(sentByPlayer.GameObject, "You can't talk because you made a vow of silence."); return; } if (player.playerHealth.IsCrit || player.playerHealth.IsCardiacArrest) { if (!player.playerHealth.IsDead) { return; } else { chatEvent.channels = ChatChannel.Ghost; } } else if (!player.playerHealth.IsDead && !player.IsGhost) { //Control the chat bubble player.playerNetworkActions.CmdToggleChatIcon(true, processedMessage.message, channels, processedMessage.chatModifiers); } } InvokeChatEvent(chatEvent); }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { message = AutoMod.ProcessChatServer(sentByPlayer, message); if (string.IsNullOrWhiteSpace(message)) { return; } var player = sentByPlayer.Script; // The exact words that leave the player's mouth (or that are narrated). Already includes HONKs, stutters, etc. // This step is skipped when speaking in the OOC channel. (string message, ChatModifier chatModifiers)processedMessage = (string.Empty, ChatModifier.None); // Placeholder values bool isOOC = channels.HasFlag(ChatChannel.OOC); if (!isOOC) { processedMessage = ProcessMessage(sentByPlayer, message); } var chatEvent = new ChatEvent { message = isOOC ? message : processedMessage.message, modifiers = (player == null) ? ChatModifier.None : processedMessage.chatModifiers, speaker = (player == null) ? sentByPlayer.Username : player.name, position = (player == null) ? TransformState.HiddenPos : player.gameObject.AssumedWorldPosServer(), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; var isAdmin = PlayerList.Instance.IsAdmin(sentByPlayer.UserId); if (isAdmin) { chatEvent.speaker = "<color=red>[Admin]</color> " + chatEvent.speaker; } else if (PlayerList.Instance.IsMentor(sentByPlayer.UserId)) { chatEvent.speaker = "<color=#6400ff>[Mentor]</color> " + chatEvent.speaker; } if (Instance.OOCMute && !isAdmin) { return; } //http/https links in OOC chat if (isAdmin || !GameManager.Instance.AdminOnlyHtml) { if (htmlRegex.IsMatch(chatEvent.message)) { var messageParts = chatEvent.message.Split(' '); var builder = new StringBuilder(); foreach (var part in messageParts) { if (!htmlRegex.IsMatch(part)) { builder.Append(part); builder.Append(" "); continue; } builder.Append($"<link={part}><color=blue>{part}</color></link> "); } chatEvent.message = builder.ToString(); //TODO have a config file available to whitelist/blacklist links if all players are allowed to post links //disables client side tag protection to allow <link=></link> tag chatEvent.stripTags = false; } } Instance.addChatLogServer.Invoke(chatEvent); //Sends OOC message to a discord webhook DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookOOCURL, message, chatEvent.speaker, ServerData.ServerConfig.DiscordWebhookOOCMentionsID); if (!ServerData.ServerConfig.DiscordWebhookSendOOCToAllChat) { return; } //Send it to All chat DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookAllChatURL, $"[{ChatChannel.OOC}] {message}\n", chatEvent.speaker); return; } // TODO the following code uses player.playerHealth, but ConciousState would be more appropriate. // Check if the player is allowed to talk: if (player != null && player.playerHealth != null) { if (!player.IsDeadOrGhost && player.mind.IsMiming && !processedMessage.chatModifiers.HasFlag(ChatModifier.Emote)) { AddWarningMsgFromServer(sentByPlayer.GameObject, "You can't talk because you made a vow of silence."); return; } if (player.playerHealth.IsCrit || player.playerHealth.IsCardiacArrest) { if (!player.playerHealth.IsDead) { return; } else { chatEvent.channels = ChatChannel.Ghost; } } else if (!player.playerHealth.IsDead && !player.IsGhost) { //Control the chat bubble player.playerNetworkActions.CmdToggleChatIcon(true, processedMessage.message, channels, processedMessage.chatModifiers); } } InvokeChatEvent(chatEvent); }
/// <summary> /// Processes message further for the chat log. /// Adds text styling, color and channel prefixes depending on the message and its modifiers. /// </summary> /// <returns>The chat message, formatted to suit the chat log.</returns> public static string ProcessMessageFurther(string message, string speaker, ChatChannel channels, ChatModifier modifiers) { //Skip everything if system message if (channels.HasFlag(ChatChannel.System)) { return(message); } //Skip everything in case of combat channel if (channels.HasFlag(ChatChannel.Combat)) { return(AddMsgColor(channels, $"<i>{message}</i>")); //POC } //Skip everything if it is an action or examine message or if it is a local message //without a speaker (which is used by machines) if (channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Local) && string.IsNullOrEmpty(speaker)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } // Skip everything if the message is a local warning if (channels.HasFlag(ChatChannel.Warning)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } message = StripTags(message); //Check for emote. If found skip chat modifiers, make sure emote is only in Local channel if ((modifiers & ChatModifier.Emote) == ChatModifier.Emote) { // /me message channels = ChatChannel.Local; message = AddMsgColor(channels, $"<i><b>{speaker}</b> {message}</i>"); return(message); } //Check for OOC. If selected, remove all other channels and modifiers (could happen if UI f***s up or someone tampers with it) if (channels.HasFlag(ChatChannel.OOC)) { //ooc name quick fix var name = Regex.Replace(speaker, @"\t\n\r", ""); if (string.IsNullOrWhiteSpace(name)) { name = "nerd"; } message = AddMsgColor(channels, $"[ooc] <b>{speaker}: {message}</b>"); return(message); } //Ghosts don't get modifiers if (channels.HasFlag(ChatChannel.Ghost)) { string[] _ghostVerbs = { "cries", "moans" }; return(AddMsgColor(channels, $"[dead] <b>{speaker}</b> {_ghostVerbs.PickRandom()}: {message}")); } string verb = "says,"; if ((modifiers & ChatModifier.Mute) == ChatModifier.Mute) { return(""); } if ((modifiers & ChatModifier.Whisper) == ChatModifier.Whisper) { verb = "whispers,"; message = $"<i>{message}</i>"; } else if ((modifiers & ChatModifier.Sing) == ChatModifier.Sing) { verb = "sings,"; message += " ♫"; } else if ((modifiers & ChatModifier.Yell) == ChatModifier.Yell) { verb = "yells,"; message = $"<b>{message}</b>"; } else if ((modifiers & ChatModifier.State) == ChatModifier.State) { verb = "states,"; } else if ((modifiers & ChatModifier.ColdlyState) == ChatModifier.ColdlyState) { verb = "coldly states,"; } else if (message.EndsWith("!")) { verb = "exclaims,"; } else if (message.EndsWith("?")) { verb = "asks,"; } var chan = $"[{channels.ToString().ToLower().Substring(0, 3)}] "; if (channels.HasFlag(ChatChannel.Command)) { chan = "[cmd] "; } if (channels.HasFlag(ChatChannel.Local)) { chan = ""; } return(AddMsgColor(channels, $"{chan}<b>{speaker}</b> {verb}" // [cmd] Username says, + " " // Two hair spaces. This triggers Text-to-Speech. + "\"" + message + "\"")); // "This text will be spoken by TTS!" }
private void PropagateChatToClients(ChatEvent chatEvent) { List <ConnectedPlayer> players; if (chatEvent.matrix != MatrixInfo.Invalid) { //get players only on provided matrix players = PlayerList.Instance.GetPlayersOnMatrix(chatEvent.matrix); } else { players = PlayerList.Instance.AllPlayers; } //Local chat range checks: if (chatEvent.channels == ChatChannel.Local || chatEvent.channels == ChatChannel.Combat || chatEvent.channels == ChatChannel.Action) { // var speaker = PlayerList.Instance.Get(chatEvent.speaker); LayerMask layerMask = LayerMask.GetMask("Walls", "Door Closed"); for (int i = 0; i < players.Count(); i++) { if (players[i].Script == null) { //joined viewer, don't message them players.Remove(players[i]); continue; } if (players[i].Script.IsGhost) { //send all to ghosts continue; } if (Vector2.Distance(chatEvent.position, //speaker.GameObject.transform.position, players[i].GameObject.transform.position) > 14f) { //Player in the list is too far away for local chat, remove them: players.Remove(players[i]); } else { //within range, but check if they are in another room or hiding behind a wall if (Physics2D.Linecast(chatEvent.position, //speaker.GameObject.transform.position, players[i].GameObject.transform.position, layerMask)) { //if it hit a wall remove that player players.Remove(players[i]); } } } //Get NPCs in vicinity var npcMask = LayerMask.GetMask("NPC"); var npcs = Physics2D.OverlapCircleAll(chatEvent.position, 14f, npcMask); foreach (Collider2D coll in npcs) { if (!Physics2D.Linecast(chatEvent.position, coll.transform.position, layerMask)) { //NPC is in hearing range, pass the message on: var mobAi = coll.GetComponent <MobAI>(); if (mobAi != null) { mobAi.LocalChatReceived(chatEvent); } } } } for (var i = 0; i < players.Count; i++) { ChatChannel channels = chatEvent.channels; if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Local) || channels.HasFlag(ChatChannel.System) || channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action)) { if (!channels.HasFlag(ChatChannel.Binary) || players[i].Script.IsGhost) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker); continue; } } if (players[i].Script == null) { channels &= ChatChannel.OOC; } else { channels &= players[i].Script.GetAvailableChannelsMask(false); } //if the mask ends up being a big fat 0 then don't do anything if (channels != ChatChannel.None) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker); } } if (RconManager.Instance != null) { string name = ""; if ((namelessChannels & chatEvent.channels) != chatEvent.channels) { name = "<b>[" + chatEvent.channels + "]</b> "; } RconManager.AddChatLog(name + chatEvent.message); } }
public void PropagateChatToClients(ChatEvent chatEvent) { List <ConnectedPlayer> players = PlayerList.Instance.AllPlayers; //Local chat range checks: if (chatEvent.channels.HasFlag(ChatChannel.Local) || chatEvent.channels.HasFlag(ChatChannel.Combat) || chatEvent.channels.HasFlag(ChatChannel.Action)) { for (int i = players.Count - 1; i >= 0; i--) { if (players[i].Script == null) { //joined viewer, don't message them players.RemoveAt(i); continue; } if (players[i].Script.gameObject == chatEvent.originator) { //Always send the originator chat to themselves continue; } if (players[i].Script.IsGhost && players[i].Script.IsPlayerSemiGhost == false) { //send all to ghosts continue; } if (chatEvent.position == TransformState.HiddenPos) { //show messages with no provided position to everyone continue; } //Send chat to PlayerChatLocation pos, usually just the player object but for AI is its vessel var playerPosition = players[i].Script.PlayerChatLocation.OrNull()?.AssumedWorldPosServer() ?? players[i].Script.gameObject.AssumedWorldPosServer(); //Do player position to originator distance check if (DistanceCheck(playerPosition) == false) { //Distance check failed so if we are Ai, then try send action and combat messages to their camera location //as well as if possible if (chatEvent.channels.HasFlag(ChatChannel.Local) == false && players[i].Script.PlayerState == PlayerScript.PlayerStates.Ai && players[i].Script.TryGetComponent <AiPlayer>(out var aiPlayer) && aiPlayer.IsCarded == false) { playerPosition = players[i].Script.gameObject.AssumedWorldPosServer(); //Check camera pos if (DistanceCheck(playerPosition)) { //Camera can see player, allow Ai to see action/combat messages continue; } } //Player failed distance checks remove them players.RemoveAt(i); } bool DistanceCheck(Vector3 playerPos) { //TODO maybe change this to (chatEvent.position - playerPos).sqrMagnitude > 196f to avoid square root for performance? if (Vector2.Distance(chatEvent.position, playerPos) > 14f) { //Player in the list is too far away for local chat, remove them: return(false); } //Within range, but check if they are in another room or hiding behind a wall if (MatrixManager.Linecast(chatEvent.position, LayerTypeSelection.Walls, layerMask, playerPos).ItHit) { //If it hit a wall remove that player return(false); } //Player can see the position return(true); } } //Get NPCs in vicinity var npcs = Physics2D.OverlapCircleAll(chatEvent.position, 14f, npcMask); foreach (Collider2D coll in npcs) { var npcPosition = coll.gameObject.AssumedWorldPosServer(); if (MatrixManager.Linecast(chatEvent.position, LayerTypeSelection.Walls, layerMask, npcPosition).ItHit == false) { //NPC is in hearing range, pass the message on: var mobAi = coll.GetComponent <MobAI>(); if (mobAi != null) { mobAi.LocalChatReceived(chatEvent); } } } } for (var i = 0; i < players.Count; i++) { ChatChannel channels = chatEvent.channels; if (channels.HasFlag(ChatChannel.Combat) || channels.HasFlag(ChatChannel.Local) || channels.HasFlag(ChatChannel.System) || channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action)) { //Binary check here to avoid speaking in local when speaking on binary if (!channels.HasFlag(ChatChannel.Binary) || (players[i].Script.IsGhost && players[i].Script.IsPlayerSemiGhost == false)) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker, chatEvent.stripTags); continue; } } if (players[i].Script == null) { channels &= ChatChannel.OOC; } else { channels &= players[i].Script.GetAvailableChannelsMask(false); } //if the mask ends up being a big fat 0 then don't do anything if (channels != ChatChannel.None) { UpdateChatMessage.Send(players[i].GameObject, channels, chatEvent.modifiers, chatEvent.message, chatEvent.messageOthers, chatEvent.originator, chatEvent.speaker, chatEvent.stripTags); } } if (rconManager != null) { string name = ""; if ((namelessChannels & chatEvent.channels) != chatEvent.channels) { name = "<b>[" + chatEvent.channels + "]</b> "; } RconManager.AddChatLog(name + chatEvent.message); } }
public static string ProcessMessageFurther(string message, string speaker, ChatChannel channels, ChatModifier modifiers) { //Skip everything if system message if (channels.HasFlag(ChatChannel.System)) { return(message); } //Skip everything in case of combat channel if (channels.HasFlag(ChatChannel.Combat)) { return(AddMsgColor(channels, $"<i>{message}</i>")); //POC } //Skip everything if it is an action or examine message or if it is a local message //without a speaker (which is used by machines) if (channels.HasFlag(ChatChannel.Examine) || channels.HasFlag(ChatChannel.Action) || channels.HasFlag(ChatChannel.Local) && string.IsNullOrEmpty(speaker)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } // Skip everything if the message is a local warning if (channels.HasFlag(ChatChannel.Warning)) { return(AddMsgColor(channels, $"<i>{message}</i>")); } message = StripTags(message); //Check for emote. If found skip chat modifiers, make sure emote is only in Local channel Regex rx = new Regex("^(/me )"); if (rx.IsMatch(message)) { // /me message channels = ChatChannel.Local; message = rx.Replace(message, " "); message = AddMsgColor(channels, $"<i><b>{speaker}</b> {message}</i>"); return(message); } //Check for OOC. If selected, remove all other channels and modifiers (could happen if UI f***s up or someone tampers with it) if (channels.HasFlag(ChatChannel.OOC)) { message = AddMsgColor(channels, $"[ooc] <b>{speaker}: {message}</b>"); return(message); } //Ghosts don't get modifiers if (channels.HasFlag(ChatChannel.Ghost)) { return(AddMsgColor(channels, $"[dead] <b>{speaker}</b>: {message}")); } message = ApplyModifiers(message, modifiers); if (message.Length < 1) { return(""); } var verb = "says:"; var toUpperCheck = message.ToUpper(CultureInfo.InvariantCulture); if (message.Contains("!") && toUpperCheck != message) { verb = "exclaims,"; } if (toUpperCheck == message) { verb = "yells,"; message = $"<b>{message}</b>"; } var chan = $"[{channels.ToString().ToLower().Substring(0, 3)}] "; if (channels.HasFlag(ChatChannel.Command)) { chan = "[cmd] "; } if (channels.HasFlag(ChatChannel.Local)) { chan = ""; } return(AddMsgColor(channels, $"{chan}<b>{speaker}</b> {verb} " + "\"" + message + "\"")); }
private static string GetChannelColor(ChatChannel channel) { if (channel.HasFlag(ChatChannel.OOC)) { return(ColorUtility.ToHtmlStringRGBA(Instance.oocColor)); } if (channel.HasFlag(ChatChannel.Ghost)) { return(ColorUtility.ToHtmlStringRGBA(Instance.ghostColor)); } if (channel.HasFlag(ChatChannel.Binary)) { return(ColorUtility.ToHtmlStringRGBA(Instance.binaryColor)); } if (channel.HasFlag(ChatChannel.Supply)) { return(ColorUtility.ToHtmlStringRGBA(Instance.supplyColor)); } if (channel.HasFlag(ChatChannel.CentComm)) { return(ColorUtility.ToHtmlStringRGBA(Instance.centComColor)); } if (channel.HasFlag(ChatChannel.Command)) { return(ColorUtility.ToHtmlStringRGBA(Instance.commandColor)); } if (channel.HasFlag(ChatChannel.Common)) { return(ColorUtility.ToHtmlStringRGBA(Instance.commonColor)); } if (channel.HasFlag(ChatChannel.Engineering)) { return(ColorUtility.ToHtmlStringRGBA(Instance.engineeringColor)); } if (channel.HasFlag(ChatChannel.Medical)) { return(ColorUtility.ToHtmlStringRGBA(Instance.medicalColor)); } if (channel.HasFlag(ChatChannel.Science)) { return(ColorUtility.ToHtmlStringRGBA(Instance.scienceColor)); } if (channel.HasFlag(ChatChannel.Security)) { return(ColorUtility.ToHtmlStringRGBA(Instance.securityColor)); } if (channel.HasFlag(ChatChannel.Service)) { return(ColorUtility.ToHtmlStringRGBA(Instance.serviceColor)); } if (channel.HasFlag(ChatChannel.Local)) { return(ColorUtility.ToHtmlStringRGBA(Instance.localColor)); } if (channel.HasFlag(ChatChannel.Combat)) { return(ColorUtility.ToHtmlStringRGBA(Instance.combatColor)); } if (channel.HasFlag(ChatChannel.Warning)) { return(ColorUtility.ToHtmlStringRGBA(Instance.warningColor)); } if (channel.HasFlag(ChatChannel.Blob)) { return(ColorUtility.ToHtmlStringRGBA(Instance.blobColor)); } return(ColorUtility.ToHtmlStringRGBA(Instance.defaultColor)); }
/// <summary> /// Send a Chat Msg from a player to the selected Chat Channels /// Server only /// </summary> public static void AddChatMsgToChat(ConnectedPlayer sentByPlayer, string message, ChatChannel channels) { message = AutoMod.ProcessChatServer(sentByPlayer, message); if (string.IsNullOrWhiteSpace(message)) { return; } var player = sentByPlayer.Script; // The exact words that leave the player's mouth (or that are narrated). Already includes HONKs, stutters, etc. // This step is skipped when speaking in the OOC channel. (string message, ChatModifier chatModifiers)processedMessage = (string.Empty, ChatModifier.None); // Placeholder values bool isOOC = channels.HasFlag(ChatChannel.OOC); if (!isOOC) { processedMessage = ProcessMessage(sentByPlayer, message); if (player.mind.occupation.JobType == JobType.MIME && player.mind.IsMiming && !processedMessage.chatModifiers.HasFlag(ChatModifier.Emote)) { AddWarningMsgFromServer(sentByPlayer.GameObject, "You can't talk because you made a vow of silence."); return; } } var chatEvent = new ChatEvent { message = isOOC ? message : processedMessage.message, modifiers = (player == null) ? ChatModifier.None : processedMessage.chatModifiers, speaker = (player == null) ? sentByPlayer.Username : player.name, position = ((player == null) ? TransformState.HiddenPos : player.WorldPos), channels = channels, originator = sentByPlayer.GameObject }; if (channels.HasFlag(ChatChannel.OOC)) { chatEvent.speaker = sentByPlayer.Username; if (PlayerList.Instance.IsAdmin(sentByPlayer.UserId)) { chatEvent.speaker = "[Admin] " + chatEvent.speaker; } Instance.addChatLogServer.Invoke(chatEvent); //Sends OOC message to a discord webhook DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookOOCURL, message, chatEvent.speaker, ServerData.ServerConfig.DiscordWebhookOOCMentionsID); //Send it to All chat DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookAllChatURL, $"[{ChatChannel.OOC}] {message}\n", chatEvent.speaker); return; } // TODO the following code uses player.playerHealth, but ConciousState would be more appropriate. // Check if the player is allowed to talk: if (player != null && player.playerHealth != null) { if (player.playerHealth.IsCrit || player.playerHealth.IsCardiacArrest) { if (!player.playerHealth.IsDead) { return; } else { channels = ChatChannel.Ghost; } } else if (!player.playerHealth.IsDead && !player.IsGhost) { //Control the chat bubble player.playerNetworkActions.CmdToggleChatIcon(true, processedMessage.message, channels, processedMessage.chatModifiers); } } string discordMessage = ""; // There could be multiple channels we need to send a message for each. // We do this on the server side that local chans can be determined correctly foreach (Enum value in Enum.GetValues(channels.GetType())) { if (channels.HasFlag((ChatChannel)value)) { //Using HasFlag will always return true for flag at value 0 so skip it if ((ChatChannel)value == ChatChannel.None) { continue; } if (IsNamelessChan((ChatChannel)value)) { continue; } chatEvent.channels = (ChatChannel)value; Instance.addChatLogServer.Invoke(chatEvent); discordMessage += $"[{chatEvent.channels}] {message}\n"; } } //Sends All Chat messages to a discord webhook DiscordWebhookMessage.Instance.AddWebHookMessageToQueue(DiscordWebhookURLs.DiscordWebhookAllChatURL, discordMessage, chatEvent.speaker); }