Очередь озвучки анонсов, фиксы багов и оптимизация генерации ттсов. (#35)

* Очередь на озвучку оповещений

* Фикс ошибок клиента и оптимизация ттса

* Укоротил сообщения о кодах

* чейнжлог
This commit is contained in:
Vigers Ray 2024-06-07 11:04:08 +03:00 committed by GitHub
parent 5137a7f93c
commit 72affc5f7a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 132 additions and 82 deletions

View file

@ -3,6 +3,7 @@ using Content.Shared._Sunrise.TTS;
using Robust.Client.Audio;
using Robust.Client.ResourceManagement;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
@ -25,9 +26,8 @@ public sealed class TTSSystem : EntitySystem
private ISawmill _sawmill = default!;
private readonly MemoryContentRoot _contentRoot = new();
private static readonly ResPath Prefix = ResPath.Root / "TTS";
private static readonly AudioResource EmptyAudioResource = new();
private const float TTSVolume = 0f;
private const float TtsVolume = 0f;
private const float AnnounceVolume = 0f;
private float _volume;
@ -36,6 +36,9 @@ public sealed class TTSSystem : EntitySystem
private float _volumeAnnounce;
private EntityUid _announcementUid = EntityUid.Invalid;
private Queue<AnnounceTtsEvent> _announceQueue = new();
private (EntityUid Entity, AudioComponent Component)? _currentPlaying;
public override void Initialize()
{
_sawmill = Logger.GetSawmill("tts");
@ -83,12 +86,28 @@ public sealed class TTSSystem : EntitySystem
if (_volumeAnnounce == 0)
return;
_announceQueue.Enqueue(ev);
}
private void PlayNextInQueue()
{
if (_announceQueue.Count == 0)
{
return;
}
var ev = _announceQueue.Dequeue();
if (_announcementUid == EntityUid.Invalid)
_announcementUid = Spawn(null);
var finalParams = new AudioParams() {Volume = AnnounceVolume + SharedAudioSystem.GainToVolume(_volumeAnnounce)};
var finalParams = new AudioParams() { Volume = AnnounceVolume + SharedAudioSystem.GainToVolume(_volumeAnnounce) };
PlayTTSBytes(ev.Data, _announcementUid, finalParams, true);
if (ev.AnnouncementSound != null)
{
_currentPlaying = _audio.PlayGlobal(ev.AnnouncementSound, _announcementUid, finalParams.AddVolume(-5f));
}
_currentPlaying = PlayTTSBytes(ev.Data, _announcementUid, finalParams, true);
}
private void OnTtsRadioVolumeChanged(float volume)
@ -103,18 +122,24 @@ public sealed class TTSSystem : EntitySystem
if (volume == 0)
return;
volume = TTSVolume + SharedAudioSystem.GainToVolume(volume * ev.VolumeModifier);
volume = TtsVolume + SharedAudioSystem.GainToVolume(volume * ev.VolumeModifier);
var audioParams = AudioParams.Default.WithVolume(volume);
PlayTTSBytes(ev.Data, GetEntity(ev.SourceUid), audioParams);
var entity = GetEntity(ev.SourceUid);
PlayTTSBytes(ev.Data, entity, audioParams);
}
private void PlayTTSBytes(byte[] data, EntityUid? sourceUid = null, AudioParams? audioParams = null, bool globally = false)
private (EntityUid Entity, AudioComponent Component)? PlayTTSBytes(byte[] data, EntityUid? sourceUid = null, AudioParams? audioParams = null, bool globally = false)
{
_sawmill.Debug($"Play TTS audio {data.Length} bytes from {sourceUid} entity");
if (data.Length == 0)
return;
return null;
// если sourceUid.Value.Id == 0 то значит эта сущность не прогружена на стороне клиента
if ((sourceUid == null || sourceUid.Value.Id == 0) && !globally)
return null;
_sawmill.Debug($"Play TTS audio {data.Length} bytes from {sourceUid} entity");
var finalParams = audioParams ?? AudioParams.Default;
@ -125,27 +150,41 @@ public sealed class TTSSystem : EntitySystem
res.Load(_dependencyCollection, Prefix / filePath);
_resourceCache.CacheResource(Prefix / filePath, res);
if (sourceUid != null)
{
_audio.PlayEntity(res.AudioStream, sourceUid.Value, finalParams);
}
else
{
_audio.PlayGlobal(res.AudioStream, finalParams);
}
(EntityUid Entity, AudioComponent Component)? playing;
if (globally)
_audio.PlayGlobal(res.AudioStream, finalParams);
{
playing = _audio.PlayGlobal(res.AudioStream, finalParams);
}
else
{
if (sourceUid == null)
_audio.PlayGlobal(res.AudioStream, finalParams);
else
_audio.PlayEntity(res.AudioStream, sourceUid.Value, finalParams);
playing = sourceUid == null ? null : _audio.PlayEntity(res.AudioStream, sourceUid.Value, finalParams);
}
_contentRoot.RemoveFile(filePath);
_fileIdx++;
return playing;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (_currentPlaying.HasValue)
{
var (entity, component) = _currentPlaying.Value;
if (Deleted(entity))
{
_currentPlaying = null;
}
else
{
return;
}
}
PlayNextInQueue();
}
}

