mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
* move-accents-to-shared * Update AccentSystem.cs * Update ListeningSystem.cs * aaaaaa * Update AccentSystem.cs * oh god * aaooao * // * review * fix * Update VocalSystem.cs
259 lines
8.8 KiB
C#
259 lines
8.8 KiB
C#
using System.Linq;
|
|
using System.Text;
|
|
using Content.Shared.Chat;
|
|
using Content.Shared.Ghost;
|
|
using Content.Shared.Players;
|
|
using Content.Shared.Speech.Prototypes;
|
|
using Robust.Shared.Console;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server.Chat.Systems;
|
|
|
|
public sealed partial class ChatSystem
|
|
{
|
|
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(session)) ? MessageRangeCheckResult.HideChat : MessageRangeCheckResult.Full;
|
|
break;
|
|
case ChatTransmitRange.HideChat:
|
|
initialResult = MessageRangeCheckResult.HideChat;
|
|
break;
|
|
case ChatTransmitRange.NoGhosts:
|
|
initialResult = (data.Observer && !_adminManager.IsAdmin(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, ChatTransmitRange range, NetUserId? author = null)
|
|
{
|
|
foreach (var (session, data) in GetRecipients(source, VoiceRange))
|
|
{
|
|
var entRange = MessageRangeCheck(session, data, range);
|
|
if (entRange == MessageRangeCheckResult.Disallowed)
|
|
continue;
|
|
var entHideChat = entRange == MessageRangeCheckResult.HideChat;
|
|
_chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author);
|
|
}
|
|
|
|
_replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given player is 'allowed' to send the given message, false otherwise.
|
|
/// </summary>
|
|
private bool CanSendInGame(string message, IConsoleShell? shell = null, ICommonSession? player = null)
|
|
{
|
|
// Non-players don't have to worry about these restrictions.
|
|
if (player == null)
|
|
return true;
|
|
|
|
var mindContainerComponent = player.ContentData()?.Mind;
|
|
|
|
if (mindContainerComponent == 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, bool capitalize = true, bool punctuate = false, bool capitalizeTheWordI = true)
|
|
{
|
|
var newMessage = SanitizeMessageReplaceWords(message.Trim());
|
|
|
|
GetRadioKeycodePrefix(source, newMessage, out newMessage, out var prefix);
|
|
|
|
// Sanitize it first as it might change the word order
|
|
_sanitizer.TrySanitizeEmoteShorthands(newMessage, source, out newMessage, out emoteStr);
|
|
|
|
if (capitalize)
|
|
newMessage = SanitizeMessageCapital(newMessage);
|
|
if (capitalizeTheWordI)
|
|
newMessage = SanitizeMessageCapitalizeTheWordI(newMessage, "i");
|
|
if (punctuate)
|
|
newMessage = SanitizeMessagePeriod(newMessage);
|
|
|
|
return prefix + newMessage;
|
|
}
|
|
|
|
private string SanitizeInGameOOCMessage(string message)
|
|
{
|
|
var newMessage = message.Trim();
|
|
newMessage = FormattedMessage.EscapeText(newMessage);
|
|
|
|
return newMessage;
|
|
}
|
|
|
|
public string TransformSpeech(EntityUid sender, string message)
|
|
{
|
|
var ev = new TransformSpeechEvent(sender, message);
|
|
RaiseLocalEvent(sender, ev, true);
|
|
|
|
return ev.Message;
|
|
}
|
|
|
|
public bool CheckIgnoreSpeechBlocker(EntityUid sender, bool ignoreBlocker)
|
|
{
|
|
if (ignoreBlocker)
|
|
return ignoreBlocker;
|
|
|
|
var ev = new CheckIgnoreSpeechBlockerEvent(sender, ignoreBlocker);
|
|
RaiseLocalEvent(sender, ev, true);
|
|
|
|
return ev.IgnoreBlocker;
|
|
}
|
|
|
|
private IEnumerable<INetChannel> GetDeadChatClients()
|
|
{
|
|
return Filter.Empty()
|
|
.AddWhereAttachedEntity(HasComp<GhostComponent>)
|
|
.Recipients
|
|
.Union(_adminManager.ActiveAdmins)
|
|
.Select(p => p.Channel);
|
|
}
|
|
|
|
private string SanitizeMessagePeriod(string message)
|
|
{
|
|
if (string.IsNullOrEmpty(message))
|
|
return message;
|
|
// Adds a period if the last character is a letter.
|
|
if (char.IsLetter(message[^1]))
|
|
message += ".";
|
|
return message;
|
|
}
|
|
|
|
public static readonly ProtoId<ReplacementAccentPrototype> ChatSanitize_Accent = "chatsanitize";
|
|
|
|
public string SanitizeMessageReplaceWords(string message)
|
|
{
|
|
if (string.IsNullOrEmpty(message)) return message;
|
|
|
|
var msg = message;
|
|
|
|
msg = _wordreplacement.ApplyReplacements(msg, ChatSanitize_Accent);
|
|
|
|
return msg;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns list of players and ranges for all players withing some range. Also returns observers with a range of -1.
|
|
/// </summary>
|
|
private Dictionary<ICommonSession, ICChatRecipientData> GetRecipients(EntityUid source, float voiceGetRange)
|
|
{
|
|
// TODO proper speech occlusion
|
|
|
|
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
|
|
|
|
var transformSource = Transform(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 = Transform(playerEntity);
|
|
|
|
if (transformEntity.MapID != sourceMapId)
|
|
continue;
|
|
|
|
var observer = _ghostHearingQuery.HasComponent(playerEntity);
|
|
|
|
// even if they are a ghost hearer, in some situations we still need the range
|
|
if (sourceCoords.TryDistance(EntityManager, transformEntity.Coordinates, out var distance) && distance < voiceGetRange)
|
|
{
|
|
recipients.Add(player, new ICChatRecipientData(distance, observer));
|
|
continue;
|
|
}
|
|
|
|
if (observer)
|
|
recipients.Add(player, new ICChatRecipientData(-1, true));
|
|
}
|
|
|
|
RaiseLocalEvent(new ExpandICChatRecipientsEvent(source, voiceGetRange, recipients));
|
|
return recipients;
|
|
}
|
|
|
|
public readonly record struct ICChatRecipientData(float Range, bool Observer, bool? HideChatOverride = null)
|
|
{
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
public string BuildGibberishString(IReadOnlyList<char> charOptions, int length)
|
|
{
|
|
var sb = new StringBuilder();
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
sb.Append(_random.Pick(charOptions));
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|