From 90870e609a0496f6b0b8962efc8ab991f296dc79 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:55:33 +0300 Subject: [PATCH] Implement cyborg TTS voice changing with robot effect and sponsor voice validation (#2936) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: VigersRay <60344369+VigersRay@users.noreply.github.com> Co-authored-by: Vigers Ray --- .../Borgs/BorgVoiceBoundUserInterface.cs | 53 ++++++ .../Silicons/Borgs/BorgVoiceWindow.cs | 85 ++++++++++ .../Silicons/Borgs/BorgVoiceWindow.xaml | 23 +++ Content.Client/_Sunrise/TTS/TTSSystem.cs | 76 +++++---- .../Silicons/Borgs/BorgVoiceSystem.cs | 157 ++++++++++++++++++ .../AnnouncementSpeakerSystem.cs | 9 +- Content.Server/_Sunrise/TTS/TTSManager.cs | 36 ---- Content.Server/_Sunrise/TTS/TTSSystem.cs | 38 ++--- .../_Sunrise/InnateItem/InnateItemSystem.cs | 11 +- .../_Sunrise/TTS/BorgVoiceChangeEvent.cs | 47 ++++++ .../_Sunrise/TTS/BorgVoiceComponent.cs | 22 +++ .../en-US/_strings/_sunrise/borg-voice.ftl | 9 + .../ru-RU/_strings/_sunrise/borg-voice.ftl | 13 ++ .../Mobs/Cyborgs/base_borg_chassis.yml | 4 + .../_Sunrise/Actions/borg_actions.yml | 15 ++ 15 files changed, 508 insertions(+), 90 deletions(-) create mode 100644 Content.Client/Silicons/Borgs/BorgVoiceBoundUserInterface.cs create mode 100644 Content.Client/Silicons/Borgs/BorgVoiceWindow.cs create mode 100644 Content.Client/Silicons/Borgs/BorgVoiceWindow.xaml create mode 100644 Content.Server/Silicons/Borgs/BorgVoiceSystem.cs create mode 100644 Content.Shared/_Sunrise/TTS/BorgVoiceChangeEvent.cs create mode 100644 Content.Shared/_Sunrise/TTS/BorgVoiceComponent.cs create mode 100644 Resources/Locale/en-US/_strings/_sunrise/borg-voice.ftl create mode 100644 Resources/Locale/ru-RU/_strings/_sunrise/borg-voice.ftl diff --git a/Content.Client/Silicons/Borgs/BorgVoiceBoundUserInterface.cs b/Content.Client/Silicons/Borgs/BorgVoiceBoundUserInterface.cs new file mode 100644 index 0000000000..00242099c5 --- /dev/null +++ b/Content.Client/Silicons/Borgs/BorgVoiceBoundUserInterface.cs @@ -0,0 +1,53 @@ +using Content.Client._Sunrise.TTS; +using Content.Shared._Sunrise.TTS; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; + +namespace Content.Client.Silicons.Borgs; + +public sealed class BorgVoiceBoundUserInterface : BoundUserInterface +{ + private BorgVoiceWindow? _window; + + public BorgVoiceBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _window = this.CreateWindow(); + _window.OnVoiceSelected += OnVoiceSelected; + _window.OnVoicePreview += OnVoicePreview; + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not BorgVoiceChangeState borgState || _window == null) + return; + + _window.UpdateState(borgState); + } + + private void OnVoiceSelected(string voiceId) + { + SendMessage(new BorgVoiceChangeMessage(voiceId)); + } + + private void OnVoicePreview(string voiceId) + { + EntMan.System().RequestPreviewTts(voiceId); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + + _window?.Dispose(); + } +} diff --git a/Content.Client/Silicons/Borgs/BorgVoiceWindow.cs b/Content.Client/Silicons/Borgs/BorgVoiceWindow.cs new file mode 100644 index 0000000000..bbc786d440 --- /dev/null +++ b/Content.Client/Silicons/Borgs/BorgVoiceWindow.cs @@ -0,0 +1,85 @@ +using System.Linq; +using Content.Shared._Sunrise.TTS; +using Content.Shared.Humanoid; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Localization; +using Robust.Shared.Prototypes; + +namespace Content.Client.Silicons.Borgs; + +[GenerateTypedNameReferences] +public sealed partial class BorgVoiceWindow : DefaultWindow +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + public event Action? OnVoiceSelected; + public event Action? OnVoicePreview; + + private readonly List _voiceList = new(); + + public BorgVoiceWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + + Title = Loc.GetString("borg-voice-window-title"); + + LoadVoiceList(); + SetupButtons(); + } + + private void LoadVoiceList() + { + _voiceList.Clear(); + _voiceList.AddRange(_prototypeManager + .EnumeratePrototypes() + .Where(v => v.RoundStart) + .OrderBy(v => Loc.GetString(v.Name))); + } + + private void SetupButtons() + { + VoiceOptionButton.OnItemSelected += args => + { + VoiceOptionButton.SelectId(args.Id); + var voice = _voiceList[args.Id]; + OnVoiceSelected?.Invoke(voice.ID); + }; + + VoicePlayButton.OnPressed += _ => + { + if (VoiceOptionButton.SelectedId != null) + { + var voice = _voiceList[VoiceOptionButton.SelectedId]; + OnVoicePreview?.Invoke(voice.ID); + } + }; + } + + public void UpdateState(BorgVoiceChangeState state) + { + VoiceOptionButton.Clear(); + + var selectedIndex = 0; + for (var i = 0; i < _voiceList.Count; i++) + { + var voice = _voiceList[i]; + var name = Loc.GetString(voice.Name); + + VoiceOptionButton.AddItem(name, i); + + if (voice.ID == state.CurrentVoiceId) + { + selectedIndex = i; + } + } + + if (_voiceList.Count > 0) + { + VoiceOptionButton.SelectId(selectedIndex); + } + } +} diff --git a/Content.Client/Silicons/Borgs/BorgVoiceWindow.xaml b/Content.Client/Silicons/Borgs/BorgVoiceWindow.xaml new file mode 100644 index 0000000000..3b344b14f7 --- /dev/null +++ b/Content.Client/Silicons/Borgs/BorgVoiceWindow.xaml @@ -0,0 +1,23 @@ + + +