View file

@ -175,21 +175,18 @@ public sealed class AlertLevelSystem : EntitySystem
var playDefault = false;
if (playSound)
{
if (detail.Sound != null)
{
var filter = _stationSystem.GetInOwningStation(station);
_audio.PlayGlobal(detail.Sound, filter, true, detail.Sound.Params);
}
else
{
if (detail.Sound == null)
playDefault = true;
}
}
if (announce)
{
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playSound: playDefault,
colorOverride: detail.Color, sender: stationName);
_chatSystem.DispatchStationAnnouncement(station,
announcementFull,
announcementSound: detail.Sound, // Sunrise-edit,
playDefault: playDefault,
colorOverride: detail.Color,
sender: stationName);
}
RaiseLocalEvent(new AlertLevelChangedEvent(station, level));

View file

@ -241,7 +241,7 @@ public sealed class CryostorageSystem : SharedCryostorageSystem
("character", name),
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))
), Loc.GetString("earlyleave-cryo-sender"),
playSound: false
playDefault: false
);
}

View file

@ -17,6 +17,6 @@ public sealed class AnnounceOnSpawnSystem : EntitySystem
{
var message = Loc.GetString(comp.Message);
var sender = comp.Sender != null ? Loc.GetString(comp.Sender) : "Central Command";
_chat.DispatchGlobalAnnouncement(message, sender, playSound: true, announcementSound: comp.Sound, colorOverride: comp.Color);
_chat.DispatchGlobalAnnouncement(message, sender, playDefault: true, announcementSound: comp.Sound, colorOverride: comp.Color);
}
}

View file

