Files
OldThink/Content.Server/Chat/Systems/ChatSystem.cs

1124 lines
42 KiB
C#
Raw Normal View History

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;
using Content.Server.Speech.Components;
using Content.Server.Speech.EntitySystems;
2023-07-31 09:33:04 +03:00
using Content.Server.Ghost.Components;
using Content.Server.Players;
using Content.Server.Popups;
using Content.Server.Speech.Components;
using Content.Server.Station.Components;
using Content.Server.Station.Systems;
using Content.Server.White.AspectsSystem.Aspects.Components;
2023-05-24 17:41:22 +06:00
using Content.Server.White.Other.Speech;
[Feat] Panda socket (#616) * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api * [DRAFT] Panda HTTP server (#570) * [Sponsor] Nairsark ghost (#492) * [Sponsor] Trora ghost (#493) * [Sponsor] Trora ghost * ckey * Vtergot fluff (#494) * Твики (#491) * Economy additions * Tweak implant cooldowns * Cult stuff * Random appearance aspect nuke ops fix * Auto shuttle enable on round end * Holy water threshold * Automatic changelog update * [Sponsor] Geraldiy fluff (#496) * [Fix] Reputation respawn hotfix (#497) * Automatic changelog update * Новые аспекты (#495) * Add ReflectAspect * Add SlipperyAspect * Add TraitorRichAspect * Add WhisperAspect * Add DarknessAspect & StolenFloorAspect * Add WindowLeakAspect * Add CatEarsAspect * Add NothingAspect * Fix fast and furious clone * Add SkeletonAspect * Add cvar ceanup * Automatic changelog update * [Sponsor] Fluff knife cappy (#498) * [Sponsor] Fluff KnifeCappy * loadout * Всякое (#501) * eftpos form jurist * ebal parameda * Automatic changelog update * [Sponsor] Zilendorie ghost upgrade (#502) * Апдейт карт, станция прибытия (#503) * Automatic changelog update * Fix aspects (#500) * Automatic changelog update * Rules popup fix (#504) * Defib fix (#499) * Automatic changelog update * Фикс флаффа (#507) * Фикс прибытия (#509) * Automatic changelog update * Фиксы (#508) * Fix cult blindfold * Add stamina resistances * Energy bolt is energy * Laser shield is anti-laser * Cult blindfold welding protection * Eject id cards on deconstruct * Wires panel power fix * Add markings for species * Ebow gaming * feat: настенные консольки (#505) * Automatic changelog update * [Sponsor] Fluff Medicgaming (#510) * Привязка банковского аккаунта (#506) * Sustenance vend price fix * Account link * Automatic changelog update * antag ban fix (#511) * antag ban fix * rename some shit * Bugfixes (#512) * Bullets go through open crates * Bullets don't hit pulled dead bodies * No glued cuffs * Missed reflect aspect mark * vehicles cannot be shot (#18910) Co-authored-by: deltanedas <@deltanedas:kde.org> * Cleanup --------- Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> * Automatic changelog update * атмос гейминг indeed (#513) * fix hypernob + plasma and trit fire * add 11 new gas types * actually fix hypernob --------- Co-authored-by: halicopter <kirillhalic@gmail.com> * Automatic changelog update * [Sponsor] thechaotic fluff * Revert "[Sponsor] thechaotic fluff" This reverts commit d19c807751258f9cfb205db01b0e4617b638fe98. * [Sponsor] Fluff updates (#518) * warete update * renosan & warete size * [Sponsor] thechaotic fluff (#519) (cherry picked from commit d19c807751258f9cfb205db01b0e4617b638fe98) * fix tts sanitize (#517) * Automatic changelog update * [Fluff] ghost regari * Buffs (#515) * FIRE axe * Bow pro * Bang * Meleeeee * Disarm two-handed hard * Automatic changelog update * Игнор вайтлист требования ролей если стоит флаг TOOLS (#514) * forAmins be like * >вайтлист в девелопменте * а описание кто напишет а * Размеры попапов в чате (#525) * попытка деконить магнит пока он активен гибает интернет приколистов (#524) * Интернет прикол * попап * Prevent using cultist items & halberd (#523) * Fix skeleton aspect (#522) * Fix cult door (#521) * Fix cult door * Prevent emagging * Walls and griders * Fix * Drone fix (#520) * Drone fix * RCD * Automatic changelog update * Смешное в конце раунда с аспектом (#516) * Cats * Fix * Automatic changelog update * Fix cult win conditions (#527) * Runes stuff (#526) * Blood boil 2.0 * Attach to grid * No * Automatic changelog update * [Sponsor] thefrendlypsychopath ghost (#529) * knifeCappy (#528) Co-authored-by: Mona Hmiza <you@example.com> * [Feat] TTS 8 new voices * borg fix * Automatic changelog update * мапперы тестили * fix void prototype * [Fix] Cult chat fix (#536) * ERT tweak (#530) * tweak values * tweak spawn logic * Invisible rune (#531) * Automatic changelog update * [Sponsor] Oleg_Tinkoff fluff (#534) * [Sponsor] Oleg_Tinkoff fluff * name fix * Small fixes (#537) * No sleeping emotes * Beakers & jugs in fridge * Drone fix * Fix teleport pulling * Fix loc * More fixes * Fix ghost role * Stuff * Automatic changelog update * Nerf grilles (#541) * Nerf grilles * tweak * Bola fix (#540) * Automatic changelog update * Куча говна (#539) * No Emp resistance (#538) * No EMP resistance * Cleanup * Automatic changelog update * mappews mapped some mowe~ * anothew update UwU * [Sponsor] Geraldiy fluff 2 (#542) * fix drydock * Objective changes (#543) * Automatic changelog update * UwU spawnews are now fixed, hopefully~ * UwU tired of it alweady * Automatic changelog update * Респрайт капитанского лазера (#546) * antique lasergun resprite lol * лицуха * Automatic changelog update * Апдейт арахнидов (#544) * Web underwear * Fix layers * Change melee sound * Arachnid 2: Episode 2 (#19984) * Shield * minor sprite changes and buffs * structure buff * Crafting stuff * tweaks * 88-88 * Better web pocket sprites. * yeah it's fine now. * Fix * Sprite tweaks * This I guess * Eye sprite --------- Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * [Sponsor] Zilendorie fluff (#545) * Automatic changelog update * Fire buff (#549) * EntityStorage deletion fix? (#548) * strip fix (#21552) (#547) Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * Automatic changelog update * [Fluff] SSAO Ghost * [Fluff] svinka ghost tweak * [Fluff] Svinka Coat * Meaty-Ore Idea (#550) * qwe * yeah * Vtergot fluff fix (#552) * наяриваем на лишние пиксели * Чтобы мапперы не втыкали * fix atmos (#551) * fix atmos * fixie * Automatic changelog update * Meow hotfix (#553) * Many stuff (#555) * Drone bucket * Weird insuls * Not too strong * No cult door bolts * Emergency shuttle after round end fix * Fix spiders * Automatic changelog update * [FEAT] Всякие прикольные разности и вкусности (#554) * feat: трикодер * feat: принтер документов * fix: текст фелинидов * feat: возможность менять голос эмоутов * feat: мяукаем при аспекте мяуканья * feat: ПНВ * fix: забирай свои метадаты * fix: oopsies * fix: линтер снова * fix: пожалуйста линтер отстань * Automatic changelog update * Гарпии (#533) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * Revert "Гарпии (#533)" (#557) This reverts commit e3f2166bf7cbd775278a396a3f8d8215a2d5c506. * Doom fluff (#556) * DOOMMAX fluff * Detective meow * Сеньёр помидор офицер * RSI validator su4ka * [Fluff] MR_Regari ghost tweak * [Fluff] svinka ghost tweak * [Fluff] Antohag gasmask fluff * ГАРПИИ (#559) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * [Tweak] Бумажная работа и фикс крафта пнв. (#560) * fix: персонал станции вспомнил как делать пнв * tweak: блюспейс технологии убраны у принтера документов * feat: заказ бумаги в карго * feat: бумажная дверь * Automatic changelog update * doommaxx-fluff nothing interesting * fluff skufa (#562) * [Sponsor] Fluff Forg (#567) * [Sponsor] Fluff Forg * fix size * sound * [Feat] TTS 15 new voices (#568) * Automatic changelog update * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine <valtos@nextmail.ru> Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter <kirillhalic@gmail.com> Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza <you@example.com> Co-authored-by: Valtos <valtos@spaces.ru> Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * newtonsoft version change * remove semaphore * remove double dooc * fix admin stealth * cleanup * remove utka sockets --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine <valtos@nextmail.ru> Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter <kirillhalic@gmail.com> Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza <you@example.com> Co-authored-by: Valtos <valtos@spaces.ru> Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com>
2024-01-09 15:54:16 +03:00
using Content.Server.White.PandaSocket.Main;
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;
using Content.Shared.Decals;
2023-08-25 18:50:46 +10:00
using Content.Shared.Ghost;
using Content.Shared.Humanoid;
2022-07-13 22:23:55 -07:00
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Mobs.Systems;
using Content.Shared.Players;
2022-11-23 00:52:19 +13:00
using Content.Shared.Radio;
using Content.Shared.White;
using Content.Shared.Speech;
2022-03-30 22:21:58 -07:00
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
2022-03-30 22:21:58 -07:00
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects.Components.Localization;
2022-03-30 22:21:58 -07:00
using Robust.Shared.Network;
using Robust.Shared.Player;
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;
namespace Content.Server.Chat.Systems;
2022-03-30 22:21:58 -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>
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!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
2022-03-30 22:21:58 -07:00
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
2022-03-30 22:21:58 -07:00
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!;
2023-07-16 11:32:42 +03:00
[Dependency] private readonly INetConfigurationManager _netConfigurationManager = default!; // WD
[Dependency] private readonly GameTicker _gameTicker = default!; // WD
2022-03-30 22:21:58 -07:00
//WD-EDIT
[Feat] Panda socket (#616) * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api * [DRAFT] Panda HTTP server (#570) * [Sponsor] Nairsark ghost (#492) * [Sponsor] Trora ghost (#493) * [Sponsor] Trora ghost * ckey * Vtergot fluff (#494) * Твики (#491) * Economy additions * Tweak implant cooldowns * Cult stuff * Random appearance aspect nuke ops fix * Auto shuttle enable on round end * Holy water threshold * Automatic changelog update * [Sponsor] Geraldiy fluff (#496) * [Fix] Reputation respawn hotfix (#497) * Automatic changelog update * Новые аспекты (#495) * Add ReflectAspect * Add SlipperyAspect * Add TraitorRichAspect * Add WhisperAspect * Add DarknessAspect & StolenFloorAspect * Add WindowLeakAspect * Add CatEarsAspect * Add NothingAspect * Fix fast and furious clone * Add SkeletonAspect * Add cvar ceanup * Automatic changelog update * [Sponsor] Fluff knife cappy (#498) * [Sponsor] Fluff KnifeCappy * loadout * Всякое (#501) * eftpos form jurist * ebal parameda * Automatic changelog update * [Sponsor] Zilendorie ghost upgrade (#502) * Апдейт карт, станция прибытия (#503) * Automatic changelog update * Fix aspects (#500) * Automatic changelog update * Rules popup fix (#504) * Defib fix (#499) * Automatic changelog update * Фикс флаффа (#507) * Фикс прибытия (#509) * Automatic changelog update * Фиксы (#508) * Fix cult blindfold * Add stamina resistances * Energy bolt is energy * Laser shield is anti-laser * Cult blindfold welding protection * Eject id cards on deconstruct * Wires panel power fix * Add markings for species * Ebow gaming * feat: настенные консольки (#505) * Automatic changelog update * [Sponsor] Fluff Medicgaming (#510) * Привязка банковского аккаунта (#506) * Sustenance vend price fix * Account link * Automatic changelog update * antag ban fix (#511) * antag ban fix * rename some shit * Bugfixes (#512) * Bullets go through open crates * Bullets don't hit pulled dead bodies * No glued cuffs * Missed reflect aspect mark * vehicles cannot be shot (#18910) Co-authored-by: deltanedas <@deltanedas:kde.org> * Cleanup --------- Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> * Automatic changelog update * атмос гейминг indeed (#513) * fix hypernob + plasma and trit fire * add 11 new gas types * actually fix hypernob --------- Co-authored-by: halicopter <kirillhalic@gmail.com> * Automatic changelog update * [Sponsor] thechaotic fluff * Revert "[Sponsor] thechaotic fluff" This reverts commit d19c807751258f9cfb205db01b0e4617b638fe98. * [Sponsor] Fluff updates (#518) * warete update * renosan & warete size * [Sponsor] thechaotic fluff (#519) (cherry picked from commit d19c807751258f9cfb205db01b0e4617b638fe98) * fix tts sanitize (#517) * Automatic changelog update * [Fluff] ghost regari * Buffs (#515) * FIRE axe * Bow pro * Bang * Meleeeee * Disarm two-handed hard * Automatic changelog update * Игнор вайтлист требования ролей если стоит флаг TOOLS (#514) * forAmins be like * >вайтлист в девелопменте * а описание кто напишет а * Размеры попапов в чате (#525) * попытка деконить магнит пока он активен гибает интернет приколистов (#524) * Интернет прикол * попап * Prevent using cultist items & halberd (#523) * Fix skeleton aspect (#522) * Fix cult door (#521) * Fix cult door * Prevent emagging * Walls and griders * Fix * Drone fix (#520) * Drone fix * RCD * Automatic changelog update * Смешное в конце раунда с аспектом (#516) * Cats * Fix * Automatic changelog update * Fix cult win conditions (#527) * Runes stuff (#526) * Blood boil 2.0 * Attach to grid * No * Automatic changelog update * [Sponsor] thefrendlypsychopath ghost (#529) * knifeCappy (#528) Co-authored-by: Mona Hmiza <you@example.com> * [Feat] TTS 8 new voices * borg fix * Automatic changelog update * мапперы тестили * fix void prototype * [Fix] Cult chat fix (#536) * ERT tweak (#530) * tweak values * tweak spawn logic * Invisible rune (#531) * Automatic changelog update * [Sponsor] Oleg_Tinkoff fluff (#534) * [Sponsor] Oleg_Tinkoff fluff * name fix * Small fixes (#537) * No sleeping emotes * Beakers & jugs in fridge * Drone fix * Fix teleport pulling * Fix loc * More fixes * Fix ghost role * Stuff * Automatic changelog update * Nerf grilles (#541) * Nerf grilles * tweak * Bola fix (#540) * Automatic changelog update * Куча говна (#539) * No Emp resistance (#538) * No EMP resistance * Cleanup * Automatic changelog update * mappews mapped some mowe~ * anothew update UwU * [Sponsor] Geraldiy fluff 2 (#542) * fix drydock * Objective changes (#543) * Automatic changelog update * UwU spawnews are now fixed, hopefully~ * UwU tired of it alweady * Automatic changelog update * Респрайт капитанского лазера (#546) * antique lasergun resprite lol * лицуха * Automatic changelog update * Апдейт арахнидов (#544) * Web underwear * Fix layers * Change melee sound * Arachnid 2: Episode 2 (#19984) * Shield * minor sprite changes and buffs * structure buff * Crafting stuff * tweaks * 88-88 * Better web pocket sprites. * yeah it's fine now. * Fix * Sprite tweaks * This I guess * Eye sprite --------- Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * [Sponsor] Zilendorie fluff (#545) * Automatic changelog update * Fire buff (#549) * EntityStorage deletion fix? (#548) * strip fix (#21552) (#547) Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * Automatic changelog update * [Fluff] SSAO Ghost * [Fluff] svinka ghost tweak * [Fluff] Svinka Coat * Meaty-Ore Idea (#550) * qwe * yeah * Vtergot fluff fix (#552) * наяриваем на лишние пиксели * Чтобы мапперы не втыкали * fix atmos (#551) * fix atmos * fixie * Automatic changelog update * Meow hotfix (#553) * Many stuff (#555) * Drone bucket * Weird insuls * Not too strong * No cult door bolts * Emergency shuttle after round end fix * Fix spiders * Automatic changelog update * [FEAT] Всякие прикольные разности и вкусности (#554) * feat: трикодер * feat: принтер документов * fix: текст фелинидов * feat: возможность менять голос эмоутов * feat: мяукаем при аспекте мяуканья * feat: ПНВ * fix: забирай свои метадаты * fix: oopsies * fix: линтер снова * fix: пожалуйста линтер отстань * Automatic changelog update * Гарпии (#533) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * Revert "Гарпии (#533)" (#557) This reverts commit e3f2166bf7cbd775278a396a3f8d8215a2d5c506. * Doom fluff (#556) * DOOMMAX fluff * Detective meow * Сеньёр помидор офицер * RSI validator su4ka * [Fluff] MR_Regari ghost tweak * [Fluff] svinka ghost tweak * [Fluff] Antohag gasmask fluff * ГАРПИИ (#559) * harpy initial * fix and some locale * ru locale * actions refactor shit * пофиксив гавпий~~~ пойду тестить~~ * hawpies are ready UwU * cweanup OwO * hawpies fixed a bit, still cant seawch them nya~ * hawpies can be stwipped now, fixie-dixie awwived~ * emotes fixie-dixied nya~ * говно * говно говна линтер соси * Automatic changelog update * [Tweak] Бумажная работа и фикс крафта пнв. (#560) * fix: персонал станции вспомнил как делать пнв * tweak: блюспейс технологии убраны у принтера документов * feat: заказ бумаги в карго * feat: бумажная дверь * Automatic changelog update * doommaxx-fluff nothing interesting * fluff skufa (#562) * [Sponsor] Fluff Forg (#567) * [Sponsor] Fluff Forg * fix size * sound * [Feat] TTS 15 new voices (#568) * Automatic changelog update * base server side * token check & base command response * panda base command response addition & some commands * web event, rest of commands & events * fix empty api --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine <valtos@nextmail.ru> Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter <kirillhalic@gmail.com> Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza <you@example.com> Co-authored-by: Valtos <valtos@spaces.ru> Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com> * newtonsoft version change * remove semaphore * remove double dooc * fix admin stealth * cleanup * remove utka sockets --------- Co-authored-by: Cinkafox <70429757+Cinkafox@users.noreply.github.com> Co-authored-by: Aviu00 <93730715+Aviu00@users.noreply.github.com> Co-authored-by: RavmorganButOnCocaine <valtos@nextmail.ru> Co-authored-by: Kotovskiy <77529717+wCATw@users.noreply.github.com> Co-authored-by: ThereDrD0 <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Remuchi <72476615+Remuchi@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: KurokoTurbo <92106367+melanoTurbo@users.noreply.github.com> Co-authored-by: halicopter <kirillhalic@gmail.com> Co-authored-by: rhailrake <49613070+rhailrake@users.noreply.github.com> Co-authored-by: Subversionary <109166122+Subversionary@users.noreply.github.com> Co-authored-by: RavMorgan <48182970+RavMorgan@users.noreply.github.com> Co-authored-by: Mona Hmiza <you@example.com> Co-authored-by: Valtos <valtos@spaces.ru> Co-authored-by: REBOLUTION228-a11 <128076300+REBOLUTION228-a11@users.noreply.github.com> Co-authored-by: PixelTK <85175107+PixelTheKermit@users.noreply.github.com>
2024-01-09 15:54:16 +03:00
[Dependency] private readonly PandaWebManager _pandaWeb = default!;
//WD-EDIT
2022-11-15 17:09:27 +13:00
public const int VoiceRange = 10; // how far voice goes in world units
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;
private bool _deadLoocEnabled;
private bool _critLoocEnabled;
private const bool AdminLoocEnabled = true;
2022-03-30 22:21:58 -07:00
[ValidatePrototypeId<ColorPalettePrototype>]
private const string _chatNamePalette = "Material";
private string[] _chatNameColors = default!;
2022-03-30 22:21:58 -07:00
public override void Initialize()
{
base.Initialize();
CacheEmotes();
2022-03-30 22:21:58 -07:00
_configurationManager.OnValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged, true);
_configurationManager.OnValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged, true);
_configurationManager.OnValueChanged(CCVars.CritLoocEnabled, OnCritLoocEnabledChanged, true);
2022-06-20 16:59:04 -04:00
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnGameChange);
var nameColors = _prototypeManager.Index<ColorPalettePrototype>(_chatNamePalette).Colors.Values.ToArray();
_chatNameColors = new string[nameColors.Length];
for (var i = 0; i < nameColors.Length; i++)
{
_chatNameColors[i] = nameColors[i].ToHex();
}
2022-03-30 22:21:58 -07:00
}
public override void Shutdown()
{
base.Shutdown();
2022-03-30 22:21:58 -07:00
_configurationManager.UnsubValueChanged(CCVars.LoocEnabled, OnLoocEnabledChanged);
_configurationManager.UnsubValueChanged(CCVars.DeadLoocEnabled, OnDeadLoocEnabledChanged);
_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"));
}
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"));
}
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)
{
2023-05-23 11:12:30 -07:00
switch (ev.New)
{
case GameRunLevel.InRound:
2023-05-23 11:12:30 -07:00
if (!_configurationManager.GetCVar(CCVars.OocEnableDuringRound))
_configurationManager.SetCVar(CCVars.OocEnabled, false);
break;
case GameRunLevel.PostRound:
2023-05-23 11:12:30 -07:00
if (!_configurationManager.GetCVar(CCVars.OocEnableDuringRound))
_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>
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>
public void TrySendInGameICMessage(
EntityUid source,
string message,
InGameICChatType desiredType,
bool hideChat, bool hideLog = false,
IConsoleShell? shell = null,
ICommonSession? player = null, string? nameOverride = null,
bool checkRadioPrefix = true,
2024-01-23 08:43:11 +03:00
bool ignoreActionBlocker = false)
{
2024-01-23 08:43:11 +03:00
TrySendInGameICMessage(source, message, desiredType, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, hideLog, shell, player, nameOverride, checkRadioPrefix, ignoreActionBlocker);
}
/// <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>
/// <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,
ICommonSession? player = null,
string? nameOverride = null,
bool checkRadioPrefix = true,
2024-01-23 08:43:11 +03:00
bool ignoreActionBlocker = false
)
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.
TrySendInGameOOCMessage(source, message, InGameOOCChatType.Dead, range == ChatTransmitRange.HideChat, shell, player);
2022-03-30 22:21:58 -07:00
return;
}
if (player != null && !_chatManager.HandleRateLimit(player))
return;
// Sus
if (player?.AttachedEntity is { Valid: true } entity && source != entity)
{
return;
}
2024-01-23 08:43:11 +03:00
if (!CanSendInGame(message, shell, player))
2022-03-30 22:21:58 -07:00
return;
ignoreActionBlocker = CheckIgnoreSpeechBlocker(source, ignoreActionBlocker);
// 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
if (player != null)
{
_chatManager.EnsurePlayer(player.UserId).AddEntity(GetNetEntity(source));
}
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..];
}
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
message = SanitizeInGameICMessage(source, message, out var emoteStr, shouldCapitalize, shouldPunctuate, sanitizeSlang);
2022-03-30 22:21:58 -07:00
// Was there an emote in the message? If so, send it.
if (emoteStr != message && emoteStr != null)
2022-03-30 22:21:58 -07:00
{
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
2022-03-30 22:21:58 -07:00
}
// This can happen if the entire string is sanitized out.
if (string.IsNullOrEmpty(message))
return;
2023-07-31 09:33:04 +03:00
if (desiredType != InGameICChatType.Emote && player is not null &&
!_chatManager.TrySendNewMessage(player, message, true)) // WD
2023-07-31 09:33:04 +03:00
return;
// 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))
{
SendEntityWhisper(source, modMessage, range, channel, nameOverride, hideLog, ignoreActionBlocker);
return;
}
}
if (desiredType == InGameICChatType.Speak &&
_gameTicker.GetActiveGameRules().Where(HasComp<WhisperAspectComponent>).Any()) // WD
{
desiredType = InGameICChatType.Whisper;
}
2022-03-30 22:21:58 -07:00
// Otherwise, send whatever type.
switch (desiredType)
{
case InGameICChatType.Speak:
SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker);
2022-03-30 22:21:58 -07:00
break;
case InGameICChatType.Whisper:
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
2022-03-30 22:21:58 -07:00
break;
case InGameICChatType.Emote:
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
2022-03-30 22:21:58 -07:00
break;
}
}
public void TrySendInGameOOCMessage(
EntityUid source,
string message,
InGameOOCChatType type,
bool hideChat,
IConsoleShell? shell = null,
ICommonSession? player = null
)
2022-03-30 22:21:58 -07:00
{
if (!CanSendInGame(message, shell, player))
return;
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);
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;
// If crit player LOOC is disabled, don't send the message at all.
if (!_critLoocEnabled && _mobStateSystem.IsCritical(source))
return;
2023-07-31 09:33:04 +03:00
if (!_chatManager.TrySendNewMessage(player, message)) // WD
return;
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;
}
#region Announcements
/// <summary>
/// Dispatches an announcement to all.
/// </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>
/// <param name="colorOverride">Optional color for the announcement message</param>
public void DispatchGlobalAnnouncement(
string message,
string sender = "Central Command",
bool playSound = true,
SoundSpecifier? announcementSound = null,
Color? colorOverride = null
)
{
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)
{
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, AudioParams.Default.WithVolume(-2f));
}
_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>
public void DispatchStationAnnouncement(
EntityUid source,
string message,
string sender = "Central Command",
bool playDefaultSound = true,
SoundSpecifier? announcementSound = null,
Color? colorOverride = null)
{
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
var station = _stationSystem.GetOwningStation(source);
if (station == null)
{
// you can't make a station announcement without a station
return;
}
if (!EntityManager.TryGetComponent<StationDataComponent>(station, out var stationDataComp)) return;
var filter = _stationSystem.GetInStation(stationDataComp);
2022-11-23 00:52:19 +13:00
_chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, wrappedMessage, source, false, true, colorOverride);
if (playDefaultSound)
{
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
}
_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
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
{
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
2022-07-26 16:49:23 -07:00
return;
2022-03-30 22:21:58 -07:00
var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage));
if (message.Length == 0)
return;
2023-04-28 10:10:25 +06:00
message = AfterSpeechTransformed(source, message);
var speech = GetSpeechVerb(source, message);
2023-04-28 10:10:25 +06:00
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;
// Check for a speech verb override
if (nameEv.SpeechVerb != null && _prototypeManager.TryIndex<SpeechVerbPrototype>(nameEv.SpeechVerb, out var proto))
speech = proto;
2022-11-15 17:09:27 +13:00
}
name = FormattedMessage.EscapeText(name);
// color the name unless it's something like "the old man"
2024-01-26 21:31:54 -05:00
string coloredName = name;
if (!TryComp<GrammarComponent>(source, out var grammar) || grammar.ProperNoun == true)
2024-01-26 21:31:54 -05:00
coloredName = $"[color={GetNameColor(name)}]{name}[/color]";
2024-01-17 18:17:35 +07:00
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message",
2024-01-26 21:31:54 -05:00
("entityName", coloredName),
2024-01-17 18:17:35 +07:00
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
("fontType", speech.FontId),
("fontSize", speech.FontSize),
("message", FormattedMessage.EscapeText(message)));
2022-03-30 22:21:58 -07:00
2023-05-24 17:41:22 +06:00
//WD-EDIT
if (TryComp<VoiceOfGodComponent>(source, out var comp))
{
wrappedMessage = Loc.GetString(comp.ChatLoc,
("entityName", name),
("message", FormattedMessage.EscapeText(message)),
("color", comp.ChatColor)
);
}
//WD-EDIT
SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range);
2022-03-30 22:21:58 -07:00
var ev = new EntitySpokeEvent(source, message, originalMessage, null, null);
2022-11-15 17:09:27 +13:00
RaiseLocalEvent(source, ev, true);
// To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc.
// Also doesn't log if hideLog is true.
if (!HasComp<ActorComponent>(source) || hideLog)
return;
if (originalMessage == message)
{
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}.");
}
else
{
if (name != Name(source))
{
_adminLogger.Add(LogType.Chat, LogImpact.Low,
$"Say from {ToPrettyString(source):user} as {name}, original: {originalMessage}, transformed: {message}.");
}
else
{
_adminLogger.Add(LogType.Chat, LogImpact.Low,
$"Say from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}.");
}
}
2022-03-30 22:21:58 -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
{
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
2022-07-26 16:49:23 -07:00
return;
2022-03-30 22:21:58 -07: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);
// get the entity's name by visual identity (if no override provided).
var nameIdentity = FormattedMessage.EscapeText(nameOverride ?? Identity.Name(source, EntityManager));
// 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
// color the name unless it's something like "the old man"
if (!TryComp<GrammarComponent>(source, out var grammar) || grammar.ProperNoun == true)
name = $"[color={GetNameColor(name)}]{name}[/color]";
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
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
foreach (var (session, data) in GetRecipients(source, WhisperMuffledRange))
2022-03-30 22:21:58 -07:00
{
if (session.AttachedEntity is not { Valid: true })
2022-03-30 22:21:58 -07:00
continue;
var listener = session.AttachedEntity.Value;
2022-03-30 22:21:58 -07: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
if (data.Range <= WhisperClearRange)
_chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel);
//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
_chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel);
//If listener is too far and has no line of sight, they can't identify the whisperer's identity
else
_chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedUnknownMessage, source, false, session.Channel);
2022-03-30 22:21:58 -07:00
}
_replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range)));
2022-11-23 00:52:19 +13:00
var ev = new EntitySpokeEvent(source, message, originalMessage, channel, obfuscatedMessage);
2022-11-15 17:09:27 +13:00
RaiseLocalEvent(source, ev, true);
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))
2023-05-23 11:12:30 -07:00
{
_adminLogger.Add(LogType.Chat, LogImpact.Low,
$"Whisper from {ToPrettyString(source):user} as {name}, original: {originalMessage}, transformed: {message}.");
2023-05-23 11:12:30 -07:00
}
else
2023-05-23 11:12:30 -07:00
{
_adminLogger.Add(LogType.Chat, LogImpact.Low,
$"Whisper from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}.");
2023-05-23 11:12:30 -07:00
}
}
2022-03-30 22:21:58 -07:00
}
private void SendEntityEmote(
EntityUid source,
string action,
ChatTransmitRange range,
string? nameOverride,
bool hideLog = false,
bool checkEmote = true,
bool ignoreActionBlocker = false,
NetUserId? author = null
)
2022-03-30 22:21:58 -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).
var ent = Identity.Entity(source, EntityManager);
var name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
2022-07-13 22:23:55 -07:00
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
("entityName", name),
("entity", ent),
("message", FormattedMessage.RemoveMarkup(action)));
2022-03-30 22:21:58 -07:00
if (checkEmote)
TryEmoteChatInput(source, action);
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
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}");
//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
};
_pandaWeb.SendBotPostMessage(utkaEmoteEvent);
//WD-EDIT
2022-03-30 22:21:58 -07:00
}
// ReSharper disable once InconsistentNaming
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
2022-03-30 22:21:58 -07: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
{
if (!AdminLoocEnabled)
return;
}
else if (!_loocEnabled)
{
return;
2022-03-30 22:21:58 -07:00
}
// If crit player LOOC is disabled, don't send the message at all.
if (!_critLoocEnabled && _mobStateSystem.IsCritical(source))
return;
var wrappedMessage = Loc.GetString("chat-manager-entity-looc-wrap-message",
("entityName", name),
("message", FormattedMessage.EscapeText(message)));
2022-03-30 22:21:58 -07:00
SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, player.UserId);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}");
2022-03-30 22:21:58 -07: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);
string wrappedMessage;
2023-07-16 11:32:42 +03:00
if (_adminManager.IsAdmin(player) &&
_netConfigurationManager.GetClientCVar(player.ConnectedClient, WhiteCVars.DeadChatAdmin)) // WD
2022-03-30 22:21:58 -07: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")),
("userName", player.Channel.UserName),
("message", FormattedMessage.EscapeText(message)));
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Admin dead chat from {player:Player}: {message}");
2022-03-30 22:21:58 -07:00
}
else
{
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")),
("playerName", (playerName)),
("message", FormattedMessage.EscapeText(message)));
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Dead chat from {player:Player}: {message}");
2022-03-30 22:21:58 -07: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
/// <summary>
/// Returns the chat name color for a mob
/// </summary>
/// <param name="name">Name of the mob</param>
/// <returns>Hex value of the color</returns>
public string GetNameColor(string name)
{
var colorIdx = Math.Abs(name.GetHashCode() % _chatNameColors.Length);
return _chatNameColors[colorIdx];
}
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;
}
2022-03-30 22:21:58 -07:00
/// <summary>
/// Sends a chat message to the given players in range of the source entity.
/// </summary>
public 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))
{
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);
2022-11-15 17:09:27 +13:00
}
2022-11-23 00:52:19 +13: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>
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
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);
if(sanitizeSlang)
newMessage = _sanitizer.SanitizeOutSlang(newMessage);
2022-04-24 20:15:40 -04:00
if (capitalize)
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;
}
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;
}
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()
.AddWhereAttachedEntity(HasComp<GhostComponent>)
2022-03-30 22:21:58 -07:00
.Recipients
.Union(_adminManager.ActiveAdmins)
.Select(p => p.Channel);
2022-03-30 22:21:58 -07: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;
}
[ValidatePrototypeId<ReplacementAccentPrototype>]
public const string ChatSanitizeAccent = "chatsanitize";
public string SanitizeMessageReplaceWords(string message)
{
if (string.IsNullOrEmpty(message))
return message;
var msg = message;
msg = _wordreplacement.ApplyReplacements(msg, ChatSanitizeAccent);
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>
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)
{
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
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
RaiseLocalEvent(new ExpandICChatRecipientstEvent(source, voiceGetRange, recipients));
2022-11-15 17:09:27 +13:00
return recipients;
}
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();
}
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();
}
2022-03-30 22:21:58 -07:00
#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>
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 string? SpeechVerb;
2022-09-28 19:22:27 -07:00
public TransformSpeakerNameEvent(EntityUid sender, string name, string? speechVerb = null)
2022-09-28 19:22:27 -07:00
{
Sender = sender;
Name = name;
SpeechVerb = speechVerb;
2022-09-28 19:22:27 -07:00
}
}
2022-03-30 22:21:58 -07:00
/// <summary>
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;
}
}
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;
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
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;
OriginalMessage = originalMessage;
2022-11-15 17:09:27 +13:00
Channel = channel;
ObfuscatedMessage = obfuscatedMessage;
2022-03-30 22:21:58 -07: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;
}
}
//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
}
/// <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
}