2022-05-12 05:05:16 -07:00
using System.Linq ;
2022-03-30 22:21:58 -07:00
using System.Text ;
using Content.Server.Administration.Logs ;
using Content.Server.Administration.Managers ;
using Content.Server.Chat.Managers ;
2022-06-20 16:59:04 -04:00
using Content.Server.GameTicking ;
2023-11-05 18:19:59 -08:00
using Content.Server.Speech.Components ;
using Content.Server.Speech.EntitySystems ;
2022-06-03 21:37:35 +10:00
using Content.Server.Station.Components ;
using Content.Server.Station.Systems ;
2023-04-27 06:01:05 +06:00
using Content.Server.UtkaIntegration ;
2022-03-30 22:21:58 -07:00
using Content.Shared.ActionBlocker ;
2023-04-28 04:44:00 +06:00
using Content.Shared.Administration ;
2022-03-30 22:21:58 -07:00
using Content.Shared.CCVar ;
using Content.Shared.Chat ;
using Content.Shared.Database ;
2023-08-25 18:50:46 +10:00
using Content.Shared.Ghost ;
2022-07-13 22:23:55 -07:00
using Content.Shared.IdentityManagement ;
2023-08-01 14:43:02 +00:00
using Content.Shared.Interaction ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs.Systems ;
2023-10-28 09:59:53 +11:00
using Content.Shared.Players ;
2022-11-23 00:52:19 +13:00
using Content.Shared.Radio ;
2023-04-25 02:17:40 +06:00
using Content.Shared.White ;
2022-03-30 22:21:58 -07:00
using Robust.Server.Player ;
2022-06-03 21:37:35 +10:00
using Robust.Shared.Audio ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.Audio.Systems ;
2022-03-30 22:21:58 -07:00
using Robust.Shared.Configuration ;
using Robust.Shared.Console ;
using Robust.Shared.Network ;
using Robust.Shared.Player ;
2022-06-23 20:11:03 +10:00
using Robust.Shared.Prototypes ;
2022-03-30 22:21:58 -07:00
using Robust.Shared.Random ;
2022-11-23 00:52:19 +13:00
using Robust.Shared.Replays ;
2022-03-30 22:21:58 -07:00
using Robust.Shared.Utility ;
2022-06-23 20:11:03 +10:00
namespace Content.Server.Chat.Systems ;
2022-03-30 22:21:58 -07:00
2023-10-14 02:02:56 -07:00
// TODO refactor whatever active warzone this class and chatmanager have become
2022-03-30 22:21:58 -07:00
/// <summary>
/// ChatSystem is responsible for in-simulation chat handling, such as whispering, speaking, emoting, etc.
/// ChatSystem depends on ChatManager to actually send the messages.
/// </summary>
2022-06-23 20:11:03 +10:00
public sealed partial class ChatSystem : SharedChatSystem
2022-03-30 22:21:58 -07:00
{
2022-11-23 00:52:19 +13:00
[Dependency] private readonly IReplayRecordingManager _replay = default ! ;
2022-03-30 22:21:58 -07:00
[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 ! ;
2022-06-23 20:11:03 +10:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
2022-03-30 22:21:58 -07:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-03-30 22:21:58 -07:00
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default ! ;
2022-06-03 21:37:35 +10:00
[Dependency] private readonly StationSystem _stationSystem = default ! ;
2022-07-26 09:58:19 -04:00
[Dependency] private readonly MobStateSystem _mobStateSystem = default ! ;
2023-01-25 17:29:41 +01:00
[Dependency] private readonly SharedAudioSystem _audio = default ! ;
2023-08-01 14:43:02 +00:00
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default ! ;
2023-10-21 15:43:11 -04:00
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default ! ;
2022-03-30 22:21:58 -07:00
2023-04-27 06:01:05 +06:00
//WD-EDIT
[Dependency] private readonly UtkaTCPWrapper _utkaSockets = default ! ;
//WD-EDIT
2022-11-15 17:09:27 +13:00
public const int VoiceRange = 10 ; // how far voice goes in world units
2023-08-01 14:43:02 +00:00
public const int WhisperClearRange = 2 ; // how far whisper goes while still being understandable, in world units
public const int WhisperMuffledRange = 5 ; // how far whisper goes at all, in world units
2022-11-15 17:09:27 +13:00
public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg" ;
2022-03-30 22:21:58 -07:00
private bool _loocEnabled = true ;
2023-07-26 02:02:48 -04:00
private bool _deadLoocEnabled ;
private bool _critLoocEnabled ;
2023-04-25 02:17:40 +06:00
private const bool AdminLoocEnabled = true ;
2022-03-30 22:21:58 -07:00
public override void Initialize ( )
{
2023-01-25 17:29:41 +01:00
base . Initialize ( ) ;
2023-12-22 09:13:45 -05:00
CacheEmotes ( ) ;
2022-03-30 22:21:58 -07:00
_configurationManager . OnValueChanged ( CCVars . LoocEnabled , OnLoocEnabledChanged , true ) ;
2022-07-26 09:58:19 -04:00
_configurationManager . OnValueChanged ( CCVars . DeadLoocEnabled , OnDeadLoocEnabledChanged , true ) ;
2023-07-26 02:02:48 -04:00
_configurationManager . OnValueChanged ( CCVars . CritLoocEnabled , OnCritLoocEnabledChanged , true ) ;
2022-06-20 16:59:04 -04:00
SubscribeLocalEvent < GameRunLevelChangedEvent > ( OnGameChange ) ;
2022-03-30 22:21:58 -07:00
}
public override void Shutdown ( )
{
2023-01-25 17:29:41 +01:00
base . Shutdown ( ) ;
2022-03-30 22:21:58 -07:00
_configurationManager . UnsubValueChanged ( CCVars . LoocEnabled , OnLoocEnabledChanged ) ;
2023-02-28 08:15:48 -08:00
_configurationManager . UnsubValueChanged ( CCVars . DeadLoocEnabled , OnDeadLoocEnabledChanged ) ;
2023-07-26 02:02:48 -04:00
_configurationManager . UnsubValueChanged ( CCVars . CritLoocEnabled , OnCritLoocEnabledChanged ) ;
2022-03-30 22:21:58 -07:00
}
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" ) ) ;
}
2022-07-26 09:58:19 -04:00
private void OnDeadLoocEnabledChanged ( bool val )
{
if ( _deadLoocEnabled = = val ) return ;
_deadLoocEnabled = val ;
_chatManager . DispatchServerAnnouncement (
Loc . GetString ( val ? "chat-manager-dead-looc-chat-enabled-message" : "chat-manager-dead-looc-chat-disabled-message" ) ) ;
}
2023-07-26 02:02:48 -04:00
private void OnCritLoocEnabledChanged ( bool val )
{
if ( _critLoocEnabled = = val )
return ;
_critLoocEnabled = val ;
_chatManager . DispatchServerAnnouncement (
Loc . GetString ( val ? "chat-manager-crit-looc-chat-enabled-message" : "chat-manager-crit-looc-chat-disabled-message" ) ) ;
}
2022-06-20 16:59:04 -04:00
private void OnGameChange ( GameRunLevelChangedEvent ev )
{
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
switch ( ev . New )
2023-02-28 08:15:48 -08:00
{
case GameRunLevel . InRound :
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
if ( ! _configurationManager . GetCVar ( CCVars . OocEnableDuringRound ) )
2023-02-28 08:15:48 -08:00
_configurationManager . SetCVar ( CCVars . OocEnabled , false ) ;
break ;
case GameRunLevel . PostRound :
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
if ( ! _configurationManager . GetCVar ( CCVars . OocEnableDuringRound ) )
2023-02-28 08:15:48 -08:00
_configurationManager . SetCVar ( CCVars . OocEnabled , true ) ;
break ;
}
2022-06-20 16:59:04 -04:00
}
2022-11-15 17:09:27 +13:00
/// <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="hideChat">Whether or not this message should appear in the chat window</param>
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
/// <param name="hideLog">Whether or not this message should appear in the adminlog window</param>
2022-11-15 17:09:27 +13:00
/// <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>
2023-08-11 22:56:34 -07:00
public void TrySendInGameICMessage (
EntityUid source ,
string message ,
InGameICChatType desiredType ,
bool hideChat , bool hideLog = false ,
IConsoleShell ? shell = null ,
2023-10-28 09:59:53 +11:00
ICommonSession ? player = null , string? nameOverride = null ,
2023-08-11 22:56:34 -07:00
bool checkRadioPrefix = true ,
bool ignoreActionBlocker = false )
2023-05-04 20:08:08 +01:00
{
2023-08-11 22:56:34 -07:00
TrySendInGameICMessage ( source , message , desiredType , hideChat ? ChatTransmitRange . HideChat : ChatTransmitRange . Normal , hideLog , shell , player , nameOverride , checkRadioPrefix , ignoreActionBlocker ) ;
2023-05-04 20:08:08 +01:00
}
/// <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>
2023-08-11 22:56:34 -07:00
/// <param name="ignoreActionBlocker">If set to true, action blocker will not be considered for whether an entity can send this message.</param>
public void TrySendInGameICMessage (
EntityUid source ,
string message ,
InGameICChatType desiredType ,
ChatTransmitRange range ,
bool hideLog = false ,
IConsoleShell ? shell = null ,
2023-10-28 09:59:53 +11:00
ICommonSession ? player = null ,
2023-08-11 22:56:34 -07:00
string? nameOverride = null ,
bool checkRadioPrefix = true ,
2023-05-01 15:05:43 +06:00
bool ignoreActionBlocker = false ,
bool force = false
2023-08-11 22:56:34 -07:00
)
2022-03-30 22:21:58 -07:00
{
if ( HasComp < GhostComponent > ( source ) )
{
2023-04-28 05:08:05 +06:00
if ( desiredType = = InGameICChatType . Emote )
return ;
2022-03-30 22:21:58 -07:00
// Ghosts can only send dead chat messages, so we'll forward it to InGame OOC.
2023-05-04 20:08:08 +01:00
TrySendInGameOOCMessage ( source , message , InGameOOCChatType . Dead , range = = ChatTransmitRange . HideChat , shell , player ) ;
2022-03-30 22:21:58 -07:00
return ;
}
2023-11-27 04:08:30 +01:00
if ( player ! = null & & ! _chatManager . HandleRateLimit ( player ) )
return ;
2022-04-01 00:08:26 -07:00
// Sus
if ( player ? . AttachedEntity is { Valid : true } entity & & source ! = entity )
{
return ;
}
2023-05-01 15:05:43 +06:00
if ( ! force & & ! CanSendInGame ( message , shell , player ) )
2022-03-30 22:21:58 -07:00
return ;
2023-12-01 19:25:20 -05:00
ignoreActionBlocker = CheckIgnoreSpeechBlocker ( source , ignoreActionBlocker ) ;
2023-11-05 18:19:59 -08:00
// this method is a disaster
// every second i have to spend working with this code is fucking agony
// scientists have to wonder how any of this was merged
// coding any game admin feature that involves chat code is pure torture
// changing even 10 lines of code feels like waterboarding myself
// and i dont feel like vibe checking 50 code paths
// so we set this here
// todo free me from chat code
2023-10-14 02:02:56 -07:00
if ( player ! = null )
2023-11-05 18:19:59 -08:00
{
_chatManager . EnsurePlayer ( player . UserId ) . AddEntity ( GetNetEntity ( source ) ) ;
}
2023-10-14 02:02:56 -07:00
2023-02-19 06:27:56 +13:00
if ( desiredType = = InGameICChatType . Speak & & message . StartsWith ( LocalPrefix ) )
{
// prevent radios and remove prefix.
checkRadioPrefix = false ;
message = message [ 1. . ] ;
}
2023-04-25 02:17:40 +06:00
var shouldCapitalize = ( desiredType ! = InGameICChatType . Emote ) ;
var shouldPunctuate = _configurationManager . GetCVar ( CCVars . ChatPunctuation ) ;
var sanitizeSlang = _configurationManager . GetCVar ( WhiteCVars . ChatSlangFilter ) ;
2022-04-24 20:15:40 -04:00
2023-04-25 02:17:40 +06:00
message = SanitizeInGameICMessage ( source , message , out var emoteStr , shouldCapitalize , shouldPunctuate , sanitizeSlang ) ;
2022-03-30 22:21:58 -07:00
2022-04-01 00:08:26 -07:00
// Was there an emote in the message? If so, send it.
2023-05-01 15:05:43 +06:00
if ( emoteStr ! = message & & emoteStr ! = null )
2022-03-30 22:21:58 -07:00
{
2023-05-01 15:05:43 +06:00
SendEntityEmote ( source , emoteStr , range , nameOverride , ignoreActionBlocker , force : force ) ;
2022-03-30 22:21:58 -07:00
}
2022-04-01 20:09:54 -07:00
// This can happen if the entire string is sanitized out.
if ( string . IsNullOrEmpty ( message ) )
return ;
2022-12-14 12:24:49 +11:00
// This message may have a radio prefix, and should then be whispered to the resolved radio channel
if ( checkRadioPrefix )
{
2023-02-19 06:27:56 +13:00
if ( TryProccessRadioMessage ( source , message , out var modMessage , out var channel ) )
2022-12-14 12:24:49 +11:00
{
2023-12-01 19:25:20 -05:00
SendEntityWhisper ( source , modMessage , range , channel , nameOverride , hideLog , ignoreActionBlocker ) ;
2022-12-14 12:24:49 +11:00
return ;
}
}
2022-03-30 22:21:58 -07:00
// Otherwise, send whatever type.
switch ( desiredType )
{
case InGameICChatType . Speak :
2023-08-11 22:56:34 -07:00
SendEntitySpeak ( source , message , range , nameOverride , hideLog , ignoreActionBlocker ) ;
2022-03-30 22:21:58 -07:00
break ;
case InGameICChatType . Whisper :
2023-08-11 22:56:34 -07:00
SendEntityWhisper ( source , message , range , null , nameOverride , hideLog , ignoreActionBlocker ) ;
2022-03-30 22:21:58 -07:00
break ;
case InGameICChatType . Emote :
2023-05-01 15:05:43 +06:00
SendEntityEmote ( source , message , range , nameOverride , hideLog : hideLog , ignoreActionBlocker : ignoreActionBlocker , force : force ) ;
2022-03-30 22:21:58 -07:00
break ;
}
}
2023-08-11 22:56:34 -07:00
public void TrySendInGameOOCMessage (
EntityUid source ,
string message ,
InGameOOCChatType type ,
bool hideChat ,
IConsoleShell ? shell = null ,
2023-10-28 09:59:53 +11:00
ICommonSession ? player = null
2023-08-11 22:56:34 -07:00
)
2022-03-30 22:21:58 -07:00
{
if ( ! CanSendInGame ( message , shell , player ) )
return ;
2023-11-27 04:08:30 +01:00
if ( player ! = null & & ! _chatManager . HandleRateLimit ( player ) )
return ;
2022-03-30 22:21:58 -07:00
// 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 ) ;
2022-07-26 09:58:19 -04:00
var sendType = type ;
// If dead player LOOC is disabled, unless you are an aghost, send dead messages to dead chat
if ( ! _adminManager . IsAdmin ( player ) & & ! _deadLoocEnabled & &
( HasComp < GhostComponent > ( source ) | | _mobStateSystem . IsDead ( source ) ) )
sendType = InGameOOCChatType . Dead ;
2023-07-26 02:02:48 -04:00
// If crit player LOOC is disabled, don't send the message at all.
if ( ! _critLoocEnabled & & _mobStateSystem . IsCritical ( source ) )
return ;
2022-07-26 09:58:19 -04:00
switch ( sendType )
2022-03-30 22:21:58 -07:00
{
case InGameOOCChatType . Dead :
SendDeadChat ( source , player , message , hideChat ) ;
break ;
case InGameOOCChatType . Looc :
SendLOOC ( source , player , message , hideChat ) ;
break ;
}
}
2023-04-28 10:10:25 +06:00
public string AfterSpeechTransformed ( EntityUid sender , string message )
{
var ev = new SpeechTransformedEvent ( sender , message ) ;
RaiseLocalEvent ( ev ) ;
return ev . Message ;
}
2022-06-03 21:37:35 +10:00
#region Announcements
/// <summary>
2022-07-04 16:00:51 +10:00
/// Dispatches an announcement to all.
2022-06-03 21:37:35 +10:00
/// </summary>
/// <param name="message">The contents of the message</param>
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
2022-07-15 12:16:41 +03:00
/// <param name="playSound">Play the announcement sound</param>
2022-06-03 21:37:35 +10:00
/// <param name="colorOverride">Optional color for the announcement message</param>
2023-08-11 22:56:34 -07:00
public void DispatchGlobalAnnouncement (
string message ,
string sender = "Central Command" ,
bool playSound = true ,
SoundSpecifier ? announcementSound = null ,
Color ? colorOverride = null
)
2022-06-03 21:37:35 +10:00
{
2022-10-18 19:59:09 +13:00
var wrappedMessage = Loc . GetString ( "chat-manager-sender-announcement-wrap-message" , ( "sender" , sender ) , ( "message" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-11-23 00:52:19 +13:00
_chatManager . ChatMessageToAll ( ChatChannel . Radio , message , wrappedMessage , default , false , true , colorOverride ) ;
2022-07-15 12:16:41 +03:00
if ( playSound )
2022-06-03 21:37:35 +10:00
{
2023-11-27 22:12:34 +11:00
_audio . PlayGlobal ( announcementSound ? . GetSound ( ) ? ? DefaultAnnouncementSound , Filter . Broadcast ( ) , true , AudioParams . Default . WithVolume ( - 2f ) ) ;
2022-06-03 21:37:35 +10:00
}
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Global station announcement from {sender}: {message}" ) ;
}
/// <summary>
/// Dispatches an announcement on a specific station
/// </summary>
/// <param name="source">The entity making the announcement (used to determine the station)</param>
/// <param name="message">The contents of the message</param>
/// <param name="sender">The sender (Communications Console in Communications Console Announcement)</param>
/// <param name="playDefaultSound">Play the announcement sound</param>
/// <param name="colorOverride">Optional color for the announcement message</param>
2023-08-11 22:56:34 -07:00
public void DispatchStationAnnouncement (
EntityUid source ,
string message ,
string sender = "Central Command" ,
bool playDefaultSound = true ,
SoundSpecifier ? announcementSound = null ,
Color ? colorOverride = null )
2022-06-03 21:37:35 +10:00
{
2022-10-18 19:59:09 +13:00
var wrappedMessage = Loc . GetString ( "chat-manager-sender-announcement-wrap-message" , ( "sender" , sender ) , ( "message" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-06-03 21:37:35 +10:00
var station = _stationSystem . GetOwningStation ( source ) ;
2022-06-04 19:09:04 +10:00
if ( station = = null )
2022-06-03 21:37:35 +10:00
{
2022-06-04 19:09:04 +10:00
// you can't make a station announcement without a station
return ;
2022-06-03 21:37:35 +10:00
}
2022-06-04 19:09:04 +10:00
if ( ! EntityManager . TryGetComponent < StationDataComponent > ( station , out var stationDataComp ) ) return ;
2022-07-04 16:00:51 +10:00
var filter = _stationSystem . GetInStation ( stationDataComp ) ;
2022-06-03 21:37:35 +10:00
2022-11-23 00:52:19 +13:00
_chatManager . ChatMessageToManyFiltered ( filter , ChatChannel . Radio , message , wrappedMessage , source , false , true , colorOverride ) ;
2022-06-03 21:37:35 +10:00
if ( playDefaultSound )
{
2023-11-27 22:12:34 +11:00
_audio . PlayGlobal ( announcementSound ? . GetSound ( ) ? ? DefaultAnnouncementSound , filter , true , AudioParams . Default . WithVolume ( - 2f ) ) ;
2022-06-03 21:37:35 +10:00
}
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Station Announcement on {station} from {sender}: {message}" ) ;
}
#endregion
2022-03-30 22:21:58 -07:00
#region Private API
2023-08-11 22:56:34 -07:00
private void SendEntitySpeak (
EntityUid source ,
string originalMessage ,
ChatTransmitRange range ,
string? nameOverride ,
bool hideLog = false ,
bool ignoreActionBlocker = false
)
2022-03-30 22:21:58 -07:00
{
2023-08-11 22:56:34 -07:00
if ( ! _actionBlocker . CanSpeak ( source ) & & ! ignoreActionBlocker )
2022-07-26 16:49:23 -07:00
return ;
2022-03-30 22:21:58 -07:00
2023-12-30 00:38:11 +00:00
var message = TransformSpeech ( source , FormattedMessage . RemoveMarkup ( originalMessage ) ) ;
2022-08-02 15:46:10 +02:00
if ( message . Length = = 0 )
return ;
2023-04-28 10:10:25 +06:00
message = AfterSpeechTransformed ( source , message ) ;
2022-11-15 17:09:27 +13:00
// get the entity's apparent name (if no override provided).
string name ;
if ( nameOverride ! = null )
{
name = nameOverride ;
}
else
{
var nameEv = new TransformSpeakerNameEvent ( source , Name ( source ) ) ;
RaiseLocalEvent ( source , nameEv ) ;
name = nameEv . Name ;
}
name = FormattedMessage . EscapeText ( name ) ;
2023-04-26 01:21:26 +06:00
//WD-EDIT
var colorEv = new SetSpeakerColorEvent ( source , name ) ;
RaiseLocalEvent ( source , colorEv ) ;
name = colorEv . Name ;
//WD-EDIT
var wrappedMessage = Loc . GetString ( "chat-manager-entity-say-wrap-message" ,
2023-04-28 10:10:25 +06:00
( "entityName" , name ) , ( "message" , message ) ) ;
2022-03-30 22:21:58 -07:00
2023-05-04 20:08:08 +01:00
SendInVoiceRange ( ChatChannel . Local , message , wrappedMessage , source , range ) ;
2022-03-30 22:21:58 -07:00
2023-04-27 08:03:44 +06:00
var ev = new EntitySpokeEvent ( source , message , originalMessage , null , null ) ;
2022-11-15 17:09:27 +13:00
RaiseLocalEvent ( source , ev , true ) ;
2022-06-27 15:56:38 +12:00
2022-08-07 20:21:56 -03:00
// To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc.
2023-12-30 00:38:11 +00:00
// Also doesn't log if hideLog is true.
2023-04-25 02:17:40 +06:00
if ( ! HasComp < ActorComponent > ( source ) | | hideLog )
2022-08-07 20:21:56 -03:00
return ;
2022-06-27 15:56:38 +12:00
if ( originalMessage = = message )
2023-02-11 13:26:44 -06:00
{
if ( name ! = Name ( source ) )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Say from {ToPrettyString(source):user} as {name}: {originalMessage}." ) ;
else
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Say from {ToPrettyString(source):user}: {originalMessage}." ) ;
}
2022-06-27 15:56:38 +12:00
else
2023-02-11 13:26:44 -06:00
{
if ( name ! = Name ( source ) )
2023-04-25 02:17:40 +06:00
{
2023-02-11 13:26:44 -06:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low ,
$"Say from {ToPrettyString(source):user} as {name}, original: {originalMessage}, transformed: {message}." ) ;
2023-04-25 02:17:40 +06:00
}
2023-02-11 13:26:44 -06:00
else
2023-04-25 02:17:40 +06:00
{
2023-02-11 13:26:44 -06:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low ,
$"Say from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}." ) ;
2023-04-25 02:17:40 +06:00
}
2023-02-11 13:26:44 -06:00
}
2022-03-30 22:21:58 -07:00
}
2023-08-11 22:56:34 -07:00
private void SendEntityWhisper (
EntityUid source ,
string originalMessage ,
ChatTransmitRange range ,
RadioChannelPrototype ? channel ,
string? nameOverride ,
bool hideLog = false ,
bool ignoreActionBlocker = false
)
2022-03-30 22:21:58 -07:00
{
2023-08-11 22:56:34 -07:00
if ( ! _actionBlocker . CanSpeak ( source ) & & ! ignoreActionBlocker )
2022-07-26 16:49:23 -07:00
return ;
2022-03-30 22:21:58 -07:00
2023-12-30 00:38:11 +00:00
var message = TransformSpeech ( source , FormattedMessage . RemoveMarkup ( originalMessage ) ) ;
2022-06-29 15:13:01 +12:00
if ( message . Length = = 0 )
return ;
2023-04-28 10:10:25 +06:00
message = AfterSpeechTransformed ( source , message ) ;
2022-03-30 22:21:58 -07:00
var obfuscatedMessage = ObfuscateMessageReadability ( message , 0.2f ) ;
2023-08-01 14:43:02 +00:00
// get the entity's name by visual identity (if no override provided).
2023-04-25 02:17:40 +06:00
var nameIdentity = FormattedMessage . EscapeText ( nameOverride ? ? Identity . Name ( source , EntityManager ) ) ;
2023-08-01 14:43:02 +00:00
// get the entity's name by voice (if no override provided).
2022-11-15 17:09:27 +13:00
string name ;
if ( nameOverride ! = null )
{
name = nameOverride ;
}
else
{
var nameEv = new TransformSpeakerNameEvent ( source , Name ( source ) ) ;
RaiseLocalEvent ( source , nameEv ) ;
name = nameEv . Name ;
}
name = FormattedMessage . EscapeText ( name ) ;
2022-03-30 22:21:58 -07:00
2022-11-23 00:52:19 +13:00
var wrappedMessage = Loc . GetString ( "chat-manager-entity-whisper-wrap-message" ,
2023-04-28 10:10:25 +06:00
( "entityName" , name ) , ( "message" , message ) ) ;
2022-11-23 00:52:19 +13:00
var wrappedobfuscatedMessage = Loc . GetString ( "chat-manager-entity-whisper-wrap-message" ,
2023-04-28 10:10:25 +06:00
( "entityName" , nameIdentity ) , ( "message" , obfuscatedMessage ) ) ;
2022-11-23 00:52:19 +13:00
2023-08-01 14:43:02 +00:00
var wrappedUnknownMessage = Loc . GetString ( "chat-manager-entity-whisper-unknown-wrap-message" ,
2023-04-28 10:10:25 +06:00
( "message" , obfuscatedMessage ) ) ;
2022-11-23 00:52:19 +13:00
2023-08-01 14:43:02 +00:00
foreach ( var ( session , data ) in GetRecipients ( source , WhisperMuffledRange ) )
2022-03-30 22:21:58 -07:00
{
2023-04-25 02:17:40 +06:00
if ( session . AttachedEntity is not { Valid : true } )
2022-03-30 22:21:58 -07:00
continue ;
2023-04-25 02:17:40 +06:00
var listener = session . AttachedEntity . Value ;
2022-03-30 22:21:58 -07:00
2023-05-04 20:08:08 +01:00
if ( MessageRangeCheck ( session , data , range ) ! = MessageRangeCheckResult . Full )
2022-11-15 17:09:27 +13:00
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.
2022-03-30 22:21:58 -07:00
2023-08-01 14:43:02 +00:00
if ( data . Range < = WhisperClearRange )
2023-04-25 02:17:40 +06:00
_chatManager . ChatMessageToOne ( ChatChannel . Whisper , message , wrappedMessage , source , false , session . Channel ) ;
2023-08-01 14:43:02 +00:00
//If listener is too far, they only hear fragments of the message
//Collisiongroup.Opaque is not ideal for this use. Preferably, there should be a check specifically with "Can Ent1 see Ent2" in mind
else if ( _interactionSystem . InRangeUnobstructed ( source , listener , WhisperMuffledRange , Shared . Physics . CollisionGroup . Opaque ) ) //Shared.Physics.CollisionGroup.Opaque
2023-04-25 02:17:40 +06:00
_chatManager . ChatMessageToOne ( ChatChannel . Whisper , obfuscatedMessage , wrappedobfuscatedMessage , source , false , session . Channel ) ;
2023-08-01 14:43:02 +00:00
//If listener is too far and has no line of sight, they can't identify the whisperer's identity
else
2023-04-25 02:17:40 +06:00
_chatManager . ChatMessageToOne ( ChatChannel . Whisper , obfuscatedMessage , wrappedUnknownMessage , source , false , session . Channel ) ;
2022-03-30 22:21:58 -07:00
}
2023-10-14 02:02:56 -07:00
_replay . RecordServerMessage ( new ChatMessage ( ChatChannel . Whisper , message , wrappedMessage , GetNetEntity ( source ) , null , MessageRangeHideChatForReplay ( range ) ) ) ;
2022-11-23 00:52:19 +13:00
2023-04-27 08:03:44 +06:00
var ev = new EntitySpokeEvent ( source , message , originalMessage , channel , obfuscatedMessage ) ;
2022-11-15 17:09:27 +13:00
RaiseLocalEvent ( source , ev , true ) ;
2023-04-25 02:17:40 +06:00
if ( hideLog )
return ;
if ( originalMessage = = message )
{
if ( name ! = Name ( source ) )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Whisper from {ToPrettyString(source):user} as {name}: {originalMessage}." ) ;
else
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Whisper from {ToPrettyString(source):user}: {originalMessage}." ) ;
}
else
{
if ( name ! = Name ( source ) )
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
{
2023-04-25 02:17:40 +06:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low ,
$"Whisper from {ToPrettyString(source):user} as {name}, original: {originalMessage}, transformed: {message}." ) ;
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
}
2023-02-11 13:26:44 -06:00
else
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
{
2023-04-25 02:17:40 +06:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low ,
2023-02-11 13:26:44 -06:00
$"Whisper from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}." ) ;
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
}
2023-04-25 02:17:40 +06:00
}
2022-03-30 22:21:58 -07:00
}
2023-08-11 22:56:34 -07:00
private void SendEntityEmote (
EntityUid source ,
string action ,
ChatTransmitRange range ,
string? nameOverride ,
bool hideLog = false ,
bool checkEmote = true ,
2023-11-05 18:19:59 -08:00
bool ignoreActionBlocker = false ,
2023-05-01 15:05:43 +06:00
NetUserId ? author = null ,
bool force = false
2023-08-11 22:56:34 -07:00
)
2022-03-30 22:21:58 -07:00
{
2023-08-11 22:56:34 -07:00
if ( ! _actionBlocker . CanEmote ( source ) & & ! ignoreActionBlocker )
return ;
2022-03-30 22:21:58 -07:00
2022-11-15 17:09:27 +13:00
// get the entity's apparent name (if no override provided).
2023-08-11 22:56:34 -07:00
var ent = Identity . Entity ( source , EntityManager ) ;
2023-04-25 02:17:40 +06:00
var name = FormattedMessage . EscapeText ( nameOverride ? ? Name ( ent ) ) ;
2022-10-18 19:59:09 +13:00
2022-07-13 22:23:55 -07:00
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
2022-10-18 19:59:09 +13:00
var wrappedMessage = Loc . GetString ( "chat-manager-entity-me-wrap-message" ,
( "entityName" , name ) ,
2023-08-11 22:56:34 -07:00
( "entity" , ent ) ,
2023-12-30 00:38:11 +00:00
( "message" , FormattedMessage . RemoveMarkup ( action ) ) ) ;
2022-03-30 22:21:58 -07:00
2023-01-25 17:29:41 +01:00
if ( checkEmote )
TryEmoteChatInput ( source , action ) ;
2023-11-05 18:19:59 -08:00
SendInVoiceRange ( ChatChannel . Emotes , action , wrappedMessage , source , range , author ) ;
2023-04-25 02:17:40 +06:00
if ( hideLog )
return ;
if ( name ! = Name ( source ) )
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Emote from {ToPrettyString(source):user} as {name}: {action}" ) ;
else
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Emote from {ToPrettyString(source):user}: {action}" ) ;
2023-04-27 06:01:05 +06:00
//WD-EDIT
string ckey = string . Empty ;
if ( TryComp < ActorComponent > ( source , out var actorComponent ) )
{
ckey = actorComponent . PlayerSession . Name ;
}
if ( string . IsNullOrEmpty ( ckey ) ) return ;
var utkaEmoteEvent = new UtkaChatMeEvent ( )
{
Ckey = ckey ,
Message = action ,
CharacterName = MetaData ( source ) . EntityName
} ;
_utkaSockets . SendMessageToAll ( utkaEmoteEvent ) ;
//WD-EDIT
2022-03-30 22:21:58 -07:00
}
// ReSharper disable once InconsistentNaming
2023-10-28 09:59:53 +11:00
private void SendLOOC ( EntityUid source , ICommonSession player , string message , bool hideChat )
2022-03-30 22:21:58 -07:00
{
2022-10-18 19:59:09 +13:00
var name = FormattedMessage . EscapeText ( Identity . Name ( source , EntityManager ) ) ;
2023-04-28 04:44:00 +06:00
if ( _adminManager . HasAdminFlag ( player , AdminFlags . Admin ) )
2022-03-30 22:21:58 -07:00
{
2023-04-25 02:17:40 +06:00
if ( ! AdminLoocEnabled )
return ;
}
else if ( ! _loocEnabled )
{
return ;
2022-03-30 22:21:58 -07:00
}
2023-07-26 02:02:48 -04:00
// If crit player LOOC is disabled, don't send the message at all.
if ( ! _critLoocEnabled & & _mobStateSystem . IsCritical ( source ) )
return ;
2022-10-18 19:59:09 +13:00
var wrappedMessage = Loc . GetString ( "chat-manager-entity-looc-wrap-message" ,
( "entityName" , name ) ,
( "message" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-03-30 22:21:58 -07:00
2023-11-05 18:19:59 -08:00
SendInVoiceRange ( ChatChannel . LOOC , message , wrappedMessage , source , hideChat ? ChatTransmitRange . HideChat : ChatTransmitRange . Normal , player . UserId ) ;
2022-05-28 23:41:17 -07:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"LOOC from {player:Player}: {message}" ) ;
2022-03-30 22:21:58 -07:00
}
2023-10-28 09:59:53 +11:00
private void SendDeadChat ( EntityUid source , ICommonSession player , string message , bool hideChat )
2022-03-30 22:21:58 -07:00
{
var clients = GetDeadChatClients ( ) ;
var playerName = Name ( source ) ;
2022-10-18 19:59:09 +13:00
string wrappedMessage ;
2022-03-30 22:21:58 -07:00
if ( _adminManager . IsAdmin ( player ) )
{
2022-10-18 19:59:09 +13:00
wrappedMessage = Loc . GetString ( "chat-manager-send-admin-dead-chat-wrap-message" ,
2022-03-30 22:21:58 -07:00
( "adminChannelName" , Loc . GetString ( "chat-manager-admin-channel-name" ) ) ,
2023-04-25 02:17:40 +06:00
( "userName" , player . Channel . UserName ) ,
2022-10-18 19:59:09 +13:00
( "message" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-08-07 20:21:56 -03:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Admin dead chat from {player:Player}: {message}" ) ;
2022-03-30 22:21:58 -07:00
}
else
{
2022-10-18 19:59:09 +13:00
wrappedMessage = Loc . GetString ( "chat-manager-send-dead-chat-wrap-message" ,
2022-03-30 22:21:58 -07:00
( "deadChannelName" , Loc . GetString ( "chat-manager-dead-channel-name" ) ) ,
2022-10-18 19:59:09 +13:00
( "playerName" , ( playerName ) ) ,
( "message" , FormattedMessage . EscapeText ( message ) ) ) ;
2022-08-07 20:21:56 -03:00
_adminLogger . Add ( LogType . Chat , LogImpact . Low , $"Dead chat from {player:Player}: {message}" ) ;
2022-03-30 22:21:58 -07:00
}
2023-11-05 18:19:59 -08:00
_chatManager . ChatMessageToMany ( ChatChannel . Dead , message , wrappedMessage , source , hideChat , true , clients . ToList ( ) , author : player . UserId ) ;
2022-03-30 22:21:58 -07:00
}
#endregion
#region Utility
2023-12-30 00:38:11 +00:00
private enum MessageRangeCheckResult
{
2023-05-04 20:08:08 +01:00
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 :
2023-10-28 09:59:53 +11:00
initialResult = ( data . Observer & & data . Range < 0 & & ! _adminManager . IsAdmin ( session ) ) ? MessageRangeCheckResult . HideChat : MessageRangeCheckResult . Full ;
2023-05-04 20:08:08 +01:00
break ;
case ChatTransmitRange . HideChat :
initialResult = MessageRangeCheckResult . HideChat ;
break ;
case ChatTransmitRange . NoGhosts :
2023-10-28 09:59:53 +11:00
initialResult = ( data . Observer & & ! _adminManager . IsAdmin ( session ) ) ? MessageRangeCheckResult . Disallowed : MessageRangeCheckResult . Full ;
2023-05-04 20:08:08 +01:00
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 ;
}
2022-03-30 22:21:58 -07:00
/// <summary>
/// Sends a chat message to the given players in range of the source entity.
/// </summary>
2023-11-05 18:19:59 -08:00
private void SendInVoiceRange ( ChatChannel channel , string message , string wrappedMessage , EntityUid source , ChatTransmitRange range , NetUserId ? author = null )
2022-03-30 22:21:58 -07:00
{
2022-11-15 17:09:27 +13:00
foreach ( var ( session , data ) in GetRecipients ( source , VoiceRange ) )
{
2023-05-04 20:08:08 +01:00
var entRange = MessageRangeCheck ( session , data , range ) ;
if ( entRange = = MessageRangeCheckResult . Disallowed )
continue ;
var entHideChat = entRange = = MessageRangeCheckResult . HideChat ;
2023-11-05 18:19:59 -08:00
_chatManager . ChatMessageToOne ( channel , message , wrappedMessage , source , entHideChat , session . ConnectedClient , author : author ) ;
2022-11-15 17:09:27 +13:00
}
2022-11-23 00:52:19 +13:00
2023-10-14 02:02:56 -07:00
_replay . RecordServerMessage ( new ChatMessage ( channel , message , wrappedMessage , GetNetEntity ( source ) , null , MessageRangeHideChatForReplay ( range ) ) ) ;
2022-03-30 22:21:58 -07:00
}
/// <summary>
/// Returns true if the given player is 'allowed' to send the given message, false otherwise.
/// </summary>
2023-10-28 09:59:53 +11:00
private bool CanSendInGame ( string message , IConsoleShell ? shell = null , ICommonSession ? player = null )
2022-03-30 22:21:58 -07:00
{
// Non-players don't have to worry about these restrictions.
if ( player = = null )
return true ;
2023-06-18 11:33:19 -07:00
var mindContainerComponent = player . ContentData ( ) ? . Mind ;
2022-03-30 22:21:58 -07:00
2023-06-18 11:33:19 -07:00
if ( mindContainerComponent = = null )
2022-03-30 22:21:58 -07:00
{
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
2023-04-25 02:17:40 +06:00
private string SanitizeInGameICMessage ( EntityUid source , string message , out string? emoteStr , bool capitalize = true , bool punctuate = false , bool sanitizeSlang = true )
2022-03-30 22:21:58 -07:00
{
var newMessage = message . Trim ( ) ;
2023-04-28 10:13:54 +06:00
newMessage = _sanitizer . SanitizeTags ( newMessage ) ;
2023-04-25 02:17:40 +06:00
if ( sanitizeSlang )
newMessage = _sanitizer . SanitizeOutSlang ( newMessage ) ;
2022-04-24 20:15:40 -04:00
if ( capitalize )
2022-08-31 13:42:46 +01:00
newMessage = SanitizeMessageCapital ( newMessage ) ;
if ( punctuate )
newMessage = SanitizeMessagePeriod ( newMessage ) ;
2022-03-30 22:21:58 -07:00
_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 ;
}
2022-08-02 15:46:10 +02:00
public string TransformSpeech ( EntityUid sender , string message )
2022-03-30 22:21:58 -07:00
{
var ev = new TransformSpeechEvent ( sender , message ) ;
RaiseLocalEvent ( ev ) ;
return ev . Message ;
}
2023-12-22 09:13:45 -05:00
2023-12-01 19:25:20 -05:00
public bool CheckIgnoreSpeechBlocker ( EntityUid sender , bool ignoreBlocker )
{
if ( ignoreBlocker )
return ignoreBlocker ;
var ev = new CheckIgnoreSpeechBlockerEvent ( sender , ignoreBlocker ) ;
RaiseLocalEvent ( sender , ev , true ) ;
return ev . IgnoreBlocker ;
}
2022-03-30 22:21:58 -07:00
private IEnumerable < INetChannel > GetDeadChatClients ( )
{
return Filter . Empty ( )
2022-07-04 16:00:51 +10:00
. AddWhereAttachedEntity ( HasComp < GhostComponent > )
2022-03-30 22:21:58 -07:00
. Recipients
. Union ( _adminManager . ActiveAdmins )
. Select ( p = > p . ConnectedClient ) ;
}
2022-08-31 13:42:46 +01:00
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 ;
}
2023-10-21 15:43:11 -04:00
[ValidatePrototypeId<ReplacementAccentPrototype>]
2023-04-25 02:17:40 +06:00
public const string ChatSanitizeAccent = "chatsanitize" ;
2023-10-21 15:43:11 -04:00
public string SanitizeMessageReplaceWords ( string message )
{
2023-04-25 02:17:40 +06:00
if ( string . IsNullOrEmpty ( message ) )
return message ;
2023-10-21 15:43:11 -04:00
var msg = message ;
2023-04-25 02:17:40 +06:00
msg = _wordreplacement . ApplyReplacements ( msg , ChatSanitizeAccent ) ;
2023-10-21 15:43:11 -04:00
return msg ;
}
2022-11-15 17:09:27 +13:00
/// <summary>
/// Returns list of players and ranges for all players withing some range. Also returns observers with a range of -1.
/// </summary>
2023-08-01 14:43:02 +00:00
private Dictionary < ICommonSession , ICChatRecipientData > GetRecipients ( EntityUid source , float voiceGetRange )
2022-03-30 22:21:58 -07:00
{
2022-11-15 17:09:27 +13:00
// TODO proper speech occlusion
var recipients = new Dictionary < ICommonSession , ICChatRecipientData > ( ) ;
2023-09-24 13:34:08 -07:00
var ghostHearing = GetEntityQuery < GhostHearingComponent > ( ) ;
2022-03-30 22:21:58 -07:00
var xforms = GetEntityQuery < TransformComponent > ( ) ;
var transformSource = xforms . GetComponent ( source ) ;
var sourceMapId = transformSource . MapID ;
var sourceCoords = transformSource . Coordinates ;
foreach ( var player in _playerManager . Sessions )
{
2023-12-30 00:38:11 +00:00
if ( player . AttachedEntity is not { Valid : true } playerEntity )
2022-03-30 22:21:58 -07:00
continue ;
var transformEntity = xforms . GetComponent ( playerEntity ) ;
2022-11-15 17:09:27 +13:00
if ( transformEntity . MapID ! = sourceMapId )
2022-03-30 22:21:58 -07:00
continue ;
2023-09-24 13:34:08 -07:00
var observer = ghostHearing . HasComponent ( playerEntity ) ;
2022-11-15 17:09:27 +13:00
2023-09-24 13:34:08 -07:00
// even if they are a ghost hearer, in some situations we still need the range
2023-08-01 14:43:02 +00:00
if ( sourceCoords . TryDistance ( EntityManager , transformEntity . Coordinates , out var distance ) & & distance < voiceGetRange )
2022-11-15 17:09:27 +13:00
{
recipients . Add ( player , new ICChatRecipientData ( distance , observer ) ) ;
continue ;
}
if ( observer )
recipients . Add ( player , new ICChatRecipientData ( - 1 , true ) ) ;
2022-03-30 22:21:58 -07:00
}
2022-11-15 17:09:27 +13:00
2023-08-01 14:43:02 +00:00
RaiseLocalEvent ( new ExpandICChatRecipientstEvent ( source , voiceGetRange , recipients ) ) ;
2022-11-15 17:09:27 +13:00
return recipients ;
}
2023-04-25 02:17:40 +06:00
public readonly record struct ICChatRecipientData ( float Range , bool Observer , bool? HideChatOverride = null ) ;
2022-03-30 22:21:58 -07:00
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
}
2022-11-15 17:09:27 +13:00
/// <summary>
/// This event is raised before chat messages are sent out to clients. This enables some systems to send the chat
/// messages to otherwise out-of view entities (e.g. for multiple viewports from cameras).
/// </summary>
2023-04-25 02:17:40 +06:00
public record ExpandICChatRecipientstEvent ( EntityUid Source , float VoiceRange , Dictionary < ICommonSession , ChatSystem . ICChatRecipientData > Recipients ) ;
2022-11-15 17:09:27 +13:00
2022-09-28 19:22:27 -07:00
public sealed class TransformSpeakerNameEvent : EntityEventArgs
{
public EntityUid Sender ;
public string Name ;
public TransformSpeakerNameEvent ( EntityUid sender , string name )
{
Sender = sender ;
Name = name ;
}
}
2022-03-30 22:21:58 -07:00
/// <summary>
Northstar Gloves (#16021)
* Added Gloves of North Star, no sprite or talking yet...
* Added sprites for the gloves of the north star...
* Replaced more placeholder sprites for northstar gloves...
* Added gloves of the north star to uplink...
* Added speech on hit, not yet configureable
* Not functional yet, but a step in the right direction I hope...
* IT WORKS!!
* Licensing and cleanup
* Reduced attack speed, changed from chat to popup, added some admin logging. It was causing too much adminlog spam otherwise
* Reorganized some files, final build??
* Changed the adminlog type from Verb to new type ItemConfigure
* More cleanup, fix sprite reference maybe
* Keronshb's suggestions, fixed some stuff, made hit sound use the meaty punch sfx
* Adds support for hiding speak/whisper/emote from adminlogs, makes northstar speak again!
* Some file shuffling, some of Keronshb's requests. Might appear a bit funky in github because vscode kept duplicating files for some reason and I had to delete them
* Made it work with the latest changes on Master
* Final? cleanup, upped dmg to 8, made ui not activate on activateinhand, instead you need to right click
* Set value to 0 credits, that's all
* Well that was much easier than I made it out to be. Now you can only activate the gloves with right click, no more mispredicts.
* Update MeleeWeaponSystem.cs
Iunno why this got changed in the first place, but I'm changin it back
* emptycommit
* emptycommit
* The tiny fixening
2023-05-23 11:12:30 -07:00
/// Raised broadcast in order to transform speech.transmit
2022-03-30 22:21:58 -07:00
/// </summary>
public sealed class TransformSpeechEvent : EntityEventArgs
{
public EntityUid Sender ;
public string Message ;
public TransformSpeechEvent ( EntityUid sender , string message )
{
Sender = sender ;
Message = message ;
}
}
2023-12-01 19:25:20 -05:00
public sealed class CheckIgnoreSpeechBlockerEvent : EntityEventArgs
{
public EntityUid Sender ;
public bool IgnoreBlocker ;
public CheckIgnoreSpeechBlockerEvent ( EntityUid sender , bool ignoreBlocker )
{
Sender = sender ;
IgnoreBlocker = ignoreBlocker ;
}
}
2022-03-30 22:21:58 -07:00
/// <summary>
/// Raised on an entity when it speaks, either through 'say' or 'whisper'.
/// </summary>
public sealed class EntitySpokeEvent : EntityEventArgs
{
2022-11-15 17:09:27 +13:00
public readonly EntityUid Source ;
public readonly string Message ;
2023-04-27 08:03:44 +06:00
public readonly string OriginalMessage ;
2022-11-15 17:09:27 +13:00
public readonly string? ObfuscatedMessage ; // not null if this was a whisper
/// <summary>
/// If the entity was trying to speak into a radio, this was the channel they were trying to access. If a radio
/// message gets sent on this channel, this should be set to null to prevent duplicate messages.
/// </summary>
public RadioChannelPrototype ? Channel ;
2022-03-30 22:21:58 -07:00
2023-04-27 08:03:44 +06:00
public EntitySpokeEvent ( EntityUid source , string message , string originalMessage , RadioChannelPrototype ? channel , string? obfuscatedMessage )
2022-03-30 22:21:58 -07:00
{
2022-11-15 17:09:27 +13:00
Source = source ;
2022-03-30 22:21:58 -07:00
Message = message ;
2023-04-27 08:03:44 +06:00
OriginalMessage = originalMessage ;
2022-11-15 17:09:27 +13:00
Channel = channel ;
ObfuscatedMessage = obfuscatedMessage ;
2022-03-30 22:21:58 -07:00
}
}
2023-04-26 01:21:26 +06:00
//WD-EDIT
public class SetSpeakerColorEvent
{
public EntityUid Sender { get ; set ; }
public string Name { get ; set ; }
public SetSpeakerColorEvent ( EntityUid sender , string name )
{
Sender = sender ;
Name = name ;
}
}
2023-04-28 10:10:25 +06:00
public sealed class SpeechTransformedEvent : EntityEventArgs
{
public EntityUid Sender ;
public string Message ;
public SpeechTransformedEvent ( EntityUid sender , string message )
{
Sender = sender ;
Message = message ;
}
}
2023-04-26 01:21:26 +06:00
//WD-EDIT
2022-03-30 22:21:58 -07:00
/// <summary>
/// InGame IC chat is for chat that is specifically ingame (not lobby) but is also in character, i.e. speaking.
/// </summary>
// ReSharper disable once InconsistentNaming
public enum InGameICChatType : byte
{
Speak ,
Emote ,
Whisper
}
/// <summary>
/// InGame OOC chat is for chat that is specifically ingame (not lobby) but is OOC, like deadchat or LOOC.
/// </summary>
public enum InGameOOCChatType : byte
{
Looc ,
Dead
}
2023-05-04 20:08:08 +01:00
/// <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
}