@ -66,7 +66,6 @@ public sealed partial class ChatSystem : SharedChatSystem
// public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
// Sunrise-TTS-End
public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg"; // Sunrise-edit
public const string NukeAnnouncementSound = "/Audio/Announcements/war.ogg"; // Sunrise-edit
private bool _loocEnabled = true;
private bool _deadLoocEnabled;
@ -319,7 +318,7 @@ public sealed partial class ChatSystem : SharedChatSystem
public void DispatchGlobalAnnouncement(
string message,
string sender = "Центральное коммандование", // Sunrise-edit
bool playSound = true,
bool playDefault = true,
SoundSpecifier? announcementSound = null,
bool playTts = true, // Sunrise-edit
Color? colorOverride = null
@ -329,20 +328,15 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToAll(ChatChannel.Radio, message, wrappedMessage, default, false, true, colorOverride);
// Sunrise-start
if (playSound)
if (playDefault && announcementSound == null)
{
if (sender == Loc.GetString("comms-console-announcement-title-nukie"))
{
announcementSound = new SoundPathSpecifier(NukeAnnouncementSound); // Sunrise-edit
}
announcementSound ??= new SoundPathSpecifier(DefaultAnnouncementSound);
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
}
if (playTts)
{
var nukie = sender == Loc.GetString("comms-console-announcement-title-nukie");
var announcementEv = new AnnouncementSpokeEvent(Filter.Broadcast(), message, nukie);
var announcementEv = new AnnouncementSpokeEvent(Filter.Broadcast(), message, announcementSound, nukie);
RaiseLocalEvent(announcementEv);
}
// Sunrise-end
@ -364,9 +358,10 @@ public sealed partial class ChatSystem : SharedChatSystem
EntityUid source,
string message,
string sender = "Центральное коммандование", // Sunrise-edit
bool playSound = true, // Sunrise-edit
bool playDefault = true, // Sunrise-edit
bool playTts = true,// Sunrise-edit
Color? colorOverride = null)
Color? colorOverride = null,
SoundSpecifier? announcementSound = null)
{
var wrappedMessage = Loc.GetString("chat-manager-sender-announcement-wrap-message", ("sender", sender), ("message", FormattedMessage.EscapeText(message)));
var station = _stationSystem.GetOwningStation(source);
@ -384,15 +379,12 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, wrappedMessage, source, false, true, colorOverride);
// Sunrise-start
if (playSound)
{
var announcementSound = new SoundPathSpecifier(DefaultAnnouncementSound);
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
}
if (playDefault && announcementSound == null)
announcementSound = new SoundPathSpecifier(DefaultAnnouncementSound);
if (playTts)
{
RaiseLocalEvent(new AnnouncementSpokeEvent(filter, message));
RaiseLocalEvent(new AnnouncementSpokeEvent(filter, message, announcementSound));
}
// Sunrise-edit
@ -1013,12 +1005,14 @@ public enum ChatTransmitRange : byte
public sealed class AnnouncementSpokeEvent(
Filter source,
string message,
SoundSpecifier? announcementSound,
bool nukie = false)
: EntityEventArgs
{
public readonly Filter Source = source;
public readonly string Message = message;
public readonly bool Nukie = nukie;
public readonly SoundSpecifier? AnnouncementSound = announcementSound;
}
public sealed class RadioSpokeEvent(EntityUid source, string message, EntityUid[] receivers) : EntityEventArgs

View file

@ -79,7 +79,7 @@ public sealed class CommsHackerSystem : SharedCommsHackerSystem
public void CallInThreat(NinjaHackingThreatPrototype ninjaHackingThreat)
{
_gameTicker.StartGameRule(ninjaHackingThreat.Rule, out _);
_chat.DispatchGlobalAnnouncement(Loc.GetString(ninjaHackingThreat.Announcement), playSound: true, playTts: true, colorOverride: Color.Red);
_chat.DispatchGlobalAnnouncement(Loc.GetString(ninjaHackingThreat.Announcement), playDefault: true, playTts: true, colorOverride: Color.Red);
}
}

View file

@ -46,7 +46,7 @@ public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSys
// main damage with this is existing arrest warrants are lost and to anger beepsky
}
_chat.DispatchGlobalAnnouncement(Loc.GetString(ent.Comp.Announcement), playSound: true, colorOverride: Color.Red);
_chat.DispatchGlobalAnnouncement(Loc.GetString(ent.Comp.Announcement), playDefault: true, colorOverride: Color.Red);
// once is enough
RemComp<CriminalRecordsHackerComponent>(ent);

View file

@ -72,7 +72,7 @@ public sealed class DragonRiftSystem : EntitySystem
var msg = Loc.GetString("carp-rift-warning",
("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, xform)))));
_chat.DispatchGlobalAnnouncement(msg, playSound: false, playTts: true, colorOverride: Color.Red);
_chat.DispatchGlobalAnnouncement(msg, playDefault: false, playTts: true, colorOverride: Color.Red);
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
_navMap.SetBeaconEnabled(uid, true);
}

View file

@ -637,7 +637,7 @@ namespace Content.Server.GameTicking
var proto = _robustRandom.Pick(options);
if (proto.Message != null)
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString(proto.Message), playSound: true, playTts: false);
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString(proto.Message), playDefault: true, playTts: false);
if (proto.Sound != null)
_audio.PlayGlobal(proto.Sound, Filter.Broadcast(), true);

View file

@ -248,7 +248,7 @@ namespace Content.Server.GameTicking
("gender", character.Gender), // Russian-LastnameGender
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(jobName))),
Loc.GetString("latejoin-arrival-sender"),
playSound: false);
playDefault: false);
}
if (player.UserId == new Guid("{e887eb93-f503-4b65-95b6-2f282c014192}"))

View file

