diff --git a/Content.Server/Communications/CommunicationsConsoleComponent.cs b/Content.Server/Communications/CommunicationsConsoleComponent.cs index e2aa0d7620..c34953971c 100644 --- a/Content.Server/Communications/CommunicationsConsoleComponent.cs +++ b/Content.Server/Communications/CommunicationsConsoleComponent.cs @@ -55,5 +55,10 @@ namespace Content.Server.Communications /// [DataField] public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Announcements/announce.ogg"); + + //WD-start + [DataField("ttsVoiceId")] + public string TtsVoiceId = "Glados"; + //WD-end } } diff --git a/Content.Server/Communications/CommunicationsConsoleSystem.cs b/Content.Server/Communications/CommunicationsConsoleSystem.cs index 9a645663e3..676c16a2ba 100644 --- a/Content.Server/Communications/CommunicationsConsoleSystem.cs +++ b/Content.Server/Communications/CommunicationsConsoleSystem.cs @@ -9,6 +9,7 @@ using Content.Server.RoundEnd; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Station.Systems; +using Content.Server.White.TTS; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; using Content.Shared.CCVar; @@ -287,6 +288,11 @@ namespace Content.Server.Communications } _chatSystem.DispatchStationAnnouncement(uid, msg, title, colorOverride: comp.Color); + //WD-start + var ttsEv = new TTSAnnouncementEvent(message.Message, comp.TtsVoiceId, uid, comp.Global); + RaiseLocalEvent(ttsEv); + //WD-end + if (message.Session.AttachedEntity != null) _adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(message.Session.AttachedEntity.Value):player} has sent the following station announcement: {msg}"); } diff --git a/Content.Server/White/TTS/TTSAnnouncementEvent.cs b/Content.Server/White/TTS/TTSAnnouncementEvent.cs new file mode 100644 index 0000000000..f82673b429 --- /dev/null +++ b/Content.Server/White/TTS/TTSAnnouncementEvent.cs @@ -0,0 +1,17 @@ +namespace Content.Server.White.TTS; + +public sealed class TTSAnnouncementEvent : EntityEventArgs +{ + public readonly string Message; + public readonly bool Global; + public readonly string VoiceId; + public readonly EntityUid Source; + + public TTSAnnouncementEvent(string message, string voiceId, EntityUid source , bool global) + { + Message = message; + Global = global; + VoiceId = voiceId; + Source = source; + } +} diff --git a/Content.Server/White/TTS/TTSSystem.cs b/Content.Server/White/TTS/TTSSystem.cs index 14f7676bf9..f233512477 100644 --- a/Content.Server/White/TTS/TTSSystem.cs +++ b/Content.Server/White/TTS/TTSSystem.cs @@ -1,8 +1,13 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Content.Server.Chat.Systems; +using Content.Server.Light.Components; +using Content.Server.Station.Components; +using Content.Server.Station.Systems; using Content.Shared.White.TTS; using Content.Shared.GameTicking; using Content.Shared.White; +using Robust.Server.Audio; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Network; @@ -24,6 +29,9 @@ public sealed partial class TTSSystem : EntitySystem [Dependency] private readonly IServerNetManager _netMgr = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly TTSPitchRateSystem _ttsPitchRateSystem = default!; + [Dependency] private readonly StationSystem _stationSystem = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; private const int MaxMessageChars = 100 * 2; // same as SingleBubbleCharLimit * 2 private bool _isEnabled = false; @@ -38,9 +46,73 @@ public sealed partial class TTSSystem : EntitySystem SubscribeLocalEvent(OnEntitySpoke); SubscribeLocalEvent(OnRoundRestartCleanup); + SubscribeLocalEvent(OnAnnounceRequest); + _netMgr.RegisterNetMessage(OnRequestTTS); } + private async void OnAnnounceRequest(TTSAnnouncementEvent ev) + { + if (!_prototypeManager.TryIndex(ev.VoiceId, out var ttsPrototype)) + return; + + var message = FormattedMessage.RemoveMarkup(ev.Message); + var soundData = await GenerateTTS(null, message, ttsPrototype.Speaker); + + if (soundData == null) + return; + + Filter filter; + if (ev.Global) + filter = Filter.Broadcast(); + else + { + var station = _stationSystem.GetOwningStation(ev.Source); + if (station == null) + return; + + if (!EntityManager.TryGetComponent(station, out var stationDataComp)) + return; + + filter = _stationSystem.GetInStation(stationDataComp); + } + + foreach (var player in filter.Recipients) + { + if (player.AttachedEntity != null) + { + // Get emergency lights in range to broadcast from + var entities = _lookup.GetEntitiesInRange(player.AttachedEntity.Value, 20f) + .Where(HasComp) + .ToList(); + + if (entities.Count == 0) + return; + + // Get closest emergency light + var entity = entities.First(); + var range = 100f; + + foreach (var item in entities) + { + var itemSource = _xforms.GetWorldPosition(Transform(item)); + var playerSource = _xforms.GetWorldPosition(Transform(player.AttachedEntity.Value)); + + var distance = playerSource.Length() - itemSource.Length(); + + if (range > distance) + { + range = distance; + entity = item; + } + } + + RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(entity), soundData), Filter.SinglePlayer(player), + false); + } + } + } + private async void OnRequestTTS(MsgRequestTTS ev) { var url = _cfg.GetCVar(WhiteCVars.TTSApiUrl); @@ -138,7 +210,7 @@ public sealed partial class TTSSystem : EntitySystem _ttsManager.ResetCache(); } - private async Task GenerateTTS(EntityUid uid, string text, string speaker, string? speechPitch = null, string? speechRate = null) + private async Task GenerateTTS(EntityUid? uid, string text, string speaker, string? speechPitch = null, string? speechRate = null) { var textSanitized = Sanitize(text); if (textSanitized == "") @@ -148,7 +220,7 @@ public sealed partial class TTSSystem : EntitySystem string rate; if (speechPitch == null || speechRate == null) { - if (!_ttsPitchRateSystem.TryGetPitchRate(uid, out var pitchRate)) + if (uid == null || !_ttsPitchRateSystem.TryGetPitchRate(uid.Value, out var pitchRate)) { pitch = "medium"; rate = "medium"; diff --git a/Content.Shared/White/TTS/TTSVoicePrototype.cs b/Content.Shared/White/TTS/TTSVoicePrototype.cs index c6655668a4..40cf501ecd 100644 --- a/Content.Shared/White/TTS/TTSVoicePrototype.cs +++ b/Content.Shared/White/TTS/TTSVoicePrototype.cs @@ -31,4 +31,7 @@ public sealed class TTSVoicePrototype : IPrototype [DataField("sponsorOnly")] public bool SponsorOnly { get; } = false; + + [DataField("borgVoice")] + public bool BorgVoice { get; } = false; } diff --git a/Resources/Locale/ru-RU/white/tts/tts-voices.ftl b/Resources/Locale/ru-RU/white/tts/tts-voices.ftl index a1d23158aa..05bf95882d 100644 --- a/Resources/Locale/ru-RU/white/tts/tts-voices.ftl +++ b/Resources/Locale/ru-RU/white/tts/tts-voices.ftl @@ -25,25 +25,17 @@ tts-voice-name-gman = G-Man tts-voice-name-obama = Обама tts-voice-name-trump = Трамп tts-voice-name-briman = Брин -tts-voice-name-kleiner-alt = Кляйнер (Альт.) tts-voice-name-father-grigori = Отец Григорий tts-voice-name-vance = Вэнс tts-voice-name-barni = Барни -tts-voice-name-gman-alt = G-Man (Альт.) tts-voice-name-alyx = Аликс tts-voice-name-mossman = Моссман tts-voice-name-bandit = Бандит -tts-voice-name-papich-alt = Папич (Альт.) -tts-voice-name-bebey-alt = Бэбэй (Альт.) -tts-voice-name-glados-alt = Гладос (Альт.) tts-voice-name-geralt = Геральт tts-voice-name-triss = Трисс tts-voice-name-kodlak-white-mane = Кодлак Белая Грива tts-voice-name-cicero = Цицерон tts-voice-name-sheogorath = Шеогорат -tts-voice-name-adventure-core-alt = Модуль Приключений (Альт.) -tts-voice-name-fact-core-alt = Модуль Фактов (Альт.) -tts-voice-name-space-core-alt = Модуль Космоса (Альт.) tts-voice-name-turret-floor = Турель tts-voice-name-cirilla = Цири tts-voice-name-lambert = Ламберт diff --git a/Resources/Prototypes/White/tts-voices.yml b/Resources/Prototypes/White/tts-voices.yml index fe312904a1..d79825a510 100644 --- a/Resources/Prototypes/White/tts-voices.yml +++ b/Resources/Prototypes/White/tts-voices.yml @@ -69,12 +69,15 @@ name: tts-voice-name-glados sex: Female speaker: glados + roundStart: false - type: ttsVoice id: Sentrybot name: tts-voice-name-sentrybot sex: Male speaker: sentrybot + roundStart: false + borgVoice: true - type: ttsVoice id: Mana @@ -105,18 +108,24 @@ name: tts-voice-name-adventure-core sex: Male speaker: adventure_core + roundStart: false + borgVoice: true - type: ttsVoice id: SpaceCore name: tts-voice-name-space-core sex: Male speaker: space_core + roundStart: false + borgVoice: true - type: ttsVoice id: FactCore name: tts-voice-name-fact-core sex: Male speaker: fact_core + roundStart: false + borgVoice: true - type: ttsVoice id: Kleiner @@ -160,12 +169,6 @@ sex: Male speaker: briman -- type: ttsVoice - id: KleinerAlt - name: tts-voice-name-kleiner-alt - sex: Male - speaker: kleiner_alt - - type: ttsVoice id: FatherGrigori name: tts-voice-name-father-grigori @@ -184,12 +187,6 @@ sex: Male speaker: barni -- type: ttsVoice - id: GmanAlt - name: tts-voice-name-gman-alt - sex: Male - speaker: gman_alt - - type: ttsVoice id: Alyx name: tts-voice-name-alyx @@ -208,24 +205,6 @@ sex: Male speaker: bandit -- type: ttsVoice - id: PapichAlt - name: tts-voice-name-papich-alt - sex: Male - speaker: papich_alt - -- type: ttsVoice - id: BebeyAlt - name: tts-voice-name-bebey-alt - sex: Male - speaker: bebey_alt - -- type: ttsVoice - id: GladosAlt - name: tts-voice-name-glados-alt - sex: Female - speaker: glados_alt - - type: ttsVoice id: Geralt name: tts-voice-name-geralt @@ -256,29 +235,13 @@ sex: Male speaker: sheogorath -- type: ttsVoice - id: AdventureCoreAlt - name: tts-voice-name-adventure-core-alt - sex: Male - speaker: adventure_core_alt - -- type: ttsVoice - id: FactCoreAlt - name: tts-voice-name-fact-core-alt - sex: Male - speaker: fact_core_alt - -- type: ttsVoice - id: SpaceCoreAlt - name: tts-voice-name-space-core-alt - sex: Male - speaker: space_core_alt - - type: ttsVoice id: TurretFloor name: tts-voice-name-turret-floor sex: Female speaker: turret_floor + roundStart: false + borgVoice: true - type: ttsVoice id: Cirilla