diff --git a/Content.Client/ADT/Language/LanguageMenuUIController.cs b/Content.Client/ADT/Language/LanguageMenuUIController.cs new file mode 100644 index 00000000000..791b616bb3d --- /dev/null +++ b/Content.Client/ADT/Language/LanguageMenuUIController.cs @@ -0,0 +1,51 @@ +using Content.Client.ADT.Language; +using Content.Client.Gameplay; +using Content.Shared.Language; +using Robust.Client.UserInterface.Controllers; +using Robust.Client.UserInterface.Controls; +using static Content.Shared.Language.Systems.SharedLanguageSystem; + +namespace Content.Client.ADT.Language; + +public sealed class LanguageMenuUIController : UIController, IOnStateEntered, IOnStateExited +{ + public LanguageMenuWindow? _languageWindow; + + public override void Initialize() + { + EntityManager.EventBus.SubscribeLocalEvent(OnActionMenu); + EntityManager.EventBus.SubscribeEvent(EventSource.Network, this, OnStateUpdate); + } + + private void OnStateUpdate(LanguageMenuStateMessage ev) + { + if (_languageWindow == null) + return; + + _languageWindow.UpdateState(ev); + } + + private void OnActionMenu(EntityUid uid, LanguageSpeakerComponent component, LanguageMenuActionEvent args) + { + if (_languageWindow == null) + return; + + if (!_languageWindow.IsOpen) + { + _languageWindow.Open(); + EntityManager.EntityNetManager?.SendSystemNetworkMessage(new RequestLanguageMenuStateMessage()); + } + } + + public void OnStateEntered(GameplayState state) + { + _languageWindow = UIManager.CreateWindow(); + LayoutContainer.SetAnchorPreset(_languageWindow, LayoutContainer.LayoutPreset.Center); + } + + public void OnStateExited(GameplayState state) + { + _languageWindow?.Dispose(); + _languageWindow = null; + } +} diff --git a/Content.Client/ADT/Language/LanguageMenuWindow.xaml b/Content.Client/ADT/Language/LanguageMenuWindow.xaml new file mode 100644 index 00000000000..188b3bc3b54 --- /dev/null +++ b/Content.Client/ADT/Language/LanguageMenuWindow.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/Content.Client/ADT/Language/LanguageMenuWindow.xaml.cs b/Content.Client/ADT/Language/LanguageMenuWindow.xaml.cs new file mode 100644 index 00000000000..0a795aebbd1 --- /dev/null +++ b/Content.Client/ADT/Language/LanguageMenuWindow.xaml.cs @@ -0,0 +1,124 @@ +using Content.Client.Language.Systems; +using Content.Shared.Language; +using Content.Shared.Language.Systems; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Console; +using Robust.Shared.Utility; +using static Content.Shared.Language.Systems.SharedLanguageSystem; + +namespace Content.Client.ADT.Language; // This EXACT class must have the _NF part because of xaml linking + +[GenerateTypedNameReferences] +public sealed partial class LanguageMenuWindow : DefaultWindow +{ + [Dependency] private readonly IConsoleHost _consoleHost = default!; + private readonly LanguageSystem _language; + + private readonly List _entries = new(); + + public LanguageMenuWindow() + { + RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); + _language = IoCManager.Resolve().GetEntitySystem(); + + Title = Loc.GetString("language-menu-window-title"); + } + + public void UpdateState(LanguageMenuStateMessage state) + { + var clanguage = _language.GetLanguage(state.CurrentLanguage); + CurrentLanguageLabel.Text = Loc.GetString("language-menu-current-language", ("language", clanguage?.LocalizedName ?? "")); + + OptionsList.RemoveAllChildren(); + _entries.Clear(); + + foreach (var language in state.Options) + { + AddLanguageEntry(language); + } + + // Disable the button for the currently chosen language + foreach (var entry in _entries) + { + if (entry.button != null) + entry.button.Disabled = entry.language == state.CurrentLanguage; + } + } + + private void AddLanguageEntry(string language) + { + var proto = _language.GetLanguage(language); + var state = new EntryState { language = language }; + + var container = new BoxContainer(); + container.Orientation = BoxContainer.LayoutOrientation.Vertical; + + // Create and add a header with the name and the button to select the language + { + var header = new BoxContainer(); + header.Orientation = BoxContainer.LayoutOrientation.Horizontal; + + header.Orientation = BoxContainer.LayoutOrientation.Horizontal; + header.HorizontalExpand = true; + header.SeparationOverride = 2; + + var name = new Label(); + name.Text = proto?.LocalizedName ?? ""; + name.MinWidth = 50; + name.HorizontalExpand = true; + + var button = new Button(); + button.Text = "Выбрать"; + button.OnPressed += _ => OnLanguageChosen(language); + state.button = button; + + header.AddChild(name); + header.AddChild(button); + + container.AddChild(header); + } + + // Create and add a collapsible description + { + var body = new CollapsibleBody(); + body.HorizontalExpand = true; + body.Margin = new Thickness(4f, 4f); + + var description = new RichTextLabel(); + description.SetMessage(proto?.LocalizedDescription ?? ""); + description.HorizontalExpand = true; + + body.AddChild(description); + + var collapser = new Collapsible(Loc.GetString("language-menu-description-header"), body); + collapser.Orientation = BoxContainer.LayoutOrientation.Vertical; + collapser.HorizontalExpand = true; + + container.AddChild(collapser); + } + + // Before adding, wrap the new container in a PanelContainer to give it a distinct look + var wrapper = new PanelContainer(); + wrapper.StyleClasses.Add("PdaBorderRect"); + + wrapper.AddChild(container); + OptionsList.AddChild(wrapper); + + _entries.Add(state); + } + + private void OnLanguageChosen(string id) + { + _consoleHost.ExecuteCommand("lsselectlang " + id); + } + + private struct EntryState + { + public string language; + public Button? button; + } +} diff --git a/Content.Client/ADT/Language/Systems/LanguageSystem.cs b/Content.Client/ADT/Language/Systems/LanguageSystem.cs new file mode 100644 index 00000000000..4d603a700a9 --- /dev/null +++ b/Content.Client/ADT/Language/Systems/LanguageSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Language.Systems; + +namespace Content.Client.Language.Systems; + +public sealed class LanguageSystem : SharedLanguageSystem +{ + +} diff --git a/Content.Client/ADT/Language/Systems/TranslatorImplanterSystem.cs b/Content.Client/ADT/Language/Systems/TranslatorImplanterSystem.cs new file mode 100644 index 00000000000..bbe28d24f5d --- /dev/null +++ b/Content.Client/ADT/Language/Systems/TranslatorImplanterSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Language.Systems; + +namespace Content.Client.Language.Systems; + +public sealed class TranslatorImplanterSystem : SharedTranslatorImplanterSystem +{ + +} diff --git a/Content.Server/ADT/Language/Commands/ListLanguagesCommand.cs b/Content.Server/ADT/Language/Commands/ListLanguagesCommand.cs new file mode 100644 index 00000000000..eda7030b9b7 --- /dev/null +++ b/Content.Server/ADT/Language/Commands/ListLanguagesCommand.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class ListLanguagesCommand : IConsoleCommand +{ + public string Command => "lslangs"; + public string Description => "List languages your current entity can speak at the current moment."; + public string Help => "lslangs"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not { } playerEntity) + { + shell.WriteError("You don't have an entity!"); + return; + } + + var languages = IoCManager.Resolve().GetEntitySystem(); + + var (spokenLangs, knownLangs) = languages.GetAllLanguages(playerEntity); + + shell.WriteLine("Spoken: " + string.Join(", ", spokenLangs)); + shell.WriteLine("Understood: " + string.Join(", ", knownLangs)); + } +} diff --git a/Content.Server/ADT/Language/Commands/SayLanguageCommand.cs b/Content.Server/ADT/Language/Commands/SayLanguageCommand.cs new file mode 100644 index 00000000000..711c2c5fbfb --- /dev/null +++ b/Content.Server/ADT/Language/Commands/SayLanguageCommand.cs @@ -0,0 +1,53 @@ +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class SayLanguageCommand : IConsoleCommand +{ + public string Command => "lsay"; + public string Description => "Send chat languages to the local channel or a specific chat channel, in a specific language."; + public string Help => "lsay "; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not { } playerEntity) + { + shell.WriteError("You don't have an entity!"); + return; + } + + if (args.Length < 2) + return; + + var languageId = args[0]; + var message = string.Join(" ", args, startIndex: 1, count: args.Length - 1).Trim(); + + if (string.IsNullOrEmpty(message)) + return; + + var languages = IoCManager.Resolve().GetEntitySystem(); + var chats = IoCManager.Resolve().GetEntitySystem(); + + var language = languages.GetLanguage(languageId); + if (language == null || !languages.CanSpeak(playerEntity, language.ID)) + { + shell.WriteError($"Language {languageId} is invalid or you cannot speak it!"); + return; + } + + chats.TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, ChatTransmitRange.Normal, false, shell, player, languageOverride: language); + } +} diff --git a/Content.Server/ADT/Language/Commands/SelectLanguageCommand.cs b/Content.Server/ADT/Language/Commands/SelectLanguageCommand.cs new file mode 100644 index 00000000000..72336747fab --- /dev/null +++ b/Content.Server/ADT/Language/Commands/SelectLanguageCommand.cs @@ -0,0 +1,48 @@ +using System.Linq; +using Content.Shared.Administration; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Language.Commands; + +[AnyCommand] +public sealed class SelectLanguageCommand : IConsoleCommand +{ + public string Command => "lsselectlang"; + public string Description => "Open a menu to select a langauge to speak."; + public string Help => "lsselectlang"; + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.Status != SessionStatus.InGame) + return; + + if (player.AttachedEntity is not { } playerEntity) + { + shell.WriteError("You don't have an entity!"); + return; + } + + if (args.Length < 1) + return; + + var languageId = args[0]; + + var languages = IoCManager.Resolve().GetEntitySystem(); + + var language = languages.GetLanguage(languageId); + if (language == null || !languages.CanSpeak(playerEntity, language.ID)) + { + shell.WriteError($"Language {languageId} is invalid or you cannot speak it!"); + return; + } + + languages.SetLanguage(playerEntity, language.ID); + } +} diff --git a/Content.Server/ADT/Language/LanguageSystem.Windows.cs b/Content.Server/ADT/Language/LanguageSystem.Windows.cs new file mode 100644 index 00000000000..52b5e7cbf46 --- /dev/null +++ b/Content.Server/ADT/Language/LanguageSystem.Windows.cs @@ -0,0 +1,40 @@ +using Content.Shared.Language; +using Robust.Server.GameObjects; +using Robust.Shared.Player; + +namespace Content.Server.Language; + +public sealed partial class LanguageSystem +{ + [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; + + public void InitializeWindows() + { + SubscribeNetworkEvent(OnLanguagesRequest); + SubscribeLocalEvent(OnLanguageSwitch); + } + + private void OnLanguagesRequest(RequestLanguageMenuStateMessage args, EntitySessionEventArgs session) + { + var uid = session.SenderSession.AttachedEntity; + if (uid == null) + return; + + var langs = GetLanguages(uid.Value); + if (langs == null) + return; + + var state = new LanguageMenuStateMessage(langs.CurrentLanguage, langs.SpokenLanguages); + RaiseNetworkEvent(state, uid.Value); + } + + private void OnLanguageSwitch(EntityUid uid, LanguageSpeakerComponent component, LanguagesUpdateEvent args) + { + var langs = GetLanguages(uid); + if (langs == null) + return; + + var state = new LanguageMenuStateMessage(langs.CurrentLanguage, langs.SpokenLanguages); + RaiseNetworkEvent(state, uid); + } +} diff --git a/Content.Server/ADT/Language/LanguageSystem.cs b/Content.Server/ADT/Language/LanguageSystem.cs new file mode 100644 index 00000000000..e76ce23cd36 --- /dev/null +++ b/Content.Server/ADT/Language/LanguageSystem.cs @@ -0,0 +1,319 @@ +using System.Linq; +using System.Text; +using Content.Shared.GameTicking; +using Content.Shared.Language; +using Content.Shared.Language.Systems; +using Robust.Shared.Random; +using Robust.Shared.Player; +using Robust.Server.GameObjects; +using UniversalLanguageSpeakerComponent = Content.Shared.Language.Components.UniversalLanguageSpeakerComponent; + +namespace Content.Server.Language; + +public sealed partial class LanguageSystem : SharedLanguageSystem +{ + /// + /// A random number added to each pseudo-random number's seed. Changes every round. + /// + public int RandomRoundSeed { get; private set; } + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInitLanguageSpeaker); + SubscribeAllEvent(it => RandomRoundSeed = _random.Next()); + SubscribeLocalEvent(OnUnknow); + + InitializeWindows(); + } + + private void OnInitLanguageSpeaker(EntityUid uid, LanguageSpeakerComponent component, ComponentInit args) + { + if (string.IsNullOrEmpty(component.CurrentLanguage)) + { + component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype); + } + } + + private void OnUnknow(EntityUid uid, UnknowLanguageComponent unknow, MapInitEvent args) + { + TryComp(uid, out var component); + + if (component != null) + { + component.SpokenLanguages.Remove(unknow.LanguageToForgot); + component.UnderstoodLanguages.Remove(unknow.LanguageToForgot); + component.CurrentLanguage = component.SpokenLanguages.FirstOrDefault(UniversalPrototype); + } + else + return; + + } + + /// + /// Obfuscate speech of the given entity, or using the given language. + /// + /// The speaker whose message needs to be obfuscated. Must not be null if "language" is not set. + /// The language for obfuscation. Must not be null if "source" is null. + public string ObfuscateSpeech(EntityUid? source, string message, LanguagePrototype? language = null) + { + if (language == null) + { + if (source is not { Valid: true }) + { + throw new NullReferenceException("Either source or language must be set."); + } + language = GetLanguage(source.Value); + } + + var builder = new StringBuilder(); + if (language.ObfuscateSyllables) + { + ObfuscateSyllables(builder, message, language); + } + else + { + ObfuscatePhrases(builder, message, language); + } + + //_sawmill.Info($"Got {message}, obfuscated to {builder}. Language: {language.ID}"); + + return builder.ToString(); + } + + private void ObfuscateSyllables(StringBuilder builder, string message, LanguagePrototype language) + { + // Go through each word. Calculate its hash sum and count the number of letters. + // Replicate it with pseudo-random syllables of pseudo-random (but similar) length. Use the hash code as the seed. + // This means that identical words will be obfuscated identically. Simple words like "hello" or "yes" in different langs can be memorized. + var wordBeginIndex = 0; + var hashCode = 0; + for (var i = 0; i < message.Length; i++) + { + var ch = char.ToLower(message[i]); + // A word ends when one of the following is found: a space, a sentence end, or EOM + if (char.IsWhiteSpace(ch) || IsSentenceEnd(ch) || i == message.Length - 1) + { + var wordLength = i - wordBeginIndex; + if (wordLength > 0) + { + var newWordLength = PseudoRandomNumber(hashCode, 1, 4); + + for (var j = 0; j < newWordLength; j++) + { + var index = PseudoRandomNumber(hashCode + j, 0, language.Replacement.Count); + builder.Append(language.Replacement[index]); + } + } + + builder.Append(ch); + hashCode = 0; + wordBeginIndex = i + 1; + } + else + { + hashCode = hashCode * 31 + ch; + } + } + } + + private void ObfuscatePhrases(StringBuilder builder, string message, LanguagePrototype language) + { + // In a similar manner, each phrase is obfuscated with a random number of conjoined obfuscation phrases. + // However, the number of phrases depends on the number of characters in the original phrase. + var sentenceBeginIndex = 0; + for (var i = 0; i < message.Length; i++) + { + var ch = char.ToLower(message[i]); + if (IsSentenceEnd(ch) || i == message.Length - 1) + { + var length = i - sentenceBeginIndex; + if (length > 0) + { + var newLength = (int) Math.Clamp(Math.Cbrt(length) - 1, 1, 4); // 27+ chars for 2 phrases, 64+ for 3, 125+ for 4. + + for (var j = 0; j < newLength; j++) + { + var phrase = _random.Pick(language.Replacement); + builder.Append(phrase); + } + } + sentenceBeginIndex = i + 1; + + if (IsSentenceEnd(ch)) + builder.Append(ch).Append(" "); + } + } + } + + public bool CanUnderstand(EntityUid listener, + LanguagePrototype language, + LanguageSpeakerComponent? listenerLanguageComp = null) + { + if (language.ID == UniversalPrototype || HasComp(listener)) + return true; + + var listenerLanguages = GetLanguages(listener, listenerLanguageComp)?.UnderstoodLanguages; + + return listenerLanguages?.Contains(language.ID, StringComparer.Ordinal) ?? false; + } + + public bool CanSpeak(EntityUid speaker, string language, LanguageSpeakerComponent? speakerComp = null) + { + if (HasComp(speaker)) + return true; + + var langs = GetLanguages(speaker, speakerComp)?.UnderstoodLanguages; + return langs?.Contains(language, StringComparer.Ordinal) ?? false; + } + + // + // Returns the current language of the given entity. Assumes Universal if not specified. + // + public LanguagePrototype GetLanguage(EntityUid speaker, LanguageSpeakerComponent? languageComp = null) + { + var id = GetLanguages(speaker, languageComp)?.CurrentLanguage; + if (id == null) + return Universal; // Fallback + + _prototype.TryIndex(id, out LanguagePrototype? proto); + + return proto ?? Universal; + } + + // + // Set the CurrentLanguage of the given entity. + // + public void SetLanguage(EntityUid speaker, string language, LanguageSpeakerComponent? languageComp = null) + { + if (!CanSpeak(speaker, language)) + return; + + if (languageComp == null && !TryComp(speaker, out languageComp)) + return; + + if (languageComp.CurrentLanguage == language) + return; + + languageComp.CurrentLanguage = language; + + RaiseLocalEvent(speaker, new LanguagesUpdateEvent(), true); + } + + /// + /// Adds a new language to the lists of understood and/or spoken languages of the given component. + /// + public void AddLanguage(LanguageSpeakerComponent comp, string language, bool addSpoken = true, bool addUnderstood = true) + { + if (addSpoken && !comp.SpokenLanguages.Contains(language, StringComparer.Ordinal)) + comp.SpokenLanguages.Add(language); + + if (addUnderstood && !comp.UnderstoodLanguages.Contains(language, StringComparer.Ordinal)) + comp.UnderstoodLanguages.Add(language); + + RaiseLocalEvent(comp.Owner, new LanguagesUpdateEvent(), true); + } + + private static bool IsSentenceEnd(char ch) + { + return ch is '.' or '!' or '?'; + } + + // This event is reused because re-allocating it each time is way too costly. + private readonly DetermineEntityLanguagesEvent _determineLanguagesEvent = new(string.Empty, new(), new()); + + /// + /// Returns a pair of (spoken, understood) languages of the given entity. + /// + public (List, List) GetAllLanguages(EntityUid speaker) + { + var languages = GetLanguages(speaker); + if (languages == null) + return (new(), new()); + + // The lists need to be copied because the internal ones are re-used for performance reasons. + return (new List(languages.SpokenLanguages), new List(languages.UnderstoodLanguages)); + } + + /// + /// Dynamically resolves the current language of the entity and the list of all languages it speaks. + /// The returned event is reused and thus must not be held as a reference anywhere but inside the caller function. + /// + private DetermineEntityLanguagesEvent? GetLanguages(EntityUid speaker, LanguageSpeakerComponent? comp = null) + { + if (comp == null && !TryComp(speaker, out comp)) + return null; + + var ev = _determineLanguagesEvent; + ev.SpokenLanguages.Clear(); + ev.UnderstoodLanguages.Clear(); + + ev.CurrentLanguage = comp.CurrentLanguage; + ev.SpokenLanguages.AddRange(comp.SpokenLanguages); + ev.UnderstoodLanguages.AddRange(comp.UnderstoodLanguages); + + RaiseLocalEvent(speaker, ev, true); + + if (ev.CurrentLanguage.Length == 0) + ev.CurrentLanguage = !string.IsNullOrEmpty(comp.CurrentLanguage) ? comp.CurrentLanguage : UniversalPrototype; // Fall back to account for admemes like admins possessing a bread + return ev; + } + + /// + /// Generates a stable pseudo-random number in the range [min, max) for the given seed. Each input seed corresponds to exactly one random number. + /// + private int PseudoRandomNumber(int seed, int min, int max) + { + // This is not a uniform distribution, but it shouldn't matter: given there's 2^31 possible random numbers, + // The bias of this function should be so tiny it will never be noticed. + seed += RandomRoundSeed; + var random = ((seed * 1103515245) + 12345) & 0x7fffffff; // Source: http://cs.uccs.edu/~cs591/bufferOverflow/glibc-2.2.4/stdlib/random_r.c + return random % (max - min) + min; + } + + /// + /// Ensures the given entity has a valid language as its current language. + /// If not, sets it to the first entry of its SpokenLanguages list, or universal if it's empty. + /// + public void EnsureValidLanguage(EntityUid entity, LanguageSpeakerComponent? comp = null) + { + if (comp == null && !TryComp(entity, out comp)) + return; + + var langs = GetLanguages(entity, comp); + + if (langs != null && !langs.SpokenLanguages.Contains(comp!.CurrentLanguage, StringComparer.Ordinal)) + { + comp.CurrentLanguage = langs.SpokenLanguages.FirstOrDefault(UniversalPrototype); + } + } + + /// + /// Raised in order to determine the language an entity speaks at the current moment, + /// as well as the list of all languages the entity may speak and understand. + /// + public sealed class DetermineEntityLanguagesEvent : EntityEventArgs + { + /// + /// The default language of this entity. If empty, remain unchanged. + /// This field has no effect if the entity decides to speak in a concrete language. + /// + public string CurrentLanguage; + /// + /// The list of all languages the entity may speak. Must NOT be held as a reference! + /// + public List SpokenLanguages; + /// + /// The list of all languages the entity may understand. Must NOT be held as a reference! + /// + public List UnderstoodLanguages; + + public DetermineEntityLanguagesEvent(string currentLanguage, List spokenLanguages, List understoodLanguages) + { + CurrentLanguage = currentLanguage; + SpokenLanguages = spokenLanguages; + UnderstoodLanguages = understoodLanguages; + } + } +} diff --git a/Content.Server/ADT/Language/TranslatorImplanterSystem.cs b/Content.Server/ADT/Language/TranslatorImplanterSystem.cs new file mode 100644 index 00000000000..f24a013cc26 --- /dev/null +++ b/Content.Server/ADT/Language/TranslatorImplanterSystem.cs @@ -0,0 +1,79 @@ +using System.Linq; +using Content.Server.Administration.Logs; +using Content.Server.Popups; +using Content.Shared.Database; +using Content.Shared.Interaction; +using Content.Shared.Language; +using Content.Shared.Language.Components; +using Content.Shared.Language.Systems; +using Content.Shared.Mobs.Components; + +namespace Content.Server.Language; + +public sealed class TranslatorImplanterSystem : SharedTranslatorImplanterSystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IAdminLogManager _adminLogger = default!; + [Dependency] private readonly LanguageSystem _language = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnImplant); + } + + private void OnImplant(EntityUid implanter, TranslatorImplanterComponent component, AfterInteractEvent args) + { + if (component.Used || !args.CanReach || args.Target is not { Valid: true } target) + return; + + if (!TryComp(target, out var speaker)) + return; + + if (component.MobsOnly && !HasComp(target)) + { + _popup.PopupEntity("translator-implanter-refuse", component.Owner); + return; + } + + var (_, understood) = _language.GetAllLanguages(target); + if (component.RequiredLanguages.Count > 0 && !component.RequiredLanguages.Any(lang => understood.Contains(lang))) + { + RefusesPopup(implanter, target); + return; + } + + var intrinsic = EnsureComp(target); + intrinsic.Enabled = true; + + foreach (var lang in component.SpokenLanguages.Where(lang => !intrinsic.SpokenLanguages.Contains(lang))) + intrinsic.SpokenLanguages.Add(lang); + + foreach (var lang in component.UnderstoodLanguages.Where(lang => !intrinsic.UnderstoodLanguages.Contains(lang))) + intrinsic.UnderstoodLanguages.Add(lang); + + component.Used = true; + SuccessPopup(implanter, target); + + _adminLogger.Add(LogType.Action, LogImpact.Medium, + $"{ToPrettyString(args.User):player} used {ToPrettyString(implanter):implanter} to give {ToPrettyString(target):target} the following languages:" + + $"\nSpoken: {string.Join(", ", component.SpokenLanguages)}; Understood: {string.Join(", ", component.UnderstoodLanguages)}"); + + OnAppearanceChange(implanter, component); + RaiseLocalEvent(target, new SharedLanguageSystem.LanguagesUpdateEvent(), true); + } + + private void RefusesPopup(EntityUid implanter, EntityUid target) + { + _popup.PopupEntity( + Loc.GetString("translator-implanter-refuse", ("implanter", implanter), ("target", target)), + implanter); + } + + private void SuccessPopup(EntityUid implanter, EntityUid target) + { + _popup.PopupEntity( + Loc.GetString("translator-implanter-success", ("implanter", implanter), ("target", target)), + implanter); + } +} diff --git a/Content.Server/ADT/Language/TranslatorSystem.cs b/Content.Server/ADT/Language/TranslatorSystem.cs new file mode 100644 index 00000000000..e19b2b29d71 --- /dev/null +++ b/Content.Server/ADT/Language/TranslatorSystem.cs @@ -0,0 +1,243 @@ +using System.Linq; +using Content.Server.Popups; +using Content.Server.PowerCell; +using Content.Shared.Hands; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory; +using Content.Shared.Inventory.Events; +using Content.Shared.Language; +using Content.Shared.Language.Components; +using Content.Shared.Language.Systems; +using Content.Shared.PowerCell; +using static Content.Server.Language.LanguageSystem; +using HandheldTranslatorComponent = Content.Shared.Language.Components.HandheldTranslatorComponent; +using HoldsTranslatorComponent = Content.Shared.Language.Components.HoldsTranslatorComponent; +using IntrinsicTranslatorComponent = Content.Shared.Language.Components.IntrinsicTranslatorComponent; + +namespace Content.Server.Language; + +// this does not support holding multiple translators at once yet. +// that should not be an issue for now, but it better get fixed later. +public sealed class TranslatorSystem : SharedTranslatorSystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly LanguageSystem _language = default!; + [Dependency] private readonly PowerCellSystem _powerCell = default!; + + private ISawmill _sawmill = default!; + + public override void Initialize() + { + base.Initialize(); + _sawmill = Logger.GetSawmill("translator"); + + // I wanna die. But my death won't help us discover polymorphism. + SubscribeLocalEvent(ApplyTranslation); + SubscribeLocalEvent(ApplyTranslation); + SubscribeLocalEvent(ApplyTranslation); + // TODO: make this thing draw power + // SubscribeLocalEvent(...); + + SubscribeLocalEvent(OnTranslatorToggle); + SubscribeLocalEvent(OnPowerCellSlotEmpty); + + SubscribeLocalEvent( + (uid, component, args) => TranslatorEquipped(args.User, uid, component)); + SubscribeLocalEvent( + (uid, component, args) => TranslatorUnequipped(args.User, uid, component)); + } + + private void ApplyTranslation(EntityUid uid, IntrinsicTranslatorComponent component, + DetermineEntityLanguagesEvent ev) + { + if (!component.Enabled) + return; + + if (!_powerCell.HasActivatableCharge(uid)) + return; + + var addUnderstood = true; + var addSpoken = true; + if (component.RequiredLanguages.Count > 0) + { + if (component.RequiresAllLanguages) + { + // Add langs when the wielder has all of the required languages + foreach (var language in component.RequiredLanguages) + { + if (!ev.SpokenLanguages.Contains(language, StringComparer.Ordinal)) + addSpoken = false; + + if (!ev.UnderstoodLanguages.Contains(language, StringComparer.Ordinal)) + addUnderstood = false; + } + } + else + { + // Add langs when the wielder has at least one of the required languages + addUnderstood = false; + addSpoken = false; + foreach (var language in component.RequiredLanguages) + { + if (ev.SpokenLanguages.Contains(language, StringComparer.Ordinal)) + addSpoken = true; + + if (ev.UnderstoodLanguages.Contains(language, StringComparer.Ordinal)) + addUnderstood = true; + } + } + } + + if (addSpoken) + { + foreach (var language in component.SpokenLanguages) + { + AddIfNotExists(ev.SpokenLanguages, language); + } + + if (component.CurrentSpeechLanguage != null && ev.CurrentLanguage.Length == 0) + { + ev.CurrentLanguage = component.CurrentSpeechLanguage; + } + } + + if (addUnderstood) + { + foreach (var language in component.UnderstoodLanguages) + { + AddIfNotExists(ev.UnderstoodLanguages, language); + } + } + } + + private void TranslatorEquipped(EntityUid holder, EntityUid translator, HandheldTranslatorComponent component) + { + if (!EntityManager.HasComponent(holder)) + return; + + var intrinsic = EntityManager.EnsureComponent(holder); + UpdateBoundIntrinsicComp(component, intrinsic, component.Enabled); + + UpdatedLanguages(holder); + } + + private void TranslatorUnequipped(EntityUid holder, EntityUid translator, HandheldTranslatorComponent component) + { + if (!EntityManager.TryGetComponent(holder, out var intrinsic)) + return; + + if (intrinsic.Issuer == component) + { + + intrinsic.Enabled = false; + EntityManager.RemoveComponent(holder, intrinsic); + } + + _language.EnsureValidLanguage(holder); + + UpdatedLanguages(holder); + } + + private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponent component, ActivateInWorldEvent args) + { + if (!component.ToggleOnInteract) + return; + + var hasPower = _powerCell.HasDrawCharge(translator); + + if (Transform(args.Target).ParentUid is { Valid: true } holder && EntityManager.HasComponent(holder)) + { + // This translator is held by a language speaker and thus has an intrinsic counterpart bound to it. Make sure it's up-to-date. + var intrinsic = EntityManager.EnsureComponent(holder); + var isEnabled = !component.Enabled; + if (intrinsic.Issuer != component) + { + // The intrinsic comp wasn't owned by this handheld component, so this comp wasn't the active translator. + // Thus it needs to be turned on regardless of its previous state. + intrinsic.Issuer = component; + isEnabled = true; + } + + isEnabled &= hasPower; + UpdateBoundIntrinsicComp(component, intrinsic, isEnabled); + component.Enabled = isEnabled; + _powerCell.SetPowerCellDrawEnabled(translator, isEnabled); + + _language.EnsureValidLanguage(holder); + UpdatedLanguages(holder); + } + else + { + // This is a standalone translator (e.g. lying on the ground). Simply toggle its state. + component.Enabled = !component.Enabled && hasPower; + _powerCell.SetPowerCellDrawEnabled(translator, !component.Enabled && hasPower); + } + + OnAppearanceChange(translator, component); + + // HasPower shows a popup when there's no power, so we do not proceed in that case + if (hasPower) + { + var message = + Loc.GetString(component.Enabled ? "translator-component-turnon" : "translator-component-shutoff", ("translator", component.Owner)); + _popup.PopupEntity(message, component.Owner, args.User); + } + } + + private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args) + { + component.Enabled = false; + _powerCell.SetPowerCellDrawEnabled(translator, false); + OnAppearanceChange(translator, component); + + if (Transform(translator).ParentUid is { Valid: true } holder && EntityManager.HasComponent(holder)) + { + if (!EntityManager.TryGetComponent(holder, out var intrinsic)) + return; + + if (intrinsic.Issuer == component) + { + intrinsic.Enabled = false; + EntityManager.RemoveComponent(holder, intrinsic); + } + + _language.EnsureValidLanguage(holder); + UpdatedLanguages(holder); + } + } + + /// + /// Copies the state from the handheld [comp] to the [intrinsic] comp, using [isEnabled] as the enabled state. + /// + private void UpdateBoundIntrinsicComp(HandheldTranslatorComponent comp, HoldsTranslatorComponent intrinsic, bool isEnabled) + { + if (isEnabled) + { + intrinsic.SpokenLanguages = new List(comp.SpokenLanguages); + intrinsic.UnderstoodLanguages = new List(comp.UnderstoodLanguages); + intrinsic.CurrentSpeechLanguage = comp.CurrentSpeechLanguage; + } + else + { + intrinsic.SpokenLanguages.Clear(); + intrinsic.UnderstoodLanguages.Clear(); + intrinsic.CurrentSpeechLanguage = null; + } + + intrinsic.Enabled = isEnabled; + intrinsic.Issuer = comp; + } + + private static void AddIfNotExists(List list, string item) + { + if (list.Contains(item)) + return; + list.Add(item); + } + + private void UpdatedLanguages(EntityUid uid) + { + RaiseLocalEvent(uid, new SharedLanguageSystem.LanguagesUpdateEvent(), true); + } +} diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 38d485458bc..93818d0df64 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -11,6 +11,7 @@ using Content.Server.Speech.EntitySystems; using Content.Server.Station.Components; using Content.Server.Station.Systems; +using Content.Server.Language; using Content.Shared.ActionBlocker; using Content.Shared.Administration; using Content.Shared.CCVar; @@ -26,6 +27,7 @@ using Content.Shared.Radio; using Content.Shared.Speech; using Content.Shared.Whitelist; +using Content.Shared.Language; using Robust.Server.Player; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; @@ -64,6 +66,7 @@ public sealed partial class ChatSystem : SharedChatSystem [Dependency] private readonly ReplacementAccentSystem _wordreplacement = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly ExamineSystemShared _examineSystem = default!; + [Dependency] private readonly LanguageSystem _language = default!; // Corvax-TTS-Start: Moved from Server to Shared // public const int VoiceRange = 10; // how far voice goes in world units @@ -177,8 +180,8 @@ public void TrySendInGameICMessage( ICommonSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true, - bool ignoreActionBlocker = false - ) + bool ignoreActionBlocker = false, + LanguagePrototype? languageOverride = null) // Frontier - languages mechanic { if (HasComp(source)) { @@ -253,10 +256,10 @@ public void TrySendInGameICMessage( switch (desiredType) { case InGameICChatType.Speak: - SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker); + SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); break; case InGameICChatType.Whisper: - SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker); + SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker, languageOverride: languageOverride); break; case InGameICChatType.Emote: SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); @@ -387,7 +390,8 @@ private void SendEntitySpeak( ChatTransmitRange range, string? nameOverride, bool hideLog = false, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + LanguagePrototype? languageOverride = null // Frontier: languages mechanic ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) @@ -416,18 +420,30 @@ private void SendEntitySpeak( speech = proto; } + // Frontier - languages mechanic + var language = languageOverride ?? _language.GetLanguage(source); + name = FormattedMessage.EscapeText(name); + // Frontier: languages mechanic ADT Upd start + if (TryComp(source, out var lang) && lang.CurrentLanguage != "GalacticCommon" && lang.CurrentLanguage != "Universal") + name = $"{lang.LocalizedID}|{name}"; + // Frontier: languages mechanic ADT Upd end + var wrappedMessage = Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message", ("entityName", name), ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), ("fontType", speech.FontId), ("fontSize", speech.FontSize), ("message", FormattedMessage.EscapeText(message))); + + var encodedMessage = _language.ObfuscateSpeech(source, message, language); + var wrappedEncodedMessage = WrapPublicMessage(source, name, encodedMessage); - SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range); + // Frontier: languages mechanic + SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, encodedMessage, wrappedEncodedMessage, source, range, languageOverride: language); - var ev = new EntitySpokeEvent(source, message, originalMessage, null, null); + var ev = new EntitySpokeEvent(source, message, originalMessage, encodedMessage, null, language, null); RaiseLocalEvent(source, ev, true); // To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc. @@ -460,7 +476,8 @@ private void SendEntityWhisper( RadioChannelPrototype? channel, string? nameOverride, bool hideLog = false, - bool ignoreActionBlocker = false + bool ignoreActionBlocker = false, + LanguagePrototype? languageOverride = null // Frontier: languages mechanic ) { if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) @@ -487,16 +504,15 @@ private void SendEntityWhisper( name = nameEv.Name; } name = FormattedMessage.EscapeText(name); + + // Frontier: languages mechanic ADT Upd start + if (TryComp(source, out var lang) && lang.CurrentLanguage != "GalacticCommon" && lang.CurrentLanguage != "Universal") + name = $"{lang.LocalizedID}|{name}"; + // Frontier: languages mechanic ADT Upd end - var wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("entityName", name), ("message", FormattedMessage.EscapeText(message))); - - var wrappedobfuscatedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", - ("entityName", nameIdentity), ("message", FormattedMessage.EscapeText(obfuscatedMessage))); - - var wrappedUnknownMessage = Loc.GetString("chat-manager-entity-whisper-unknown-wrap-message", - ("message", FormattedMessage.EscapeText(obfuscatedMessage))); - + // Frontier - languages mechanic (+ everything in the foreach loop) + var language = languageOverride ?? _language.GetLanguage(source); + var languageEncodedMessage = _language.ObfuscateSpeech(source, message, language); foreach (var (session, data) in GetRecipients(source, WhisperMuffledRange)) { @@ -509,19 +525,42 @@ private void SendEntityWhisper( if (MessageRangeCheck(session, data, range) != MessageRangeCheckResult.Full) 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. + var canUnderstand = _language.CanUnderstand(listener, language); + var _message = canUnderstand ? message : languageEncodedMessage; + if (data.Range <= WhisperClearRange) - _chatManager.ChatMessageToOne(ChatChannel.Whisper, message, wrappedMessage, source, false, session.Channel); + { + var wrappedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", name), ("message", FormattedMessage.EscapeText(_message))); + + _chatManager.ChatMessageToOne(ChatChannel.Whisper, _message, wrappedMessage, source, false, session.Channel); + } //If listener is too far, they only hear fragments of the message else if (_examineSystem.InRangeUnOccluded(source, listener, WhisperMuffledRange)) - _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedobfuscatedMessage, source, false, session.Channel); + { + var _obfuscatedMessage = ObfuscateMessageReadability(_message, 0.2f); + + var wrappedobfuscatedMessage = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", nameIdentity), ("message", FormattedMessage.EscapeText(_obfuscatedMessage))); + _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); + { + var _obfuscatedMessage = ObfuscateMessageReadability(_message, 0.2f); + + var wrappedUnknownMessage = Loc.GetString("chat-manager-entity-whisper-unknown-wrap-message", + ("message", FormattedMessage.EscapeText(_obfuscatedMessage))); + _chatManager.ChatMessageToOne(ChatChannel.Whisper, _obfuscatedMessage, wrappedUnknownMessage, source, false, session.Channel); + } } - _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); + var replayWrap = Loc.GetString("chat-manager-entity-whisper-wrap-message", + ("entityName", name), + ("message", FormattedMessage.EscapeText(message))); + _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, replayWrap, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); - var ev = new EntitySpokeEvent(source, message, originalMessage, channel, obfuscatedMessage); + var ev = new EntitySpokeEvent(source, message, originalMessage, languageEncodedMessage, channel, language, obfuscatedMessage); RaiseLocalEvent(source, ev, true); if (!hideLog) if (originalMessage == message) @@ -568,7 +607,7 @@ private void SendEntityEmote( if (checkEmote) TryEmoteChatInput(source, action); - SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author); + SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage,encodedMessage: string.Empty, wrappedEncodedMessage: string.Empty, source, range, author); if (!hideLog) if (name != Name(source)) _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}"); @@ -595,7 +634,12 @@ private void SendLOOC(EntityUid source, ICommonSession player, string message, b ("entityName", name), ("message", FormattedMessage.EscapeText(message))); - SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, player.UserId); + SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, + encodedMessage: string.Empty, + wrappedEncodedMessage: string.Empty, + source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, + player.UserId, + languageOverride: LanguageSystem.Universal); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); } @@ -676,15 +720,31 @@ private MessageRangeCheckResult MessageRangeCheck(ICommonSession session, ICChat /// /// Sends a chat message to the given players in range of the source entity. /// - private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null) + private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, string encodedMessage, string wrappedEncodedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null, LanguagePrototype? languageOverride = null) { + + var language = languageOverride ?? _language.GetLanguage(source); // frontier + 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); + + // Frontier - languages mechanic + if (session.AttachedEntity is not { Valid: true } playerEntity) + continue; + EntityUid listener = session.AttachedEntity.Value; + + if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language)) + { + _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.ConnectedClient, author: author); + } + else + { + _chatManager.ChatMessageToOne(channel, encodedMessage, wrappedEncodedMessage, source, entHideChat, session.ConnectedClient, author: author); + } } _replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); @@ -794,6 +854,19 @@ public string SanitizeMessageReplaceWords(string message) return msg; } + // Frontier - languages mechanic + public string WrapPublicMessage(EntityUid source, string name, string message) + { + var speech = GetSpeechVerb(source, message); + var verbName = Loc.GetString(_random.Pick(speech.SpeechVerbStrings)); + return Loc.GetString(speech.Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message", + ("entityName", name), + ("verb", verbName), + ("fontType", speech.FontId), + ("fontSize", speech.FontSize), + ("message", FormattedMessage.EscapeText(message))); + } + /// /// Returns list of players and ranges for all players withing some range. Also returns observers with a range of -1. /// @@ -930,7 +1003,9 @@ public sealed class EntitySpokeEvent : EntityEventArgs public readonly EntityUid Source; public readonly string Message; public readonly string OriginalMessage; - public readonly string? ObfuscatedMessage; // not null if this was a whisper + public readonly string LanguageEncodedMessage; + public readonly string? ObfuscatedMessage; // not null if this was a + public readonly LanguagePrototype Language; /// /// If the entity was trying to speak into a radio, this was the channel they were trying to access. If a radio @@ -938,13 +1013,15 @@ public sealed class EntitySpokeEvent : EntityEventArgs /// public RadioChannelPrototype? Channel; - public EntitySpokeEvent(EntityUid source, string message, string originalMessage, RadioChannelPrototype? channel, string? obfuscatedMessage) + public EntitySpokeEvent(EntityUid source, string message, string originalMessage, string languageEncodedMessage, RadioChannelPrototype? channel, LanguagePrototype language, string? obfuscatedMessage) { Source = source; Message = message; OriginalMessage = originalMessage; // Corvax-TTS: Spec symbol sanitize + LanguageEncodedMessage = languageEncodedMessage; Channel = channel; ObfuscatedMessage = obfuscatedMessage; + Language = language; } } diff --git a/Content.Shared/ADT/Language/Components/CommonLangUnknown.cs b/Content.Shared/ADT/Language/Components/CommonLangUnknown.cs new file mode 100644 index 00000000000..bac1230704f --- /dev/null +++ b/Content.Shared/ADT/Language/Components/CommonLangUnknown.cs @@ -0,0 +1,14 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language; + +[RegisterComponent] +public sealed partial class UnknowLanguageComponent : Component +{ + [DataField("language")] + public string LanguageToForgot = "GalacticCommon"; +} diff --git a/Content.Shared/ADT/Language/Components/LanguageSpeakerComponent.cs b/Content.Shared/ADT/Language/Components/LanguageSpeakerComponent.cs new file mode 100644 index 00000000000..2d3953f78ca --- /dev/null +++ b/Content.Shared/ADT/Language/Components/LanguageSpeakerComponent.cs @@ -0,0 +1,53 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language; + +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class LanguageSpeakerComponent : Component +{ + /// + /// The current language the entity may use to speak. + /// Other listeners will hear the entity speak in this language. + /// + [ViewVariables(VVAccess.ReadWrite)] + [AutoNetworkedField] + public string CurrentLanguage = default!; + + /// + /// чтоб в чате видно было не айди, а название. + /// + public string LocalizedID => Loc.GetString("language-" + CurrentLanguage); + + + /// + /// List of languages this entity can speak. + /// + [ViewVariables] + [DataField("speaks", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] + public List SpokenLanguages = new(); + + /// + /// List of languages this entity can understand. + /// + [ViewVariables] + [DataField("understands", customTypeSerializer: typeof(PrototypeIdListSerializer), required: true)] + public List UnderstoodLanguages = new(); + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("languageMenuAction", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string LanguageMenuAction = "ActionLanguageMenu"; + + [DataField] public EntityUid? Action; +} + +[Serializable, NetSerializable] +public enum LanguageMenuUiKey : byte +{ + Key +} + +public sealed partial class LanguageMenuActionEvent : InstantActionEvent { } diff --git a/Content.Shared/ADT/Language/Components/TranslatorComponent.cs b/Content.Shared/ADT/Language/Components/TranslatorComponent.cs new file mode 100644 index 00000000000..a5fac7d2ff5 --- /dev/null +++ b/Content.Shared/ADT/Language/Components/TranslatorComponent.cs @@ -0,0 +1,93 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language.Components; + +public abstract partial class BaseTranslatorComponent : Component +{ + /// + /// The language this translator changes the speaker's language to when they don't specify one. + /// If null, does not modify the default language. + /// + [DataField("default-language")] + [ViewVariables(VVAccess.ReadWrite)] + public string? CurrentSpeechLanguage = null; + + /// + /// The list of additional languages this translator allows the wielder to speak. + /// + [DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [ViewVariables(VVAccess.ReadWrite)] + public List SpokenLanguages = new(); + + /// + /// The list of additional languages this translator allows the wielder to understand. + /// + [DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [ViewVariables(VVAccess.ReadWrite)] + public List UnderstoodLanguages = new(); + + /// + /// The languages the wielding MUST know in order for this translator to have effect. + /// The field [RequiresAllLanguages] indicates whether all of them are required, or just one. + /// + [DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer))] + [ViewVariables(VVAccess.ReadWrite)] + public List RequiredLanguages = new(); + + /// + /// If true, the wielder must understand all languages in [RequiredLanguages] to speak [SpokenLanguages], + /// and understand all languages in [RequiredLanguages] to understand [UnderstoodLanguages]. + /// + /// Otherwise, at least one language must be known (or the list must be empty). + /// + [DataField("requires-all")] + [ViewVariables(VVAccess.ReadWrite)] + public bool RequiresAllLanguages = false; + + [DataField("enabled")] + public bool Enabled = true; +} + +/// +/// A translator that must be held in a hand or a pocket of an entity in order ot have effect. +/// +[RegisterComponent] +public sealed partial class HandheldTranslatorComponent : BaseTranslatorComponent +{ + /// + /// Whether or not interacting with this translator + /// toggles it on or off. + /// + [DataField("toggleOnInteract")] + public bool ToggleOnInteract = true; +} + +/// +/// A translator attached to an entity that translates its speech. +/// An example is a translator implant that allows the speaker to speak another language. +/// +[RegisterComponent, Virtual] +public partial class IntrinsicTranslatorComponent : BaseTranslatorComponent +{ +} + +/// +/// Applied internally to the holder of [HandheldTranslatorComponent]. +/// Do not use directly. Use [HandheldTranslatorComponent] instead. +/// +[RegisterComponent] +public sealed partial class HoldsTranslatorComponent : IntrinsicTranslatorComponent +{ + public Component? Issuer = null; +} + +/// +/// Applied to entities who were injected with a translator implant. +/// +[RegisterComponent] +public sealed partial class ImplantedTranslatorComponent : IntrinsicTranslatorComponent +{ +} diff --git a/Content.Shared/ADT/Language/Components/TranslatorImplanterComponent.cs b/Content.Shared/ADT/Language/Components/TranslatorImplanterComponent.cs new file mode 100644 index 00000000000..28ed4cce185 --- /dev/null +++ b/Content.Shared/ADT/Language/Components/TranslatorImplanterComponent.cs @@ -0,0 +1,34 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; + +namespace Content.Shared.Language.Components; + +/// +/// An item that, when used on a mob, adds an intrinsic translator to it. +/// +[RegisterComponent] +public sealed partial class TranslatorImplanterComponent : Component +{ + [DataField("spoken", customTypeSerializer: typeof(PrototypeIdListSerializer)), ViewVariables] + public List SpokenLanguages = new(); + + [DataField("understood", customTypeSerializer: typeof(PrototypeIdListSerializer)), ViewVariables] + public List UnderstoodLanguages = new(); + + /// + /// The list of languages the mob must understand in order for this translator to have effect. + /// Knowing one language is enough. + /// + [DataField("requires", customTypeSerializer: typeof(PrototypeIdListSerializer)), ViewVariables] + public List RequiredLanguages = new(); + + /// + /// If true, only allows to use this implanter on mobs. + /// + [DataField("mobs-only")] + public bool MobsOnly = true; + + /// + /// Whether this implant has been used already. + /// + public bool Used = false; +} diff --git a/Content.Shared/ADT/Language/Components/UniversalLanguageSpeacerComponent.cs b/Content.Shared/ADT/Language/Components/UniversalLanguageSpeacerComponent.cs new file mode 100644 index 00000000000..7ef609e6ba0 --- /dev/null +++ b/Content.Shared/ADT/Language/Components/UniversalLanguageSpeacerComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Language.Components; + +// +// Signifies that this entity can speak and understand any language. +// Applies to such entities as ghosts. +// +[RegisterComponent] +public sealed partial class UniversalLanguageSpeakerComponent : Component +{ + +} diff --git a/Content.Shared/ADT/Language/LanguagePrototype.cs b/Content.Shared/ADT/Language/LanguagePrototype.cs new file mode 100644 index 00000000000..c160187bf62 --- /dev/null +++ b/Content.Shared/ADT/Language/LanguagePrototype.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Language; + +[Prototype("language")] +public sealed class LanguagePrototype : IPrototype +{ + [IdDataField] + public string ID { get; private set; } = default!; + + // + // If true, obfuscated phrases of creatures speaking this language will have their syllables replaced with "replacement" syllables. + // Otherwise entire sentences will be replaced. + // + [DataField("obfuscateSyllables", required: true)] + public bool ObfuscateSyllables { get; private set; } = false; + + // + // Lists all syllables that are used to obfuscate a message a listener cannot understand if obfuscateSyllables is true, + // Otherwise uses all possible phrases the creature can make when trying to say anything. + // + [DataField("replacement", required: true)] + public List Replacement = new(); + + public string LocalizedName => Loc.GetString("language-" + ID + "-name"); + + public string LocalizedDescription => Loc.GetString("language-" + ID + "-description"); +} diff --git a/Content.Shared/ADT/Language/Systems/SharedLanguageSystem.cs b/Content.Shared/ADT/Language/Systems/SharedLanguageSystem.cs new file mode 100644 index 00000000000..54c33fc1859 --- /dev/null +++ b/Content.Shared/ADT/Language/Systems/SharedLanguageSystem.cs @@ -0,0 +1,73 @@ +using Content.Shared.Actions; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedLanguageSystem : EntitySystem +{ + [ValidatePrototypeId] + public static readonly string GalacticCommonPrototype = "GalacticCommon"; + [ValidatePrototypeId] + public static readonly string UniversalPrototype = "Universal"; + public static LanguagePrototype GalacticCommon { get; private set; } = default!; + public static LanguagePrototype Universal { get; private set; } = default!; + + [Dependency] private readonly SharedActionsSystem _action = default!; + [Dependency] protected readonly IPrototypeManager _prototype = default!; + [Dependency] protected readonly IRobustRandom _random = default!; + protected ISawmill _sawmill = default!; + + public override void Initialize() + { + GalacticCommon = _prototype.Index("GalacticCommon"); + Universal = _prototype.Index("Universal"); + _sawmill = Logger.GetSawmill("language"); + + SubscribeLocalEvent(OnInit); + } + + public LanguagePrototype? GetLanguage(string id) + { + _prototype.TryIndex(id, out var proto); + return proto; + } + + private void OnInit(EntityUid uid, LanguageSpeakerComponent component, MapInitEvent args) + { + _action.AddAction(uid, ref component.Action, component.LanguageMenuAction, uid); + } + + /// + /// Raised on an entity when its list of languages changes. + /// + public sealed class LanguagesUpdateEvent : EntityEventArgs + { + } + + /// + /// Sent when a client wants to update its language menu. + /// + [Serializable, NetSerializable] + public sealed class RequestLanguageMenuStateMessage : EntityEventArgs + { + } + + /// + /// Sent by the server when the client needs to update its language menu, + /// or directly after [RequestLanguageMenuStateMessage]. + /// + [Serializable, NetSerializable] + public sealed class LanguageMenuStateMessage : EntityEventArgs + { + public string CurrentLanguage; + public List Options; + + public LanguageMenuStateMessage(string currentLanguage, List options) + { + CurrentLanguage = currentLanguage; + Options = options; + } + } +} diff --git a/Content.Shared/ADT/Language/Systems/SharedTranslatorImplanterSystem.cs b/Content.Shared/ADT/Language/Systems/SharedTranslatorImplanterSystem.cs new file mode 100644 index 00000000000..5a602d03c54 --- /dev/null +++ b/Content.Shared/ADT/Language/Systems/SharedTranslatorImplanterSystem.cs @@ -0,0 +1,36 @@ +using Content.Shared.Examine; +using Content.Shared.Implants.Components; +using Content.Shared.Language.Components; +using Robust.Shared.Serialization; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedTranslatorImplanterSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, TranslatorImplanterComponent component, ExaminedEvent args) + { + if (!args.IsInDetailsRange) + return; + + var text = !component.Used + ? Loc.GetString("translator-implanter-ready") + : Loc.GetString("translator-implanter-used"); + + args.PushText(text); + } + + protected void OnAppearanceChange(EntityUid implanter, TranslatorImplanterComponent component) + { + var used = component.Used; + _appearance.SetData(implanter, ImplanterVisuals.Full, !used); + } +} diff --git a/Content.Shared/ADT/Language/Systems/SharedTranslatorSystem.cs b/Content.Shared/ADT/Language/Systems/SharedTranslatorSystem.cs new file mode 100644 index 00000000000..540744139da --- /dev/null +++ b/Content.Shared/ADT/Language/Systems/SharedTranslatorSystem.cs @@ -0,0 +1,34 @@ +using Content.Shared.Examine; +using Content.Shared.Language.Components; +using Content.Shared.Toggleable; + +namespace Content.Shared.Language.Systems; + +public abstract class SharedTranslatorSystem : EntitySystem +{ + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamined); + } + + private void OnExamined(EntityUid uid, HandheldTranslatorComponent component, ExaminedEvent args) + { + var state = Loc.GetString(component.Enabled + ? "translator-enabled" + : "translator-disabled"); + + args.PushMarkup(state); + } + + protected void OnAppearanceChange(EntityUid translator, HandheldTranslatorComponent? comp = null) + { + if (comp == null && !TryComp(translator, out comp)) + return; + + _appearance.SetData(translator, ToggleVisuals.Toggled, comp.Enabled); + } +} diff --git a/Resources/Locale/ru-RU/ADT/Actions/language-menu.ftl b/Resources/Locale/ru-RU/ADT/Actions/language-menu.ftl new file mode 100644 index 00000000000..56833276de6 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/Actions/language-menu.ftl @@ -0,0 +1,6 @@ +language-menu-window-title = Выбор языка +language-menu-current-language = Выбранный язык: {$language} +language-menu-description-header = Описание +choose-lang-button = Выбрать +language-menu-action = Меню языков +language-menu-action-desc = Открыть меню выбора языка. diff --git a/Resources/Locale/ru-RU/ADT/Entities/Objects/Device/translators.ftl b/Resources/Locale/ru-RU/ADT/Entities/Objects/Device/translators.ftl new file mode 100644 index 00000000000..5ae89edec38 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/Entities/Objects/Device/translators.ftl @@ -0,0 +1,61 @@ +translator-component-shutoff = {$translator} выключается. +translator-component-turnon = The {$translator} включается. +translator-enabled = Оно включено. +translator-disabled = Оно выключено. + +ent-TranslatorUnpowered = переводчик + .desc = Переводит речь. + +ent-Translator = переводчик + .desc = Переводит речь. + +ent-VulpTranslator = переводчик вульпканинов + .desc = Используется вульпканинами, для понимания Общегалактического языка в случае его незнания. + +ent-CanilunztTranslator = переводчик Каннилунц + .desc = Используется для взаимного перевода Общегалактического языка и языка Каннилунц. + +ent-BubblishTranslator = переводчик Пузырчатого языка + .desc = Используется для взаимного перевода Общегалактического и Пузырчатого языков. + +ent-NekomimeticTranslator = переводчик некоязыка + .desc = Используется для взаимного перевода Общегалактического и неко-языков. + +ent-DraconicTranslator = переводчик Синта'унати + .desc = Используется для взаимного перевода Общегалактического языка и Синта'унати. + +ent-SolCommonTranslator = переводчик Солнечного языка + .desc = Используется для взаимного перевода Общегалактического и Солнечного языков. + +ent-RootSpeakTranslator = переводчик Песни Корней + .desc = Используется для взаимного перевода Общегалактического языка и Песни Корней. + +ent-MofficTranslator = переводчик Паучьего языка + .desc = Используется для взаимного перевода Общегалактического и Паучьего языков. + +ent-XenoTranslator = переводчик языка ксено + .desc = Используется для общения с наименее агрессивными ксеноморфами. + +ent-AnimalTranslator = переводчик для животных + .desc = Используется для общения с животными. + +ent-DraskTranslator = переводчик языка Орлуум + .desc = Используется для взаимного перевода Общегалактического языка и языка Орлуум. + +ent-SikTajTranslator = переводчик Сик'тайр + .desc = Используется для взаимного перевода Общегалактического языка и Сик'тайр. + +ent-CintaTajTranslator = переводчик Синта’Тайр + .desc = Используется для взаимного перевода Общегалактического языка и Синта’тайр. + +ent-ArkaneTranslator = переводчик языка Каукиттен + .desc = Используется для взаимного перевода Общегалактического языка и Каукиттен. + +ent-ShadowkinTranslator = переводчик языка Миар + .desc = Используется для взаимного перевода Общегалактического языка и Миар. + +ent-NianTranslator = переводчик Ткачьего языка + .desc = Используется для взаимного перевода Общегалактического и Ткачьего языков. + +ent-FireTranslator = переводчик Огненного языка + .desc = Используется для взаимного перевода Общегалактического и Огненного языков. diff --git a/Resources/Locale/ru-RU/ADT/Entities/Objects/Misc/implanters.ftl b/Resources/Locale/ru-RU/ADT/Entities/Objects/Misc/implanters.ftl new file mode 100644 index 00000000000..ae8e374fb57 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/Entities/Objects/Misc/implanters.ftl @@ -0,0 +1,68 @@ +translator-implanter-refuse = {$implanter} не возымел эффекта для {$target}. +translator-implanter-success = {$implanter} успешно имплантирован в {$target}. +translator-implanter-ready = Имплантер готов к использованию. +translator-implanter-used = Имплантер пуст. + +ent-BasicGalaticCommonTranslatorImplanter = базовый языковой имплант Общегалактического языка + .desc = Имплант, позволяющий понимать Общегалактический язык. + +ent-AdvancedGalaticCommonTranslatorImplanter = полноценный языковой имплант Общегалактического языка + .desc = Имплант, позволяющий понимать и общаться на Общегалактическом языке. + +ent-BubblishTranslatorImplanter = полноценный языковой имплант Пузырчатого языка + .desc = Имплант, позволяющий понимать и общаться на Пузырчатом языке. + +ent-NekomimeticTranslatorImplanter = полноценный языковой имплант некоязыка + .desc = Имплант, позволяющий понимать и общаться на некоязыке. + +ent-DraconicTranslatorImplanter = полноценный языковой имплант Синта'унати + .desc = Имплант, позволяющий понимать и общаться на Синта'унати. + +ent-CanilunztTranslatorImplanter = полноценный языковой имплант Канилунц + .desc = Имплант, позволяющий понимать и общаться на языке Канилунц. + +ent-SolCommonTranslatorImplanter = полноценный языковой имплант Солнечного языка + .desc = Имплант, позволяющий понимать и общаться на Солнечном языке. + +ent-RootSpeakTranslatorImplanter = базовый языковой имплант Песни корней + .desc = Имплант, позволяющий понимать Песнь корней. + +ent-MofficTranslatorImplanter = полноценный языковой имплант Паучьего языка + .desc = Имплант, позволяющий понимать и общаться на Паучьем языке. + +ent-CodeSpeakImplanter = полноценный языковой имплант Кодового языка + .desc = Имплант, позволяющий понимать и общаться на Кодовом языке. + +ent-SikTajTranslatorImplanter = полноценный языковой имплант языка Сик'тайр + .desc = Имплант, позволяющий понимать и общаться на Сик'тайре. + +ent-CintaTajTranslatorImplanter = полноценный языковой имплант языка Синта’Тайр + .desc = Имплант, позволяющий понимать и общаться на Синта’тайре. + +ent-NianTranslatorImplanter = полноценный языковой имплант Ткачьего языка + .desc = Имплант, позволяющий понимать и общаться на Ткачьем языке. + +ent-FireTranslatorImplanter = базовый языковой имплант Огненного языка + .desc = Имплант, позволяющий понимать Огненный язык. + +ent-DraskTranslatorImplanter = полноценный языковой имплант Орлуум + .desc = Имплант, позволяющий понимать и общаться на языке Орлуум. + +ent-UrsTranslatorImplanter = полноценный языковой имплант Рыкрур + .desc = Имплант, позволяющий понимать и общаться на языке Рыкрур. + +ent-ArkaneTranslatorImplanter = полноценный языковой имплант Каукиттен + .desc = Имплант, позволяющий понимать и общаться на языке Каукиттен. + +ent-ShadowkinTranslatorImplanter = полноценный языковой имплант Миар + .desc = Имплант, позволяющий понимать и общаться на языке Миар. + +ent-BorgTranslatorImplanter = полноценный языковой имплант двоичного кода + .desc = Имплант, позволяющий понимать и общаться на двоичном коде. + +ent-SyndUniversalTranslatorImplanter = универсальный языковой имплант + .desc = Имплант, позволяющий (только) понимать все расовые языки. + +ent-DevTranslatorImplanter = имплант языка разработчика + .desc = Опять чинить апстрим? + .suffix = АДМЕМЫ diff --git a/Resources/Locale/ru-RU/ADT/Languages/languages.ftl b/Resources/Locale/ru-RU/ADT/Languages/languages.ftl new file mode 100644 index 00000000000..f1cdbe6c926 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/Languages/languages.ftl @@ -0,0 +1,133 @@ +language-Universal-name = Универсальный +language-Universal-description = Что ты такое? +language-GalacticCommon-name = Общегалактический +language-GalacticCommon-description = Обычно используется для межвидового общения и официальных целей. +language-Bubblish-name = Пузырчатый +language-Bubblish-description = Язык слаймолюдов. Это смесь булькающих звуков и хлюпов. Человеку очень трудно говорить без механической помощи. +language-RootSpeak-name = Песнь корней +language-RootSpeak-description = Странный шелестящий язык, на котором говорят дионы. +language-CodeSpeak-name = Кодовый язык +language-CodeSpeak-description = Оперативники синдиката могут использовать серию кодовых слов для передачи сложной информации, в то время как для любого слушателя они звучат как случайные понятия и напитки. +language-Nekomimetic-name = Некоязык +language-Nekomimetic-description = Для стороннего наблюдателя этот язык представляет собой непонятную смесь ломаного японского. Для фелинидов он каким-то образом понятен. +language-Draconic-name = Синта'унати +language-Draconic-description = Общий язык унатхов с преобладающими шипящими звуками. +language-Canilunzt-name = Канилунц +language-Canilunzt-description = Гортанный язык, на котором говорят и используют обитатели системы Ваззенда, состоящий из рычания, лая, тявканья и интенсивного использования движений ушей и хвоста, вулпканины говорят на этом языке с легкостью. + +language-SikTaj-name = Сик'тайр +language-SikTaj-description = Язык, на котором говорят и используют обитатели системы Адомай. Таяры говорят на этом языке с легкостью. + +language-Nian-name = Ткачий +language-Nian-description = Язык, состоящий из жужжащих звуков, на котором говорят и используют нианы. + +language-Fire-name = Огненный +language-Fire-description = Звуки огня, что каким-то образом складываются в осмысленную речь. + +language-SolCommon-name = Солнечный язык +language-SolCommon-description = Общий язык, на котором говорят обитатели солнечной системы. +language-Cat-name = Кошачий +language-Cat-description = Примитивные звуки, издаваемые кошками. Каким-то образом они передают смысл! + +language-Dog-name = Собачий +language-Dog-description = Лающие и рычащие звуки, используемые для передачи примитивных значений. + +language-Mothroach-name = Молиный +language-Mothroach-description = Милые пищащие звуки, из которых иногда складываются осмысленные фразы. + +language-Xeno-name = Ксено +language-Xeno-description = Давно забытый язык ксеноморфов. + +language-RobotTalk-name = Троичный +language-RobotTalk-description = Это не язык сам по себе, но он используется роботами и КПБ для обмена данными. + +language-Monkey-name = Обезьяний +language-Monkey-description = Набор звуков и жестов, издаваемых приматами с целью общения. + +language-Bee-name = Пчелиный +language-Bee-description = Странный язык, основанный на движениях, которые пчелы используют для общения. + +language-Mouse-name = Мышиный +language-Mouse-description = Милые пищащие звуки, которые мыши используют, чтобы выпрашивать еду. + +# These ones are half-assed because these creatures are almost never played as. +language-Chicken-name = Animal chicken +language-Chicken-description = A collection of sounds made by chickens. + +language-Duck-name = Animal duck +language-Duck-description = A collection of sounds made by ducks. + +language-Cow-name = Animal cow +language-Cow-description = A collection of sounds made by cows. + +language-Sheep-name = Animal sheep +language-Sheep-description = A collection of sounds made by sheep. + +language-Kangaroo-name = Animal kangaroo +language-Kangaroo-description = A collection of sounds made by kangaroos. + +language-Pig-name = Animal pig +language-Pig-description = A collection of sounds made by pigs. + +language-Moffic-name = Паучий +language-Moffic-description = Древний язык, на котором говорят арахниды. + +language-Drask-name = Орлуум +language-Drask-description = Монотонный, вибрирующий язык драсков. Он чем-то напоминает китовую песню. + +language-BorgTalk-name = Двоичный +language-BorgTalk-description = Нули и единицы, передающие массивные и не очень данные. + +language-Urs-name = Рыкрур +language-Urs-description = Басистый и рычащий язык, на котором говорят урсы. + +language-Arkane-name = Каукиттен +language-Arkane-description = Протяжный, чем-то напоминающий Солнечный язык, на котором говорят арканы. + +language-Shadowkin-name = Миар +language-Shadowkin-description = Загадочный язык, на котором говорят сумеречники. + +language-Dwarf-name = Шахтёрский +language-Dwarf-description = Rock and stone! + +language-Dev-name = Разработческий +language-Dev-description = Больше звучит как ругань покрытая кодом, чем язык. + +language-CintaTaj-name = Синта’Тайр +language-CintaTaj-description = Язык, разработанный таярами и унатхами для общения между двумя расами, представляет собой смесь шипений и слов. + +language-GalacticCommon = Общ. +language-Bubblish = Пузырчатый +language-RootSpeak = Песнь корней +language-CodeSpeak = Кодовый +language-Nekomimetic = Неко +language-Draconic = Синта'унати +language-Canilunzt = Канилунц +language-SikTaj = Сик'тайр +language-Nian = Ткачий +language-Fire = Огненный +language-SolCommon = Солнечный +language-Cat = Кошачий +language-Dog = Собачий +language-Mothroach = Молиный +language-Xeno = Ксено +language-RobotTalk = Троичный +language-Monkey = Обезьяний +language-Bee = Пчелиный +language-Mouse = Мышиный +language-Drask = Орлуум +# These ones are half-assed because these creatures are almost never played as. +language-Chicken = Animal chicken +language-Duck = Animal duck +language-Cow = Animal cow +language-Sheep = Animal sheep +language-Kangaroo = Animal kangaroo +language-Pig = Animal pig +language-Moffic = Паучий +language-BorgTalk = Двоичный +language-Urs = Рыкрур +language-Arkane = Каукиттен +language-Shadowkin = Миар +language-Dev = Разраб +language-Dwarf = Шахт +language-CintaTaj = Синта’тайр diff --git a/Resources/Locale/ru-RU/ADT/Research/translators.ftl b/Resources/Locale/ru-RU/ADT/Research/translators.ftl new file mode 100644 index 00000000000..13ef3294229 --- /dev/null +++ b/Resources/Locale/ru-RU/ADT/Research/translators.ftl @@ -0,0 +1,3 @@ +research-technology-basic-translation = Технология базовых переводчиков + +research-technology-advanced-translation = Технология продвинутых переводчиков diff --git a/Resources/Prototypes/ADT/Actions/language.yml b/Resources/Prototypes/ADT/Actions/language.yml new file mode 100644 index 00000000000..222a73c9ba0 --- /dev/null +++ b/Resources/Prototypes/ADT/Actions/language.yml @@ -0,0 +1,12 @@ +- type: entity + id: ActionLanguageMenu + name: language-menu-action + description: language-menu-action-desc + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + icon: _NF/Interface/Actions/language.png + event: !type:LanguageMenuActionEvent + useDelay: 2 + priority: -97 diff --git a/Resources/Prototypes/ADT/Entities/Objects/Device/translators.yml b/Resources/Prototypes/ADT/Entities/Objects/Device/translators.yml new file mode 100644 index 00000000000..97280e5348e --- /dev/null +++ b/Resources/Prototypes/ADT/Entities/Objects/Device/translators.yml @@ -0,0 +1,386 @@ +- type: entity + id: TranslatorUnpowered + parent: [ BaseItem ] + name: Translator + description: "Translates speech." + components: + - type: Item + sprite: _NF/Objects/Devices/translator.rsi + inhandVisuals: + left: + - state: inhand-left + right: + - state: inhand-right + - type: Sprite + sprite: _NF/Objects/Devices/translator.rsi + state: icon + layers: + - state: icon + - state: translator + shader: unshaded + visible: false + map: [ "enum.ToggleVisuals.Layer", "enum.PowerDeviceVisualLayers.Powered" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ToggleVisuals.Toggled: + enum.ToggleVisuals.Layer: + True: { visible: true } + False: { visible: false } + - type: HandheldTranslator + enabled: false + - type: ToggleableLightVisuals + spriteLayer: translator + inhandVisuals: + left: + - state: inhand-left-translator + shader: unshaded + right: + - state: inhand-right-translator + shader: unshaded + +- type: entity + id: Translator + parent: [ TranslatorUnpowered, PowerCellSlotMediumItem ] + suffix: Powered + components: + - type: PowerCellDraw + drawRate: 1 + +- type: entity + id: TranslatorEmtpy + parent: [ Translator ] + suffix: Empty + components: + - type: ItemSlots + slots: + cell_slot: + name: power-cell-slot-component-slot-name-default + +- type: entity + id: VulpTranslator + parent: [ Translator ] + name: Vulpkanin translator + description: "Used only by Vulpkanin to understand and speak with Galatic Common speakers." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + understood: + - GalacticCommon + requires: + - Canilunzt + requires-all: false + - type: PowerCellDraw + drawRate: 0.1 + - type: StaticPrice + price: 35 + +- type: entity + id: CanilunztTranslator + parent: [ TranslatorEmtpy ] + name: Canilunzt translator + description: "Translates speech between Canilunzt and Galactic Common. Commonly used by Vulpkanin to communicate with galactic common speakers" + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Canilunzt + understood: + - GalacticCommon + - Canilunzt + requires: + - GalacticCommon + - Canilunzt + requires-all: false + +- type: entity + id: BubblishTranslator + parent: [ TranslatorEmtpy ] + name: Bubblish translator + description: "Translates speech between Bubblish and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Bubblish + understood: + - GalacticCommon + - Bubblish + requires: + - GalacticCommon + - Bubblish + requires-all: false + +- type: entity + id: NekomimeticTranslator + parent: [ TranslatorEmtpy ] + name: Nekomimetic translator + description: "Translates speech between Nekomimetic and Galactic Common. Why would you want that?" + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Nekomimetic + understood: + - GalacticCommon + - Nekomimetic + requires: + - GalacticCommon + - Nekomimetic + requires-all: false + +- type: entity + id: DraconicTranslator + parent: [ TranslatorEmtpy ] + name: Draconic translator + description: "Translates speech between Draconic and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Draconic + understood: + - GalacticCommon + - Draconic + requires: + - GalacticCommon + - Draconic + requires-all: false + +- type: entity + id: SolCommonTranslator + parent: [ TranslatorEmtpy ] + name: Sol Common translator + description: "Translates speech between Sol Common and Galactic Common. Like a true Earthman!" + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - SolCommon + understood: + - GalacticCommon + - SolCommon + requires: + - GalacticCommon + - SolCommon + requires-all: false + +- type: entity + id: RootSpeakTranslator + parent: [ TranslatorEmtpy ] + name: RootSpeak translator + description: "Translates speech between RootSpeak and Galactic Common. Like a true plant?" + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - RootSpeak + understood: + - GalacticCommon + - RootSpeak + requires: + - GalacticCommon + - RootSpeak + requires-all: false + +- type: entity + id: MofficTranslator + parent: [ TranslatorEmtpy ] + name: Moffic translator + description: "Translates speech between Moffic and Galactic Common. Like a true moth... or bug?" + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Moffic + understood: + - GalacticCommon + - Moffic + requires: + - GalacticCommon + - Moffic + requires-all: false + +- type: entity + id: XenoTranslator + parent: [ TranslatorEmtpy ] + name: Xeno translator + description: "Translates speech between Xeno and Galactic Common. Not sure if that will help." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Xeno + understood: + - GalacticCommon + - Xeno + requires: + - GalacticCommon + +- type: entity + id: AnimalTranslator + parent: [ TranslatorEmtpy ] + name: Animal translator + description: "Translates all the cutes nosies that animals make into a more understandable form!" + components: + - type: HandheldTranslator + understood: + - Cat + - Dog + - Mothroach + - Monkey + - Bee + - Mouse + - Chicken + - Duck + - Cow + - Sheep + - Kangaroo + - Pig + requires: + - GalacticCommon + requires-all: false + +- type: entity + id: DraskTranslator + parent: [ TranslatorEmtpy ] + name: Orluum translator + description: "Translates speech between Orluum and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Drask + understood: + - GalacticCommon + - Drask + requires: + - GalacticCommon + - Drask + requires-all: false + + +- type: entity + id: ShadowkinTranslator + parent: [ TranslatorEmtpy ] + name: Shadowkin translator + description: "Translates speech between Shadowkin lang and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Shadowkin + understood: + - GalacticCommon + - Shadowkin + requires: + - GalacticCommon + - Shadowkin + requires-all: false + +- type: entity + id: ArkaneTranslator + parent: [ TranslatorEmtpy ] + name: Arkane translator + description: "Translates speech between Arkane lang and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Arkane + understood: + - GalacticCommon + - Arkane + requires: + - GalacticCommon + - Arkane + requires-all: false + +- type: entity + id: NianTranslator + parent: [ TranslatorEmtpy ] + name: Nian translator + description: "Translates speech between Nian lang and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Nian + understood: + - GalacticCommon + - Nian + requires: + - GalacticCommon + - Nian + requires-all: false + +- type: entity + id: FireTranslator + parent: [ TranslatorEmtpy ] + name: Fire translator + description: "Translates speech between Shadowkin lang and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - Fire + understood: + - GalacticCommon + - Fire + requires: + - GalacticCommon + - Fire + requires-all: false + +- type: entity + id: SikTajTranslator + parent: [ TranslatorEmtpy ] + name: SikTaj translator + description: "Translates speech between SikTaj and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - SikTaj + understood: + - GalacticCommon + - SikTaj + requires: + - GalacticCommon + - SikTaj + requires-all: false + +- type: entity + id: CintaTajTranslator + parent: [ TranslatorEmtpy ] + name: CintaTajTaj translator + description: "Translates speech between CintaTaj and Galactic Common." + components: + - type: HandheldTranslator + default-language: GalacticCommon + spoken: + - GalacticCommon + - CintaTaj + understood: + - GalacticCommon + - CintaTaj + requires: + - GalacticCommon + - CintaTaj + requires-all: false diff --git a/Resources/Prototypes/ADT/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/ADT/Entities/Objects/Misc/implanters.yml new file mode 100644 index 00000000000..3ebd7c90366 --- /dev/null +++ b/Resources/Prototypes/ADT/Entities/Objects/Misc/implanters.yml @@ -0,0 +1,287 @@ +- type: entity + id: BaseTranslatorImplanter + parent: [ BaseItem ] + name: Basic translator implant + description: "Translates speech." + abstract: true + components: + - type: Sprite + sprite: Objects/Specific/Medical/implanter.rsi + state: implanter0 + layers: + - state: implanter1 + map: [ "implantFull" ] + visible: true + - state: implanter0 + map: [ "implantBroken" ] + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ImplanterVisuals.Full: + implantFull: + True: {visible: true} + False: {visible: false} + implantBroken: + True: {visible: false} + False: {visible: true} + +- type: entity + id: BasicGalaticCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Basic Galatic Common translator implant + description: "An implant giving the ability to understand Galatic Common." + components: + - type: TranslatorImplanter + understood: + - GalacticCommon + +- type: entity + id: AdvancedGalaticCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Advanced Galatic Common translator implant + description: "An implant giving the ability to understand and speak Galatic Common." + components: + - type: TranslatorImplanter + spoken: + - GalacticCommon + understood: + - GalacticCommon + +- type: entity + id: BubblishTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Bubblish translator implant + description: "An implant giving the ability to understand and speak Bubblish." + components: + - type: TranslatorImplanter + spoken: + - Bubblish + understood: + - Bubblish + +- type: entity + id: NekomimeticTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Nekomimetic translator implant + description: "An implant giving the ability to understand and speak Nekomimetic, Nya~!" + components: + - type: TranslatorImplanter + spoken: + - Nekomimetic + understood: + - Nekomimetic + +- type: entity + id: DraconicTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Draconic translator implant + description: "An implant giving the ability to understand and speak Draconic." + components: + - type: TranslatorImplanter + spoken: + - Draconic + understood: + - Draconic + +- type: entity + id: CanilunztTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Canilunzt translator implant + description: "An implant giving the ability to understand and speak Canilunzt, Yeeps!" + components: + - type: TranslatorImplanter + spoken: + - Canilunzt + understood: + - Canilunzt + +- type: entity + id: SolCommonTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: SolCommon translator implant + description: "An implant giving the ability to understand and speak SolCommon, raaagh!" + components: + - type: TranslatorImplanter + spoken: + - SolCommon + understood: + - SolCommon + +- type: entity + id: RootSpeakTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: RootSpeak translator implant + description: "An implant giving the ability to understand and speak RootSpeak." + components: + - type: TranslatorImplanter + understood: + - RootSpeak + +- type: entity + id: MofficTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Moffic translator implant + description: "An implant giving the ability to understand and speak Moffic." + components: + - type: TranslatorImplanter + spoken: + - Moffic + understood: + - Moffic + +- type: entity + id: CodeSpeakImplanter + parent: [ BaseTranslatorImplanter ] + name: CodeSpeak Implanter + description: "\"CodeSpeak(tm) - Secure your communication with metaphors so elaborate, they seem randomly generated!\"" + components: + - type: TranslatorImplanter + spoken: + - CodeSpeak + understood: + - CodeSpeak + - type: StaticPrice + price: 150 + +- type: entity + id: SikTajTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: SikTaj translator implant + description: "An implant giving the ability to understand and speak SikTaj." + components: + - type: TranslatorImplanter + spoken: + - SikTaj + understood: + - SikTaj + +- type: entity + id: NianTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Nian translator implant + description: "An implant giving the ability to understand and speak Nian." + components: + - type: TranslatorImplanter + spoken: + - Nian + understood: + - Nian + +- type: entity + id: FireTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Nian translator implant + description: "An implant giving the ability to understand and speak Nian." + components: + - type: TranslatorImplanter + understood: + - Fire + +- type: entity + id: DraskTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Orluum translator implant + description: "An implant giving the ability to understand and speak Orluum." + components: + - type: TranslatorImplanter + spoken: + - Drask + understood: + - Drask + +- type: entity + id: UrsTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Urs translator implant + description: "An implant giving the ability to understand and speak Ursus language." + components: + - type: TranslatorImplanter + spoken: + - Urs + understood: + - Urs + +- type: entity + id: ArkaneTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Arkane translator implant + description: "An implant giving the ability to understand and speak Arcane language." + components: + - type: TranslatorImplanter + spoken: + - Arkane + understood: + - Arkane + +- type: entity + id: ShadowkinTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Shadowkin translator implant + description: "An implant giving the ability to understand and speak Shadowkin language." + components: + - type: TranslatorImplanter + spoken: + - Shadowkin + understood: + - Shadowkin + +- type: entity + id: BorgTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Borg translator implant + description: "An implant giving the ability to understand and speak Binary." + components: + - type: TranslatorImplanter + spoken: + - BorgTalk + understood: + - BorgTalk + +- type: entity + id: CintaTajTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: CintaTaj translator implant + description: "An implant giving the ability to understand and speak CintaTaj." + components: + - type: TranslatorImplanter + spoken: + - CintaTaj + understood: + - CintaTaj + + +- type: entity + id: SyndUniversalTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: universal translator implant + description: "An implant giving the ability to understand any racial language." + components: + - type: TranslatorImplanter + understood: + - RobotTalk + - Shadowkin + - Arkane + - Urs + - Drask + - Fire + - Nian + - SikTaj + - Moffic + - RootSpeak + - SolCommon + - Canilunzt + - Draconic + - Bubblish + - Nekomimetic + +- type: entity + id: DevTranslatorImplanter + parent: [ BaseTranslatorImplanter ] + name: Dev translator implant + description: "e" + components: + - type: TranslatorImplanter + spoken: + - Dev + understood: + - Dev diff --git a/Resources/Prototypes/ADT/Languages/languages.yml b/Resources/Prototypes/ADT/Languages/languages.yml new file mode 100644 index 00000000000..10dde690b5b --- /dev/null +++ b/Resources/Prototypes/ADT/Languages/languages.yml @@ -0,0 +1,949 @@ +# The universal language, assumed if the entity has a UniversalLanguageSpeakerComponent. +# Do not use otherwise. Try to use the respective component instead of this language. +- type: language + id: Universal + obfuscateSyllables: false + replacement: + - "*incomprehensible*" + +# The common galactic tongue. +- type: language + id: GalacticCommon + obfuscateSyllables: true + replacement: + - Бла + - Бла + - Бла + - динг-донг + - динг + - донг + - жиббер-жаббер + - жуббр + - бле + - зиппти + - зуп + - виббл + - воббл + - виггл + - яда + - мех + - нех + - нах + - вах + +# Spoken by slimes. +- type: language + id: Bubblish + obfuscateSyllables: true + replacement: + - блоб + - плоп + - поп + - боп + - буп + - блюп + - бульп + - блеб + +# Spoken by moths. +- type: language + id: Moffic + obfuscateSyllables: true + replacement: + - år + - i + - går + - sek + - mo + - ff + - ok + - gj + - ø + - gå + - la + - le + - lit + - ygg + - van + - dår + - næ + - møt + - idd + - hvo + - ja + - på + - han + - så + - ån + - det + - att + - nå + - gö + - bra + - int + - tyc + - om + - när + - två + - må + - dag + - sjä + - vii + - vuo + - eil + - tun + - käyt + - teh + - vä + - hei + - huo + - suo + - ää + - ten + - ja + - heu + - stu + - uhr + - kön + - we + - hön + + # Spoken by dionas. +- type: language + id: RootSpeak + obfuscateSyllables: true + replacement: + - хс + - зт + - кр + - ст + - ш + - шш + +# Syndicate operatives can use a series of codewords to convey complex information. +- type: language + id: CodeSpeak + obfuscateSyllables: true + replacement: + - Белый русский + - Представитель станции + - Шериф + - Депутат + - Хранилище + - Фронтир + - Станция + - Безопасность + - Кофе + - Кола + - Вода + - Инженерный + - Капитан + - Вызываю друга + - Атмос + - Робототехника + - Медицинский + - ИИ + - Человек + - Вульпканин + - Унатх + - Ниан + - Чай + - Стул + - Кресло + - Корабль + - Шаттл + - Оружие + - Пушки + - Лазер + - Карп + - Космический Карп + - Ксено + - Ксеноморф + - Биоугроза + - Деньги + - Космос + - Опасность + - Обезьяна + - Пун-пун + - Внутри + - Бежит + - Убивает + - Убить + - Спасти + - Жизнь + - Дракон + - Ниндзя + - Секрет + +# A mess of broken Japanese, spoken by Felinds and Oni +- type: language + id: Nekomimetic + obfuscateSyllables: true + replacement: + - неко + - нян + - мими + - ми + - мофу + - фува + - кьяя + - кавай + - пока + - муня + - пуни + - мунью + - уфуфу + - ича + - доки + - кьюн + - кусу + - ня + - няя + - десу + - кис + - ама + - чуу + - бака + - хево + - буп + - гато + - кит + - сюн + - йори + - соу + - бака + - чан + - сан + - кун + - махо + - йатта + - сукки + - усаги + - домо + - ори + - ува + - заазаа + - шику + - пуру + - ира + - хето + - етто + +# Spoken by the Lizard race. +- type: language + id: Draconic + obfuscateSyllables: true + replacement: + - za + - az + - ze + - ez + - zi + - iz + - zo + - oz + - zu + - uz + - zs + - sz + - ha + - ah + - he + - eh + - hi + - ih + - ho + - oh + - hu + - uh + - hs + - sh + - la + - al + - le + - el + - li + - il + - lo + - ol + - lu + - ul + - ls + - sl + - ka + - ak + - ke + - ek + - ki + - ik + - ko + - ok + - ku + - uk + - ks + - sk + - sa + - as + - se + - es + - si + - is + - so + - os + - su + - us + - ss + - ss + - ra + - ar + - re + - er + - ri + - ir + - ro + - or + - ru + - ur + - rs + - sr + - a + - a + - e + - e + - i + - i + - o + - o + - u + - u + - s + - s + +# Spoken by the Vulpkanin race. +- type: language + id: Canilunzt + obfuscateSyllables: true + replacement: + - rur + - ya + - cen + - rawr + - bar + - kuk + - tek + - qat + - uk + - wu + - vuh + - tah + - tch + - schz + - auch + - ist + - ein + - entch + - zwichs + - tut + - mir + - wo + - bis + - es + - vor + - nic + - gro + - lll + - enem + - zandt + - tzch + - noch + - hel + - ischt + - far + - wa + - baram + - iereng + - tech + - lach + - sam + - mak + - lich + - gen + - or + - ag + - eck + - gec + - stag + - onn + - bin + - ket + - jarl + - vulf + - einech + - cresthz + - azunein + - ghzth + +# Spoken by the Tajaran race. +- type: language + id: SikTaj + obfuscateSyllables: true + replacement: + - rae + - ye + - cen + - prr + - mer + - kuk + - tek + - qar + - ud + - wu + - wuh + - "taj'" + - tch + - ser + - "'aerch" + - it + - ei + - endr + - zeihs + - tet + - mir + - wo + - bis + - es + - voj + - jic + - fro + - rrr + - ener + - zajet + - th + - nort + - hel + - ist + - far + - wa + - bara + - ierg + - tech + - lach + - sam + - mak + - "'lirch" + - get + - or + - "jag'" + - eck + - gec + - stag + - "'onn" + - bin + - ket + - "jart'" + - vul + - einech + - cres + - "'eirr" + - ghh + +# Spoken by the Nian race. +- type: language + id: Nian + obfuscateSyllables: true + replacement: + - жз + - эзз + - ззе + - ржез + - зшусж + - жсса + - жж + - жжс + - еж + - шшз + - ззуш + - жашшз + - ззис + - ссж + - ужжс + - сз + - жш + - эшзз + - жесш + - шжш + - жуж + - ж + - ш + - з + - ззз + - зсе + - фжж + - зсжу + +# Spoken by the Novakid race. +- type: language + id: Fire + obfuscateSyllables: true + replacement: + - шш + - вшш + - шжшш + - сшшшс + - шсжсс + - ссш + - сс + - жжс + - жшж + + +# The common language of the Sol system. +- type: language + id: SolCommon + obfuscateSyllables: true + replacement: + - тао + - ши + - тзу + - йи + - ком + - би + - ис + - я + - оп + - ви + - эд + - лек + - мо + - кле + - те + - дис + - ее + +# Languages spoken by various critters. +- type: language + id: Cat + obfuscateSyllables: true + replacement: + - мурр + - мяу + - мурр + - мрау + +- type: language + id: Dog + obfuscateSyllables: true + replacement: + - вуф + - гав + - гаф + - рафф + - гарр + +- type: language + id: Mothroach + obfuscateSyllables: false + replacement: + - Бзз + - Чрик + - Скуик + - Пип + - Ииии + - Иип + +- type: language + id: Xeno + obfuscateSyllables: true + replacement: + - ссс + - сСс + - ССС +# IPC +- type: language + id: RobotTalk + obfuscateSyllables: true + replacement: + - "0" + - "1" + - "2" +# Borgs +- type: language + id: BorgTalk + obfuscateSyllables: true + replacement: + - "0" + - "1" + +- type: language + id: Monkey + obfuscateSyllables: true + replacement: + - аг + - ааг + - аааг + - ааааг + - аааааг + +- type: language + id: Bee + obfuscateSyllables: false + replacement: + - Бз + - Бзз + - Бззз + - Бзззз + - Бззззз + +- type: language + id: Mouse + obfuscateSyllables: false + replacement: + - Скуик + - Пип + - Чуу + - Ииии + - Пип + +- type: language + id: Chicken + obfuscateSyllables: false + replacement: + - Coo + - Coot + - Cooot + +- type: language + id: Duck + obfuscateSyllables: false + replacement: + - Quack + +- type: language + id: Cow + obfuscateSyllables: false + replacement: + - Moo + - Mooo + +- type: language + id: Sheep + obfuscateSyllables: false + replacement: + - Ba + - Baa + - Baaa + +- type: language + id: Kangaroo + obfuscateSyllables: false + replacement: + - Shreak + - Chuu + +- type: language + id: Pig + obfuscateSyllables: false + replacement: + - Oink + +# Spoken by the Drask race. +- type: language + id: Drask + obfuscateSyllables: true + replacement: + - овв + - оумн + - мноо + - румум + - ваар + - дромнн + - руум + - гоом + - фмонг + - оорм + - гаар + - хоорб + - саар + - ссооумн + - гнии + - вииск + - вррм + +# Spoken by the Ursus race. +- type: language + id: Urs + obfuscateSyllables: true + replacement: + - раа + - ишш + - тзуур + - арр + - кромм + - би + - инн + - рр + - оот + - вирр + - эк + - лерр + - мои + - крр + - тее + - ррс + - аа + - ир + - ррэк + - ер + - маа + - ке + - етт + +# Spoken by the Arkane race. +- type: language + id: Arkane + obfuscateSyllables: true + replacement: + - рииа + - инн + - тлаа + - айр + - коо + - бии + - иинта + - реаа + - онт + - виер + - энк + - лааир + - оии + - кееа + - теу + - риина + - ин + +# Spoken by the Shadowkin race. +- type: language + id: Shadowkin + obfuscateSyllables: true + replacement: + - mia + - ar + - ren + - naar + - mae + - tre + - ien + - trr + - ou + - rin + - nae + - ena + - oi + - kerr + - que + - uoh + - een + +# Spoken by the Dwarf race. +- type: language + id: Dwarf + obfuscateSyllables: true + replacement: + - арр + - рок + - стн + - шахт + - руд + - трэ + - кирк + - диг + - йи + - мыэ + - молл + - бур + - пив + - стн + - дрг + - эйе + +# Spoken by the Dwarf race. +- type: language + id: CintaTaj + obfuscateSyllables: true + replacement: + - za + - az + - ze + - ez + - zi + - iz + - zo + - oz + - zu + - uz + - zs + - sz + - ha + - ah + - he + - eh + - hi + - ih + - ho + - oh + - hu + - uh + - hs + - sh + - la + - al + - le + - el + - li + - il + - lo + - ol + - lu + - ul + - ls + - sl + - ka + - ak + - ke + - ek + - ki + - ik + - ko + - ok + - ku + - uk + - ks + - sk + - sa + - as + - se + - es + - si + - is + - so + - os + - su + - us + - ss + - ss + - ra + - ar + - re + - er + - ri + - ir + - ro + - or + - ru + - ur + - rs + - sr + - a + - a + - e + - e + - i + - i + - o + - o + - u + - u + - s + - s + - rae + - ye + - cen + - prr + - mer + - kuk + - tek + - qar + - ud + - wu + - wuh + - "taj'" + - tch + - ser + - "'aerch" + - it + - ei + - endr + - zeihs + - tet + - mir + - wo + - bis + - es + - voj + - jic + - fro + - rrr + - ener + - zajet + - th + - nort + - hel + - ist + - far + - wa + - bara + - ierg + - tech + - lach + - sam + - mak + - "'lirch" + - get + - or + - "jag'" + - eck + - gec + - stag + - "'onn" + - bin + - ket + - "jart'" + - vul + - einech + - cres + - "'eirr" + - ghh + +# Here goes admeme languages. +- type: language + id: Dev + obfuscateSyllables: true + replacement: + - су + - ка + - офф + - ямл + - бл + - пид + - рас + - о + - виз + - код + - с# + - мета + - билд + - сук + - гни + - эмис + - баг + - да + - бля + - гит + - мердж + - пулл + - ПР + - огг + - пнг + - робст + - релз + - дебаг diff --git a/Resources/Prototypes/ADT/Recipes/Lathes/translators.yml b/Resources/Prototypes/ADT/Recipes/Lathes/translators.yml new file mode 100644 index 00000000000..233acfe64dd --- /dev/null +++ b/Resources/Prototypes/ADT/Recipes/Lathes/translators.yml @@ -0,0 +1,358 @@ +# Translators +- type: latheRecipe + id: CanilunztTranslator + result: CanilunztTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: BubblishTranslator + result: BubblishTranslator + completetime: 2 + materials: + Steel: 500 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: NekomimeticTranslator + result: NekomimeticTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: DraconicTranslator + result: DraconicTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: SolCommonTranslator + result: SolCommonTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: RootSpeakTranslator + result: RootSpeakTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: MofficTranslator + result: MofficTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: BasicGalaticCommonTranslatorImplanter + result: BasicGalaticCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: XenoTranslator + result: XenoTranslator + completetime: 2 + materials: + Steel: 200 + Plastic: 50 + Gold: 50 + Plasma: 50 + Silver: 50 + +- type: latheRecipe + id: AdvancedGalaticCommonTranslatorImplanter + result: AdvancedGalaticCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: BubblishTranslatorImplanter + result: BubblishTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: NekomimeticTranslatorImplanter + result: NekomimeticTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: DraconicTranslatorImplanter + result: DraconicTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: CanilunztTranslatorImplanter + result: CanilunztTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: SolCommonTranslatorImplanter + result: SolCommonTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: RootSpeakTranslatorImplanter + result: RootSpeakTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: MofficTranslatorImplanter + result: MofficTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: AnimalTranslator + result: AnimalTranslator + completetime: 2 + materials: + Steel: 200 + Plastic: 50 + Gold: 50 + Plasma: 50 + Silver: 5 + +- type: latheRecipe + id: SikTajTranslatorImplanter + result: SikTajTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: NianTranslatorImplanter + result: NianTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: FireTranslatorImplanter + result: FireTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: DraskTranslator + result: DraskTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: DraskTranslatorImplanter + result: DraskTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: UrsTranslatorImplanter + result: UrsTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: ArkaneTranslatorImplanter + result: ArkaneTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: ShadowkinTranslatorImplanter + result: ShadowkinTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +- type: latheRecipe + id: CintaTajTranslatorImplanter + result: CintaTajTranslatorImplanter + completetime: 2 + materials: + Steel: 500 + Glass: 500 + Plastic: 100 + Gold: 50 + Silver: 50 + +#- type: latheRecipe +# id: UrsTranslator +# result: UrsTranslator +# completetime: 2 +# materials: +# Steel: 500 +# Glass: 100 +# Plastic: 50 +# Gold: 50 + +- type: latheRecipe + id: ArkaneTranslator + result: ArkaneTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: ShadowkinTranslator + result: ShadowkinTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: NianTranslator + result: NianTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: FireTranslator + result: FireTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: SikTajTranslator + result: SikTajTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 + +- type: latheRecipe + id: CintaTajTranslator + result: CintaTajTranslator + completetime: 2 + materials: + Steel: 500 + Glass: 100 + Plastic: 50 + Gold: 50 diff --git a/Resources/Prototypes/ADT/Research/translators.yml b/Resources/Prototypes/ADT/Research/translators.yml new file mode 100644 index 00000000000..a687d06a53d --- /dev/null +++ b/Resources/Prototypes/ADT/Research/translators.yml @@ -0,0 +1,59 @@ +# Tier 2 - entrypoint to translators +- type: technology + id: BasicTranslation + name: research-technology-basic-translation + icon: + sprite: _NF/Objects/Devices/translator.rsi + state: icon + discipline: CivilianServices + tier: 2 + cost: 10000 + recipeUnlocks: + - CanilunztTranslator + - BubblishTranslator + - NekomimeticTranslator + - DraconicTranslator + - SolCommonTranslator + - RootSpeakTranslator + - BasicGalaticCommonTranslatorImplanter + - MofficTranslator + - DraskTranslator +# - UrsTranslator # Закомменчено до раундстарт урсов + - ArkaneTranslator + - ShadowkinTranslator + - NianTranslator + - FireTranslator + - SikTajTranslator + - CintaTajTranslator + + +# Frontier - languages mechanic +- type: technology + id: AdvancedTranslation + name: research-technology-advanced-translation + icon: + sprite: _NF/Objects/Devices/translator.rsi + state: icon + discipline: CivilianServices + tier: 3 + cost: 15000 + recipeUnlocks: + - XenoTranslator + - AdvancedGalaticCommonTranslatorImplanter + - BubblishTranslatorImplanter + - NekomimeticTranslatorImplanter + - DraconicTranslatorImplanter + - CanilunztTranslatorImplanter + - SolCommonTranslatorImplanter + - RootSpeakTranslatorImplanter + - AnimalTranslator + - MofficTranslatorImplanter + - DraskTranslatorImplanter + - SikTajTranslatorImplanter + - NianTranslatorImplanter + - FireTranslatorImplanter + - DraskTranslatorImplanter +# - UrsTranslatorImplanter # Закомменчено до раундстарт урсов + - ArkaneTranslatorImplanter + - ShadowkinTranslatorImplanter + - CintaTajTranslatorImplanter diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index a79fbfbf246..baf7d7ade0b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -62,6 +62,11 @@ - type: Tag tags: - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Mouse + understands: + - Mouse - type: entity name: bee @@ -1046,6 +1051,11 @@ - type: HTN rootTask: task: SimpleHostileCompound + - type: LanguageSpeaker # Frontier + speaks: + - Monkey + understands: + - Monkey - type: entity name: kangaroo @@ -1282,6 +1292,11 @@ Burn: 3 clumsySound: path: /Audio/Animals/monkey_scream.ogg + - type: LanguageSpeaker # Frontier + speaks: + - Monkey + understands: + - Monkey - type: entity name: monkey @@ -1312,6 +1327,12 @@ - type: GhostTakeoverAvailable - type: Loadout prototypes: [SyndicateOperativeGearMonkey] + - type: LanguageSpeaker # Frontier + speaks: + - Monkey + understands: + - GalacticCommon + - Monkey - type: entity id: MobMonkeySyndicateAgent @@ -1471,6 +1492,9 @@ - Syndicate - type: Loadout prototypes: [SyndicateOperativeGearMonkey] + - type: LanguageSpeaker # Frontier + understands: + - GalacticCommon - type: entity id: MobKoboldSyndicateAgent @@ -1655,6 +1679,11 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: LanguageSpeaker # Frontier + speaks: + - Mouse + understands: + - Mouse - type: entity parent: MobMouse @@ -2613,6 +2642,11 @@ - type: Tag tags: - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Dog + understands: + - Dog - type: entity name: corrupted corgi @@ -2766,6 +2800,11 @@ - type: Tag tags: - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Cat + understands: + - Cat - type: entity name: calico cat @@ -3161,6 +3200,11 @@ - type: FireVisuals sprite: Mobs/Effects/onfire.rsi normalState: Mouse_burning + - type: LanguageSpeaker # Frontier + speaks: + - Mouse + understands: + - Mouse - type: entity name: pig diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml index 4436ebc612c..fe265c7cb61 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml @@ -36,6 +36,12 @@ - VimPilot - type: StealTarget stealGroup: AnimalIan + - type: LanguageSpeaker # Frontier + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: entity name: Old Ian @@ -121,6 +127,12 @@ tags: - CannotSuicide - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Cat + understands: + - GalacticCommon + - Cat - type: entity name: Exception @@ -139,6 +151,12 @@ tags: - CannotSuicide - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Cat + understands: + - GalacticCommon + - Cat - type: entity name: Floppa @@ -164,6 +182,12 @@ tags: - CannotSuicide - VimPilot + - type: LanguageSpeaker # Frontier + speaks: + - Cat + understands: + - GalacticCommon + - Cat - type: entity name: Bandito @@ -295,6 +319,12 @@ - VimPilot - type: StealTarget stealGroup: AnimalMcGriff + - type: LanguageSpeaker # Frontier + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: entity name: Paperwork @@ -388,6 +418,12 @@ - VimPilot - type: StealTarget stealGroup: AnimalWalter + - type: LanguageSpeaker # Frontier + speaks: + - Dog + understands: + - GalacticCommon + - Dog - type: entity name: Morty @@ -577,6 +613,12 @@ - Hamster - VimPilot - ChefPilot + - type: LanguageSpeaker # Frontier + speaks: + - Mouse + understands: + - GalacticCommon + - Mouse - type: entity name: Shiva @@ -781,6 +823,12 @@ attributes: proper: true gender: male + - type: LanguageSpeaker # Frontier + speaks: + - Monkey + understands: + - GalacticCommon + - Monkey - type: entity name: Tropico diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml index d63afd4fa4f..eefaedb47f4 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml @@ -97,3 +97,8 @@ - RevenantTheme - type: Speech speechVerb: Ghost + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + understands: + - GalacticCommon diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 1686b723b59..28758fb95b2 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -109,6 +109,13 @@ - type: StepTriggerImmune - type: NoSlip - type: Insulated + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - BorgTalk + understands: + - GalacticCommon + - BorgTalk - type: entity parent: MobSiliconBase diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml index c50ad70ea24..d4970d88de1 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/slimes.yml @@ -112,6 +112,11 @@ speechSounds: Slime - type: TypingIndicator proto: slime + - type: LanguageSpeaker # Frontier + speaks: + - Bubblish + understands: + - Bubblish - type: entity name: basic slime diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 9fb0be02aec..3cf6fc69f52 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -119,6 +119,11 @@ molsPerSecondPerUnitMass: 0.0005 - type: Speech speechVerb: LargeMob + - type: LanguageSpeaker # Frontier + speaks: + - Xeno + understands: + - Xeno - type: entity name: Praetorian diff --git a/Resources/Prototypes/Entities/Mobs/Player/observer.yml b/Resources/Prototypes/Entities/Mobs/Player/observer.yml index 85be42cc025..0f826936a5c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/observer.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/observer.yml @@ -48,6 +48,7 @@ - type: Tag tags: - BypassInteractionRangeChecks + - type: UniversalLanguageSpeaker # Frontier / Ghosts should understand any language. - type: entity id: ActionGhostBoo diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index 8b3c66d5dd1..e1ec78b4197 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -122,6 +122,13 @@ sprite: "Effects/creampie.rsi" state: "creampie_arachnid" visible: false + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - Moffic + understands: + - GalacticCommon + - Moffic - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index d956f1871d7..ee19b937ce2 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -221,6 +221,12 @@ - CanPilot - FootstepSound - DoorBumpOpener + # Frontier - languages mechanic + - type: LanguageSpeaker + speaks: + - GalacticCommon + understands: + - GalacticCommon - type: entity save: false diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index ba1a2cbf76f..98341e8d12e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -102,6 +102,13 @@ actionPrototype: DionaGibAction allowedStates: - Dead + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - RootSpeak + understands: + - GalacticCommon + - RootSpeak - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index 5a54b56c48e..d332914399b 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -56,6 +56,13 @@ hideLayersOnEquip: - Hair - Snout + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - Dwarf + understands: + - GalacticCommon + - Dwarf - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index 6716d3902b8..1152f654daa 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -20,6 +20,13 @@ hideLayersOnEquip: - Hair - Snout + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - SolCommon + understands: + - GalacticCommon + - SolCommon - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index f6fde849efe..a2730b6d7e9 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -118,6 +118,13 @@ sprite: "Effects/creampie.rsi" state: "creampie_moth" visible: false + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - Nian + understands: + - GalacticCommon + - Nian - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index ad543620cf8..4d096902015 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -62,6 +62,15 @@ types: Heat : 1.5 #per second, scales with temperature & other constants - type: Wagging + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + - Draconic + - CintaTaj + understands: + - GalacticCommon + - Draconic + - CintaTaj - type: entity parent: BaseSpeciesDummy diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index caa3690e5d2..7c76d34222e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -110,6 +110,13 @@ types: Asphyxiation: -1.0 maxSaturation: 15 + - type: LanguageSpeaker + speaks: + - GalacticCommon + - Bubblish + understands: + - GalacticCommon + - Bubblish - type: entity parent: MobHumanDummy diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index d973c5c607c..c3b4bbbb829 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -46,6 +46,11 @@ - type: TTS # Corvax-TTS - type: RequireProjectileTarget active: False + - type: LanguageSpeaker # Frontier + speaks: + - GalacticCommon + understands: + - GalacticCommon # Used for mobs that have health and can take damage. - type: entity diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 8021d39028a..28f5796c710 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -333,6 +333,42 @@ - FauxTileAstroSnow - OreBagOfHolding - DeviceQuantumSpinInverter + # vv Frontier - languages mechanic vv + - CanilunztTranslator + - BubblishTranslator + - NekomimeticTranslator + - DraconicTranslator + - SolCommonTranslator + - RootSpeakTranslator + - XenoTranslator + - DraskTranslator + - BasicGalaticCommonTranslatorImplanter + - AdvancedGalaticCommonTranslatorImplanter + - BubblishTranslatorImplanter + - NekomimeticTranslatorImplanter + - DraconicTranslatorImplanter + - CanilunztTranslatorImplanter + - SolCommonTranslatorImplanter + - RootSpeakTranslatorImplanter + - AnimalTranslator + - MofficTranslatorImplanter + - MofficTranslator + - SikTajTranslatorImplanter + - NianTranslatorImplanter + - FireTranslatorImplanter + - DraskTranslatorImplanter +# - UrsTranslatorImplanter # Закомменчено до раундстарт урсов + - ArkaneTranslatorImplanter + - ShadowkinTranslatorImplanter + - CintaTajTranslatorImplanter +# - UrsTranslator # Закомменчено до раундстарт урсов + - ArkaneTranslator + - ShadowkinTranslator + - NianTranslator + - FireTranslator + - SikTajTranslator + - CintaTajTranslator + # ^^ Frontier - languages mechanic ^^ - type: EmagLatheRecipes emagDynamicRecipes: - BoxBeanbag diff --git a/Resources/Textures/_NF/Interface/Actions/language.png b/Resources/Textures/_NF/Interface/Actions/language.png new file mode 100644 index 00000000000..51962871ac7 Binary files /dev/null and b/Resources/Textures/_NF/Interface/Actions/language.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/icon.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/icon.png new file mode 100644 index 00000000000..92c3c4ab249 Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/icon.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left-translator.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left-translator.png new file mode 100644 index 00000000000..84add80f419 Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left-translator.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left.png new file mode 100644 index 00000000000..7e46e55fb4e Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-left.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right-translator.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right-translator.png new file mode 100644 index 00000000000..76a1db299de Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right-translator.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right.png new file mode 100644 index 00000000000..f6c8e44768e Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/inhand-right.png differ diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/meta.json b/Resources/Textures/_NF/Objects/Devices/translator.rsi/meta.json new file mode 100644 index 00000000000..fbf835d9249 --- /dev/null +++ b/Resources/Textures/_NF/Objects/Devices/translator.rsi/meta.json @@ -0,0 +1,43 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Created by discord:auriss093 для Времени Приключений MRP", + "states": [ + { + "name": "icon" + }, + { + "name": "translator", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-left-translator", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-right-translator", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/_NF/Objects/Devices/translator.rsi/translator.png b/Resources/Textures/_NF/Objects/Devices/translator.rsi/translator.png new file mode 100644 index 00000000000..9a9efb98423 Binary files /dev/null and b/Resources/Textures/_NF/Objects/Devices/translator.rsi/translator.png differ