@ -463,7 +463,7 @@ public sealed class NukeSystem : EntitySystem
("time", (int) component.RemainingTime),
("location", FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((uid, nukeXform)))));
var sender = Loc.GetString("nuke-component-announcement-sender");
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, playSound: false, colorOverride: Color.Red);
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, playDefault: false, colorOverride: Color.Red);
_sound.PlayGlobalOnStation(uid, _audio.GetSound(component.ArmSound));
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;

View file

@ -71,7 +71,7 @@ public sealed class WarDeclaratorSystem : EntitySystem
if (ev.Status == WarConditionStatus.WarReady)
{
var title = Loc.GetString(ent.Comp.SenderTitle);
_chat.DispatchGlobalAnnouncement(ent.Comp.Message, title, playSound: true, ent.Comp.Sound, colorOverride: ent.Comp.Color);
_chat.DispatchGlobalAnnouncement(ent.Comp.Message, title, playDefault: true, ent.Comp.Sound, colorOverride: ent.Comp.Color);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"{ToPrettyString(args.Actor):player} has declared war with this text: {ent.Comp.Message}");
}

View file

@ -129,7 +129,7 @@ namespace Content.Server.PowerSink
_chat.DispatchStationAnnouncement(
station.Value,
Loc.GetString("powersink-immiment-explosion-announcement"),
playSound: true,
playDefault: true,
colorOverride: Color.Yellow
);
}

View file

@ -314,7 +314,7 @@ public sealed partial class EmergencyShuttleSystem
if (remaining > 0)
_chatSystem.DispatchGlobalAnnouncement(
Loc.GetString("emergency-shuttle-console-auth-left", ("remaining", remaining)),
playSound: false, colorOverride: DangerColor);
playDefault: false, colorOverride: DangerColor);
if (!CheckForLaunch(component))
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), recordReplay: true);
@ -418,7 +418,7 @@ public sealed partial class EmergencyShuttleSystem
_announced = true;
_chatSystem.DispatchGlobalAnnouncement(
Loc.GetString("emergency-shuttle-launch-time", ("consoleAccumulator", $"{_consoleAccumulator:0}")),
playSound: false,
playDefault: false,
colorOverride: DangerColor);
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), recordReplay: true);

View file

@ -38,6 +38,7 @@ using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.GameTicking;
using Robust.Shared.Audio;
namespace Content.Server.Shuttles.Systems;
@ -283,6 +284,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
}
var targetGrid = _station.GetLargestGrid(Comp<StationDataComponent>(stationUid));
var announcementSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
// Sunrise-start
DockTime = _timing.CurTime;
@ -292,9 +294,8 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
if (targetGrid == null)
{
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}");
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"), playSound: false);
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"), announcementSound: announcementSound); // Sunrise-edit
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
return;
}
@ -307,7 +308,7 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playSound: false);
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playDefault: false);
}
// shuttle timers
@ -336,12 +337,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem
var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery);
var direction = ContentLocalizationManager.FormatDirection(angle.GetDir());
var location = FormattedMessage.RemoveMarkup(_navMap.GetNearestBeaconString((stationShuttle.EmergencyShuttle.Value, xform)));
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), playSound: false);
_chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("time", $"{_consoleAccumulator:0}"), ("direction", direction), ("location", location)), announcementSound: announcementSound); // Sunrise-Edit
}
_logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}");
// TODO: Need filter extensions or something don't blame me.
_audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
}
}

View file

@ -60,7 +60,7 @@ public sealed class RandomSentienceRule : StationEventSystem<RandomSentienceRule
("kind1", kind1), ("kind2", kind2), ("kind3", kind3), ("amount", groupList.Count),
("data", Loc.GetString($"random-sentience-event-data-{RobustRandom.Next(1, 6)}")),
("strength", Loc.GetString($"random-sentience-event-strength-{RobustRandom.Next(1, 8)}"))),
playSound: false,
playDefault: false,
colorOverride: Color.Gold
);
}

View file

