CatStation/Content.Server/Corvax/TTS/TTSSystem.cs
2023-09-13 20:29:59 +03:00

142 lines
5.2 KiB
C#

using System.Threading.Tasks;
using Content.Server.Chat.Systems;
using Content.Shared.Corvax.CCCVars;
using Content.Shared.Corvax.TTS;
using Content.Shared.GameTicking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Corvax.TTS;
// ReSharper disable once InconsistentNaming
public sealed partial class TTSSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IServerNetManager _netMgr = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TTSManager _ttsManager = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
private const int MaxMessageChars = 100 * 2; // same as SingleBubbleCharLimit * 2
private bool _isEnabled = false;
public override void Initialize()
{
_cfg.OnValueChanged(CCCVars.TTSEnabled, v => _isEnabled = v, true);
SubscribeLocalEvent<TransformSpeechEvent>(OnTransformSpeech);
SubscribeLocalEvent<TTSComponent, EntitySpokeEvent>(OnEntitySpoke);
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
_netMgr.RegisterNetMessage<MsgRequestTTS>(OnRequestTTS);
}
private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
{
_ttsManager.ResetCache();
}
private async void OnRequestTTS(MsgRequestTTS ev)
{
if (!_isEnabled ||
ev.Text.Length > MaxMessageChars ||
!_playerManager.TryGetSessionByChannel(ev.MsgChannel, out var session) ||
!_prototypeManager.TryIndex<TTSVoicePrototype>(ev.VoiceId, out var protoVoice))
return;
var soundData = await GenerateTTS(ev.Text, protoVoice.Speaker);
if (soundData is null) return;
RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(ev.Uid), soundData), Filter.SinglePlayer(session));
}
private async void OnEntitySpoke(EntityUid uid, TTSComponent component, EntitySpokeEvent args)
{
var voiceId = component.VoicePrototypeId;
if (!_isEnabled ||
args.Message.Length > MaxMessageChars ||
voiceId == null)
return;
var voiceEv = new TransformSpeakerVoiceEvent(uid, voiceId);
RaiseLocalEvent(uid, voiceEv);
voiceId = voiceEv.VoiceId;
if (!_prototypeManager.TryIndex<TTSVoicePrototype>(voiceId, out var protoVoice))
return;
if (args.ObfuscatedMessage != null)
{
HandleWhisper(uid, args.Message, args.ObfuscatedMessage, protoVoice.Speaker);
return;
}
HandleSay(uid, args.Message, protoVoice.Speaker);
}
private async void HandleSay(EntityUid uid, string message, string speaker)
{
var soundData = await GenerateTTS(message, speaker);
if (soundData is null) return;
RaiseNetworkEvent(new PlayTTSEvent(GetNetEntity(uid), soundData), Filter.Pvs(uid));
}
private async void HandleWhisper(EntityUid uid, string message, string obfMessage, string speaker)
{
var fullSoundData = await GenerateTTS(message, speaker, true);
if (fullSoundData is null) return;
var obfSoundData = await GenerateTTS(obfMessage, speaker, true);
if (obfSoundData is null) return;
var fullTtsEvent = new PlayTTSEvent(GetNetEntity(uid), fullSoundData, true);
var obfTtsEvent = new PlayTTSEvent(GetNetEntity(uid), obfSoundData, true);
// TODO: Check obstacles
var xformQuery = GetEntityQuery<TransformComponent>();
var sourcePos = _xforms.GetWorldPosition(xformQuery.GetComponent(uid), xformQuery);
var receptions = Filter.Pvs(uid).Recipients;
foreach (var session in receptions)
{
if (!session.AttachedEntity.HasValue) continue;
var xform = xformQuery.GetComponent(session.AttachedEntity.Value);
var distance = (sourcePos - _xforms.GetWorldPosition(xform, xformQuery)).Length();
if (distance > ChatSystem.VoiceRange * ChatSystem.VoiceRange)
continue;
RaiseNetworkEvent(distance > ChatSystem.WhisperClearRange ? obfTtsEvent : fullTtsEvent, session);
}
}
// ReSharper disable once InconsistentNaming
private async Task<byte[]?> GenerateTTS(string text, string speaker, bool isWhisper = false)
{
var textSanitized = Sanitize(text);
if (textSanitized == "") return null;
if (char.IsLetter(textSanitized[^1]))
textSanitized += ".";
var ssmlTraits = SoundTraits.RateFast;
if (isWhisper)
ssmlTraits = SoundTraits.PitchVerylow;
var textSsml = ToSsmlText(textSanitized, ssmlTraits);
return await _ttsManager.ConvertTextToSpeech(speaker, textSsml);
}
}
public sealed class TransformSpeakerVoiceEvent : EntityEventArgs
{
public EntityUid Sender;
public string VoiceId;
public TransformSpeakerVoiceEvent(EntityUid sender, string voiceId)
{
Sender = sender;
VoiceId = voiceId;
}
}