/// </summary>
/// <param name="source">The entity that is speaking</param>
/// <param name="emoteId">The id of emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
- /// <param name="hideChat">Whether or not this message should appear in the chat window</param>
- /// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
+ /// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</param>
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
- public void TryEmoteWithChat(EntityUid source, string emoteId, bool hideChat = false,
- bool hideGlobalGhostChat = false, string? nameOverride = null)
+ public void TryEmoteWithChat(EntityUid source, string emoteId, ChatTransmitRange range = ChatTransmitRange.Normal, string? nameOverride = null)
{
if (!_prototypeManager.TryIndex<EmotePrototype>(emoteId, out var proto))
return;
- TryEmoteWithChat(source, proto, hideChat, hideGlobalGhostChat, nameOverride);
+ TryEmoteWithChat(source, proto, range, nameOverride);
}
/// <summary>
/// </summary>
/// <param name="source">The entity that is speaking</param>
/// <param name="emote">The emote prototype. Should has valid <see cref="EmotePrototype.ChatMessages"/></param>
- /// <param name="hideChat">Whether or not this message should appear in the chat window</param>
- /// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
+ /// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</param>
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
- public void TryEmoteWithChat(EntityUid source, EmotePrototype emote, bool hideChat = false,
- bool hideGlobalGhostChat = false, string? nameOverride = null)
+ public void TryEmoteWithChat(EntityUid source, EmotePrototype emote, ChatTransmitRange range = ChatTransmitRange.Normal, string? nameOverride = null)
{
// check if proto has valid message for chat
if (emote.ChatMessages.Count != 0)
{
var action = _random.Pick(emote.ChatMessages);
- SendEntityEmote(source, action, hideChat, hideGlobalGhostChat, nameOverride, false);
+ SendEntityEmote(source, action, range, nameOverride, false);
}
// do the rest of emote event logic here
/// <param name="message">The message being spoken or emoted</param>
/// <param name="desiredType">The chat type</param>
/// <param name="hideChat">Whether or not this message should appear in the chat window</param>
- /// <param name="hideGlobalGhostChat">Whether or not this message should appear in the chat window for out-of-range ghosts (which otherwise ignore range restrictions)</param>
/// <param name="shell"></param>
/// <param name="player">The player doing the speaking</param>
/// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
- public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, bool hideChat, bool hideGlobalGhostChat = false,
+ public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, bool hideChat,
+ IConsoleShell? shell = null, IPlayerSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true)
+ {
+ TrySendInGameICMessage(source, message, desiredType, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, shell, player, nameOverride, checkRadioPrefix);
+ }
+
+ /// <summary>
+ /// Sends an in-character chat message to relevant clients.
+ /// </summary>
+ /// <param name="source">The entity that is speaking</param>
+ /// <param name="message">The message being spoken or emoted</param>
+ /// <param name="desiredType">The chat type</param>
+ /// <param name="range">Conceptual range of transmission, if it shows in the chat window, if it shows to far-away ghosts or ghosts at all...</param>
+ /// <param name="shell"></param>
+ /// <param name="player">The player doing the speaking</param>
+ /// <param name="nameOverride">The name to use for the speaking entity. Usually this should just be modified via <see cref="TransformSpeakerNameEvent"/>. If this is set, the event will not get raised.</param>
+ public void TrySendInGameICMessage(EntityUid source, string message, InGameICChatType desiredType, ChatTransmitRange range,
IConsoleShell? shell = null, IPlayerSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true)
{
if (HasComp<GhostComponent>(source))
{
// Ghosts can only send dead chat messages, so we'll forward it to InGame OOC.
- TrySendInGameOOCMessage(source, message, InGameOOCChatType.Dead, hideChat, shell, player);
+ TrySendInGameOOCMessage(source, message, InGameOOCChatType.Dead, range == ChatTransmitRange.HideChat, shell, player);
return;
}
message = message[1..];
}
- hideGlobalGhostChat |= hideChat;
bool shouldCapitalize = (desiredType != InGameICChatType.Emote);
bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation);
// Was there an emote in the message? If so, send it.
if (player != null && emoteStr != message && emoteStr != null)
{
- SendEntityEmote(source, emoteStr, hideChat, hideGlobalGhostChat, nameOverride);
+ SendEntityEmote(source, emoteStr, range, nameOverride);
}
// This can happen if the entire string is sanitized out.
{
if (TryProccessRadioMessage(source, message, out var modMessage, out var channel))
{
- SendEntityWhisper(source, modMessage, hideChat, hideGlobalGhostChat, channel, nameOverride);
+ SendEntityWhisper(source, modMessage, range, channel, nameOverride);
return;
}
}
switch (desiredType)
{
case InGameICChatType.Speak:
- SendEntitySpeak(source, message, hideChat, hideGlobalGhostChat, nameOverride);
+ SendEntitySpeak(source, message, range, nameOverride);
break;
case InGameICChatType.Whisper:
- SendEntityWhisper(source, message, hideChat, hideGlobalGhostChat, null, nameOverride);
+ SendEntityWhisper(source, message, range, null, nameOverride);
break;
case InGameICChatType.Emote:
- SendEntityEmote(source, message, hideChat, hideGlobalGhostChat, nameOverride);
+ SendEntityEmote(source, message, range, nameOverride);
break;
}
}
#region Private API
- private void SendEntitySpeak(EntityUid source, string originalMessage, bool hideChat, bool hideGlobalGhostChat, string? nameOverride)
+ private void SendEntitySpeak(EntityUid source, string originalMessage, ChatTransmitRange range, string? nameOverride)
{
if (!_actionBlocker.CanSpeak(source))
return;
var wrappedMessage = Loc.GetString("chat-manager-entity-say-wrap-message",
("entityName", name), ("message", FormattedMessage.EscapeText(message)));
- SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, hideChat, hideGlobalGhostChat);
+ SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range);
var ev = new EntitySpokeEvent(source, message, null, null);
RaiseLocalEvent(source, ev, true);
}
}
- private void SendEntityWhisper(EntityUid source, string originalMessage, bool hideChat, bool hideGlobalGhostChat, RadioChannelPrototype? channel, string? nameOverride)
+ private void SendEntityWhisper(EntityUid source, string originalMessage, ChatTransmitRange range, RadioChannelPrototype? channel, string? nameOverride)
{
if (!_actionBlocker.CanSpeak(source))
return;
if (session.AttachedEntity is not { Valid: true } playerEntity)
continue;
- if (hideGlobalGhostChat && data.Observer && data.Range < 0)
+ if (MessageRangeCheck(session, data, range) != MessageRangeCheckResult.Full)
continue; // Won't get logged to chat, and ghosts are too far away to see the pop-up, so we just won't send it to them.
if (data.Range <= WhisperRange)
- _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, data.HideChatOverride ?? hideChat, session.ConnectedClient);
+ _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.ConnectedClient);
else
- _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, data.HideChatOverride ?? hideChat, session.ConnectedClient);
+ _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.ConnectedClient);
}
- _replay.QueueReplayMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, source, hideChat));
+ _replay.QueueReplayMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, source, MessageRangeHideChatForReplay(range)));
var ev = new EntitySpokeEvent(source, message, channel, obfuscatedMessage);
RaiseLocalEvent(source, ev, true);
}
}
- private void SendEntityEmote(EntityUid source, string action, bool hideChat,
- bool hideGlobalGhostChat, string? nameOverride, bool checkEmote = true)
+ private void SendEntityEmote(EntityUid source, string action, ChatTransmitRange range, string? nameOverride, bool checkEmote = true)
{
if (!_actionBlocker.CanEmote(source)) return;
if (checkEmote)
TryEmoteChatInput(source, action);
- SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, hideChat, hideGlobalGhostChat);
+ SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range);
if (name != Name(source))
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}");
("entityName", name),
("message", FormattedMessage.EscapeText(message)));
- SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat, false);
+ SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}");
}
#region Utility
+ private enum MessageRangeCheckResult {
+ Disallowed,
+ HideChat,
+ Full
+ }
+
+ /// <summary>
+ /// If hideChat should be set as far as replays are concerned.
+ /// </summary>
+ private bool MessageRangeHideChatForReplay(ChatTransmitRange range)
+ {
+ return range == ChatTransmitRange.HideChat;
+ }
+
+ /// <summary>
+ /// Checks if a target as returned from GetRecipients should receive the message.
+ /// Keep in mind data.Range is -1 for out of range observers.
+ /// </summary>
+ private MessageRangeCheckResult MessageRangeCheck(ICommonSession session, ICChatRecipientData data, ChatTransmitRange range)
+ {
+ var initialResult = MessageRangeCheckResult.Full;
+ switch (range)
+ {
+ case ChatTransmitRange.Normal:
+ initialResult = MessageRangeCheckResult.Full;
+ break;
+ case ChatTransmitRange.GhostRangeLimit:
+ initialResult = (data.Observer && data.Range < 0 && !_adminManager.IsAdmin((IPlayerSession) session)) ? MessageRangeCheckResult.HideChat : MessageRangeCheckResult.Full;
+ break;
+ case ChatTransmitRange.HideChat:
+ initialResult = MessageRangeCheckResult.HideChat;
+ break;
+ case ChatTransmitRange.NoGhosts:
+ initialResult = (data.Observer && !_adminManager.IsAdmin((IPlayerSession) session)) ? MessageRangeCheckResult.Disallowed : MessageRangeCheckResult.Full;
+ break;
+ }
+ var insistHideChat = data.HideChatOverride ?? false;
+ var insistNoHideChat = !(data.HideChatOverride ?? true);
+ if (insistHideChat && initialResult == MessageRangeCheckResult.Full)
+ return MessageRangeCheckResult.HideChat;
+ if (insistNoHideChat && initialResult == MessageRangeCheckResult.HideChat)
+ return MessageRangeCheckResult.Full;
+ return initialResult;
+ }
+
/// <summary>
/// Sends a chat message to the given players in range of the source entity.
/// </summary>
- private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool hideGlobalGhostChat)
+ private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range)
{
foreach (var (session, data) in GetRecipients(source, VoiceRange))
{
- var entHideChat = data.HideChatOverride ?? (hideChat || hideGlobalGhostChat && data.Observer && data.Range < 0);
+ var entRange = MessageRangeCheck(session, data, range);
+ if (entRange == MessageRangeCheckResult.Disallowed)
+ continue;
+ var entHideChat = entRange == MessageRangeCheckResult.HideChat;
_chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.ConnectedClient);
}
- _replay.QueueReplayMessage(new ChatMessage(channel, message, wrappedMessage, source, hideChat));
+ _replay.QueueReplayMessage(new ChatMessage(channel, message, wrappedMessage, source, MessageRangeHideChatForReplay(range)));
}
/// <summary>
Looc,
Dead
}
+
+/// <summary>
+/// Controls transmission of chat.
+/// </summary>
+public enum ChatTransmitRange : byte
+{
+ /// Acts normal, ghosts can hear across the map, etc.
+ Normal,
+ /// Normal but ghosts are still range-limited.
+ GhostRangeLimit,
+ /// Hidden from the chat window.
+ HideChat,
+ /// Ghosts can't hear or see it at all. Regular players can if in-range.
+ NoGhosts
+}
+