@ -42,7 +42,7 @@ public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : ICompo
AdminLogManager.Add(LogType.EventAnnounced, $"Event added / announced: {ToPrettyString(uid)}");
if (stationEvent.StartAnnouncement != null)
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playSound: false, colorOverride: stationEvent.StartAnnouncementColor);
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.StartAnnouncement), playDefault: false, colorOverride: stationEvent.StartAnnouncementColor);
Audio.PlayGlobal(stationEvent.StartAudio, Filter.Broadcast(), true);
}
@ -78,7 +78,7 @@ public abstract class StationEventSystem<T> : GameRuleSystem<T> where T : ICompo
AdminLogManager.Add(LogType.EventStopped, $"Event ended: {ToPrettyString(uid)}");
if (stationEvent.EndAnnouncement != null)
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playSound: false, colorOverride: stationEvent.EndAnnouncementColor);
ChatSystem.DispatchGlobalAnnouncement(Loc.GetString(stationEvent.EndAnnouncement), playDefault: false, colorOverride: stationEvent.EndAnnouncementColor);
Audio.PlayGlobal(stationEvent.EndAudio, Filter.Broadcast(), true);
}

View file

@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Chat.Systems;
using Content.Shared._Sunrise.SunriseCCVars;
@ -121,14 +122,11 @@ public sealed partial class TTSSystem : EntitySystem
if (!_isEnabled ||
args.Message.Length > MaxMessageChars * 2 ||
!GetVoicePrototype(args.Nukie ? _nukieVoiceId : _voiceId, out var protoVoice))
{
RaiseNetworkEvent(new AnnounceTtsEvent(new byte[] { }), args.Source.RemovePlayers(_ignoredRecipients));
return;
}
var soundData = await GenerateTTS(args.Message, protoVoice.Speaker, isAnnounce: true);
soundData ??= new byte[] { };
RaiseNetworkEvent(new AnnounceTtsEvent(soundData), args.Source.RemovePlayers(_ignoredRecipients));
soundData ??= [];
RaiseNetworkEvent(new AnnounceTtsEvent(soundData, args.AnnouncementSound), args.Source.RemovePlayers(_ignoredRecipients));
}
private async void OnEntitySpoke(EntityUid uid, TTSComponent component, EntitySpokeEvent args)
@ -159,9 +157,20 @@ public sealed partial class TTSSystem : EntitySystem
private async void HandleSay(EntityUid uid, string message, string speaker)
{
var recipients = Filter.Pvs(uid, 1F).RemovePlayers(_ignoredRecipients);
// Если нету получаетей ттса то зачем вообще генерировать его?
if (!recipients.Recipients.Any())
return;
var soundData = await GenerateTTS(message, speaker);
if (soundData is null) return;
RaiseNetworkEvent(new PlayTTSEvent(soundData, GetNetEntity(uid)), Filter.Pvs(uid).RemovePlayers(_ignoredRecipients));
if (soundData is null)
return;
var netEntity = GetNetEntity(uid);
RaiseNetworkEvent(new PlayTTSEvent(soundData, netEntity), recipients);
}
private async void HandleWhisper(EntityUid uid, string message, string speaker, bool isRadio)

View file

@ -1,10 +1,12 @@
using Robust.Shared.Audio;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.TTS;
[Serializable, NetSerializable]
public sealed class AnnounceTtsEvent(byte[] data)
public sealed class AnnounceTtsEvent(byte[] data, SoundSpecifier? announcementSound)
: EntityEventArgs
{
public byte[] Data { get; } = data;
public SoundSpecifier? AnnouncementSound = announcementSound;
}

View file

@ -247,3 +247,12 @@ Entries:
type: Tweak
id: 21
time: '2024-06-05T16:26:42.210195+00:00'
- author: VigersRay
changes:
- message: "\u041E\u0437\u0432\u0443\u0447\u043A\u0430 \u043E\u043F\u043E\u0432\u0435\
\u0449\u0435\u043D\u0438\u0439 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435\
\ \u043D\u0430\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0435\u0442\u0441\u044F\
\ \u0434\u0440\u0443\u0433 \u043D\u0430 \u0434\u0440\u0443\u0433\u0430."
type: Fix
id: 22
time: '2024-06-07T08:01:08.686524+00:00'

View file

@ -5,19 +5,19 @@ alert-level-green = Зелёный
alert-level-green-announcement = Можно безопасно возвращаться на свои рабочие места.
alert-level-green-instructions = Выполняйте свою работу.
alert-level-blue = Синий
alert-level-blue-announcement = На станции присутствует неизвестная угроза. Службе безопасности разрешено проводить выборочные обыски. Членам экипажа рекомендуется выполнять указания, отдаваемые действующей властью. Для ускорения процедур, просим сотрудников проверить наличие ID-карт в своих КПК.
alert-level-blue-announcement = На станции присутствует неизвестная угроза. Службе безопасности разрешено проводить выборочные обыски. Для ускорения процедур, просим сотрудников проверить наличие ID-карт в своих КПК.
alert-level-blue-instructions = Каждый сотрудник обязан носить свою ID-карту в своём КПК. Также членам экипажа рекомендуется проявлять бдительность и сообщать службе безопасности o любой подозрительной активности.
alert-level-red = Красный
alert-level-red-announcement = На станции присутствует известная угроза. Служба безопасности имеет право применять летальную силу по необходимости. Все члены экипажа, за исключением должностных лиц, обязаны проследовать в свои отделы и ожидать дальнейших инструкций до отмены кода. Нарушители подлежат наказанию.
alert-level-red-announcement = На станции присутствует известная угроза. Служба безопасности имеет право применять летальную силу по необходимости. Все члены экипажа, за исключением должностных лиц, обязаны проследовать в свои отделы и ожидать дальнейших инструкций до отмены кода.
alert-level-red-instructions = Экипаж обязан подчиняться правомерным приказам сотрудников Службы Безопасности. Переключите режим работы своего костюма в режим "Координаты" и находитесь в своём отделе.
alert-level-violet = Фиолетовый
alert-level-violet-announcement = На станции присутствует угроза вируса. Медицинскому персоналу необходимо изолировать членов экипажа с любыми симптомами. Членам экипажа рекомендуется дистанцироваться друг от друга и соблюдать меры безопасности по предотвращению дальнейшего распространения вируса, следовать иным указаниям Главного Врача смены. На время действия Фиолетового Кода любые стыковки станции с другими объектами категорически запрещены. Сотрудники Службы Безопасности продолжают выполнение своих обязанностей по предыдущему коду.
alert-level-violet-announcement = На станции присутствует угроза вируса. Членам экипажа рекомендуется держать дистанцию между собой и соблюдать меры безопасности по предотвращению дальнейшего распространения вируса.
alert-level-violet-instructions = Членам экипажа рекомендуется держать дистанцию между собой и соблюдать меры безопасности по предотвращению дальнейшего распространения вируса. Если вы чувствуете себя плохо - вам следует незамедлительно пройти на обследование, надев заранее стерильную маску.
alert-level-yellow = Жёлтый
alert-level-yellow-announcement = На станции присутствует структурная или атмосферная угроза. Инженерно-техническому персоналу требуется немедленно предпринять меры по устранению угрозы. Всем остальным сотрудникам запрещено находиться в опасном участке. Сотрудники Службы Безопасности продолжают выполнение своих обязанностей по предыдущему коду.
alert-level-yellow-announcement = На станции присутствует структурная или атмосферная угроза. Инженерно-техническому персоналу требуется немедленно предпринять меры по устранению угрозы. Всем остальным сотрудникам запрещено находиться в опасном участке.
alert-level-yellow-instructions = Членам экипажа необходимо в срочном порядке покинуть опасную зону и, по возможности, оставаться на своих рабочих местах.
alert-level-gamma = Гамма
alert-level-gamma-announcement = Центральное командование объявило на станции уровень угрозы "Гамма". Служба безопасности должна постоянно иметь при себе оружие, гражданский персонал обязан немедленно обратиться к главам отделов для получения указаний к эвакуации. Службе Безопасности разрешено применение летальной силы в случае неповиновения.
alert-level-gamma-announcement = Центральное командование объявило на станции уровень угрозы "Гамма". Служба безопасности должна постоянно иметь при себе оружие, гражданский персонал обязан немедленно обратиться к главам отделов для получения указаний к эвакуации.
alert-level-gamma-instructions = Гражданский персонал обязан немедленно обратиться к главам отделов для получения указаний к эвакуации. Корпорация Nanotrasen заверяет вас - опасность скоро будет нейтрализована.
alert-level-delta = Дельта
alert-level-delta-announcement = Станция находится под угрозой неминуемого уничтожения. Членам экипажа рекомендуется слушать глав отделов для получения дополнительной информации. Службе Безопасности приказано работать по протоколу Дельта.