From 8f61a36ce923c50e589e812edb299052a726cf1a Mon Sep 17 00:00:00 2001 From: mqole Date: Wed, 30 Oct 2024 12:35:56 +1100 Subject: [PATCH] Reverting language (commits 6459be64, b9e5f42c) --- Content.Client/Input/ContentContexts.cs | 1 - .../Options/UI/Tabs/KeyRebindTab.xaml.cs | 1 - .../MenuBar/GameTopMenuBarUIController.cs | 4 - .../MenuBar/Widgets/GameTopMenuBar.xaml | 10 - .../Language/LanguageMenuWindow.xaml | 18 - .../Language/LanguageMenuWindow.xaml.cs | 143 ---- .../Language/Systems/LanguageSystem.cs | 61 -- .../Language/LanguageMenuUIController.cs | 87 --- .../Administration/Commands/DSay.cs | 3 +- .../Administration/Commands/OSay.cs | 6 +- .../EntitySystems/AdvertiseSystem.cs | 3 +- .../EntitySystems/SpeakOnUIClosedSystem.cs | 3 +- .../Body/Systems/RespiratorSystem.cs | 3 +- Content.Server/Chat/Commands/LOOCCommand.cs | 3 +- Content.Server/Chat/Commands/MeCommand.cs | 3 +- Content.Server/Chat/Commands/SayCommand.cs | 3 +- .../Chat/Commands/WhisperCommand.cs | 3 +- .../Chat/Systems/AutoEmoteSystem.cs | 3 +- .../Chat/Systems/ChatSystem.Emote.cs | 3 +- Content.Server/Chat/Systems/ChatSystem.cs | 235 +++--- .../Chat/Systems/EmoteOnDamageSystem.cs | 4 +- .../Chat/Systems/SpeakOnUseSystem.cs | 3 +- Content.Server/Cloning/CloningSystem.cs | 5 +- Content.Server/Cluwne/CluwneSystem.cs | 5 +- Content.Server/EntityEffects/Effects/Emote.cs | 3 +- .../EntityEffects/Effects/MakeSentient.cs | 18 - Content.Server/Magic/MagicSystem.cs | 3 +- Content.Server/Medical/DefibrillatorSystem.cs | 7 +- .../Mind/Commands/MakeSentientCommand.cs | 13 - Content.Server/Mobs/CritMobActionsSystem.cs | 3 +- .../Operators/SayKeyOperator.cs | 3 +- .../PrimitiveTasks/Operators/SpeakOperator.cs | 3 +- .../Specific/MedibotInjectOperator.cs | 3 +- .../Radio/EntitySystems/HeadsetSystem.cs | 16 +- .../Radio/EntitySystems/RadioDeviceSystem.cs | 5 +- .../Radio/EntitySystems/RadioSystem.cs | 75 +- Content.Server/Radio/RadioEvent.cs | 15 +- Content.Server/RatKing/RatKingSystem.cs | 3 +- .../Speech/EntitySystems/ListeningSystem.cs | 8 +- Content.Server/Speech/Muting/MutingSystem.cs | 6 +- .../SurveillanceCameraSpeakerSystem.cs | 4 +- .../Weapons/Melee/MeleeWeaponSystem.cs | 3 +- .../Language/Commands/AdminLanguageCommand.cs | 77 -- .../Commands/AdminTranslatorCommand.cs | 156 ---- .../Language/Commands/ListLanguagesCommand.cs | 58 -- .../Language/Commands/SayLanguageCommand.cs | 52 -- .../Commands/SelectLanguageCommand.cs | 90 --- .../Language/LanguageKnowledgeComponent.cs | 23 - .../Language/LanguageSystem.cs | 233 ------ .../Language/TranslatorImplantSystem.cs | 66 -- .../Language/TranslatorSystem.cs | 165 ---- .../UniversalLanguageSpeakerComponent.cs | 12 - .../Assorted/ForeingerTraitComponent.cs | 37 - .../Traits/Assorted/ForeingerTraitSystem.cs | 105 --- .../LanguageKnowledgeModiferSystem.cs | 33 - .../LanguageKnowledgeModifierComponent.cs | 20 - .../Heretic/Abilities/HereticAbilitySystem.cs | 3 +- .../EntitySystems/MansusGraspSystem.cs | 3 +- .../_Impstation/Chat/Commands/GBSay.cs | 3 +- Content.Shared/Chat/SharedChatSystem.cs | 41 - Content.Shared/Input/ContentKeyFunctions.cs | 1 - .../Components/LanguageSpeakerComponent.cs | 45 -- .../Components/TranslatorImplantComponent.cs | 21 - .../Translators/BaseTranslatorComponent.cs | 37 - .../HandheldTranslatorComponent.cs | 25 - .../Translators/HoldsTranslatorComponent.cs | 13 - .../IntrinsicTranslatorComponent.cs | 10 - .../Events/DetermineEntityLanguagesEvent.cs | 25 - .../Language/Events/LanguagesSetMessage.cs | 14 - .../Language/Events/LanguagesUpdateEvent.cs | 12 - .../Language/LanguagePrototype.cs | 84 --- .../Language/ObfuscationMethods.cs | 184 ----- .../Language/Systems/SharedLanguageSystem.cs | 66 -- .../Systems/SharedTranslatorSystem.cs | 44 -- Resources/Fonts/Copperplate.otf | Bin 34436 -> 0 bytes Resources/Fonts/Mangat.ttf | Bin 29964 -> 0 bytes Resources/Fonts/Noganas.ttf | Bin 84396 -> 0 bytes Resources/Fonts/RubikBubbles.ttf | Bin 219436 -> 0 bytes .../_EinsteinEngine/Language/commands.ftl | 22 - .../Language/language-menu.ftl | 4 - .../Language/languages-sign.ftl | 5 - .../_EinsteinEngine/Language/languages.ftl | 71 -- .../_EinsteinEngine/Language/technologies.ftl | 2 - .../_EinsteinEngine/Language/translator.ftl | 13 - .../en-US/_EinsteinEngine/Traits/traits.ftl | 12 - .../en-US/chat/managers/chat-manager.ftl | 8 +- .../en-US/headset/headset-component.ftl | 4 +- .../Mobs/Cyborgs/base_borg_chassis.yml | 7 - .../Prototypes/Entities/Mobs/NPCs/animals.yml | 144 +--- .../Entities/Mobs/NPCs/argocyte.yml | 7 +- .../Prototypes/Entities/Mobs/NPCs/pets.yml | 66 +- .../Entities/Mobs/NPCs/regalrat.yml | 15 - .../Entities/Mobs/NPCs/revenant.yml | 1 - .../Prototypes/Entities/Mobs/NPCs/shadows.yml | 9 +- .../Prototypes/Entities/Mobs/NPCs/silicon.yml | 7 - .../Prototypes/Entities/Mobs/NPCs/slimes.yml | 8 +- .../Prototypes/Entities/Mobs/NPCs/space.yml | 21 +- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 12 - .../Entities/Mobs/Player/dragon.yml | 9 +- .../Entities/Mobs/Player/observer.yml | 1 - .../Entities/Mobs/Player/replay_observer.yml | 1 - .../Prototypes/Entities/Mobs/Species/base.yml | 6 +- .../Entities/Mobs/Species/diona.yml | 7 - .../Entities/Mobs/Species/dwarf.yml | 7 - .../Entities/Mobs/Species/human.yml | 10 +- .../Prototypes/Entities/Mobs/Species/moth.yml | 9 +- .../Entities/Mobs/Species/reptilian.yml | 8 +- .../Entities/Mobs/Species/slime.yml | 7 - .../Prototypes/Entities/Objects/Fun/pai.yml | 18 +- .../Objects/Specific/Robotics/mmi.yml | 5 - .../Computers/base_structurecomputers.yml | 9 - .../Entities/Structures/Machines/lathe.yml | 5 - .../Structures/Machines/vending_machines.yml | 7 - .../Prototypes/Research/civilianservices.yml | 29 - .../_DeltaV/Entities/Mobs/NPCs/nukiemouse.yml | 8 +- .../Entities/Objects/Devices/translators.yml | 180 ----- .../_EinsteinEngine/Language/languages.yml | 712 ------------------ .../Recipes/Lathes/language.yml | 71 -- .../_EinsteinEngine/Traits/quirks.yml | 34 - .../Entities/Mobs/Species/gray.yml | 7 - .../_EinsteinEngine/Interface/language.png | Bin 739 -> 0 bytes .../Objects/Devices/translator.rsi/icon.png | Bin 278 -> 0 bytes .../Objects/Devices/translator.rsi/meta.json | 17 - .../Devices/translator.rsi/translator.png | Bin 202 -> 0 bytes .../_EinsteinEngine/Traits/inconveniences.yml | 22 - Resources/keybinds.yml | 3 - 126 files changed, 210 insertions(+), 3982 deletions(-) delete mode 100644 Content.Client/_EinsteinEngine/Language/LanguageMenuWindow.xaml delete mode 100644 Content.Client/_EinsteinEngine/Language/LanguageMenuWindow.xaml.cs delete mode 100644 Content.Client/_EinsteinEngine/Language/Systems/LanguageSystem.cs delete mode 100644 Content.Client/_EinsteinEngine/UserInterface/Systems/Language/LanguageMenuUIController.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/Commands/AdminLanguageCommand.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/Commands/AdminTranslatorCommand.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/Commands/ListLanguagesCommand.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/Commands/SayLanguageCommand.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/Commands/SelectLanguageCommand.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/LanguageKnowledgeComponent.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/LanguageSystem.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/TranslatorImplantSystem.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/TranslatorSystem.cs delete mode 100644 Content.Server/_EinsteinEngine/Language/UniversalLanguageSpeakerComponent.cs delete mode 100644 Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitComponent.cs delete mode 100644 Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitSystem.cs delete mode 100644 Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModiferSystem.cs delete mode 100644 Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModifierComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/LanguageSpeakerComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/TranslatorImplantComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/Translators/BaseTranslatorComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/Translators/HandheldTranslatorComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/Translators/HoldsTranslatorComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Components/Translators/IntrinsicTranslatorComponent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Events/DetermineEntityLanguagesEvent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Events/LanguagesSetMessage.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Events/LanguagesUpdateEvent.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/LanguagePrototype.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/ObfuscationMethods.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Systems/SharedLanguageSystem.cs delete mode 100644 Content.Shared/_EinsteinEngine/Language/Systems/SharedTranslatorSystem.cs delete mode 100644 Resources/Fonts/Copperplate.otf delete mode 100644 Resources/Fonts/Mangat.ttf delete mode 100644 Resources/Fonts/Noganas.ttf delete mode 100644 Resources/Fonts/RubikBubbles.ttf delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/commands.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/language-menu.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/languages-sign.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/languages.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/technologies.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Language/translator.ftl delete mode 100644 Resources/Locale/en-US/_EinsteinEngine/Traits/traits.ftl delete mode 100644 Resources/Prototypes/_EinsteinEngine/Entities/Objects/Devices/translators.yml delete mode 100644 Resources/Prototypes/_EinsteinEngine/Language/languages.yml delete mode 100644 Resources/Prototypes/_EinsteinEngine/Recipes/Lathes/language.yml delete mode 100644 Resources/Prototypes/_EinsteinEngine/Traits/quirks.yml delete mode 100644 Resources/Textures/_EinsteinEngine/Interface/language.png delete mode 100644 Resources/Textures/_EinsteinEngine/Objects/Devices/translator.rsi/icon.png delete mode 100644 Resources/Textures/_EinsteinEngine/Objects/Devices/translator.rsi/meta.json delete mode 100644 Resources/Textures/_EinsteinEngine/Objects/Devices/translator.rsi/translator.png delete mode 100644 Resources/Textures/_EinsteinEngine/Traits/inconveniences.yml diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs index 62329c196be85a..639f326f7f422f 100644 --- a/Content.Client/Input/ContentContexts.cs +++ b/Content.Client/Input/ContentContexts.cs @@ -68,7 +68,6 @@ public static void SetupContexts(IInputContextContainer contexts) human.AddFunction(ContentKeyFunctions.MovePulledObject); human.AddFunction(ContentKeyFunctions.ReleasePulledObject); human.AddFunction(ContentKeyFunctions.OpenCraftingMenu); - human.AddFunction(ContentKeyFunctions.OpenLanguageMenu); human.AddFunction(ContentKeyFunctions.OpenInventoryMenu); human.AddFunction(ContentKeyFunctions.SmartEquipBackpack); human.AddFunction(ContentKeyFunctions.SmartEquipBelt); diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs index a6140b821492ae..24be904e061f8d 100644 --- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs @@ -214,7 +214,6 @@ void AddCheckBox(string checkBoxName, bool currentState, Action UIManager.GetActiveUIWidgetOrNull(); @@ -49,7 +47,6 @@ public void UnloadButtons() _action.UnloadButton(); _sandbox.UnloadButton(); _emotes.UnloadButton(); - _language.UnloadButton(); } public void LoadButtons() @@ -63,6 +60,5 @@ public void LoadButtons() _action.LoadButton(); _sandbox.LoadButton(); _emotes.LoadButton(); - _language.LoadButton(); } } diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index 28de1cd5052722..dc8972970ac869 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -73,16 +73,6 @@ HorizontalExpand="True" AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}" /> - - - - - - - - - - - - - - - diff --git a/Content.Client/_EinsteinEngine/Language/LanguageMenuWindow.xaml.cs b/Content.Client/_EinsteinEngine/Language/LanguageMenuWindow.xaml.cs deleted file mode 100644 index 365df347371815..00000000000000 --- a/Content.Client/_EinsteinEngine/Language/LanguageMenuWindow.xaml.cs +++ /dev/null @@ -1,143 +0,0 @@ -using Content.Client._EinsteinEngine.Language.Systems; -using Content.Shared._EinsteinEngine.Language; -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.CustomControls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; - -namespace Content.Client._EinsteinEngine.Language; - -[GenerateTypedNameReferences] -public sealed partial class LanguageMenuWindow : DefaultWindow -{ - private readonly LanguageSystem _clientLanguageSystem; - private readonly List _entries = new(); - - public LanguageMenuWindow() - { - RobustXamlLoader.Load(this); - _clientLanguageSystem = IoCManager.Resolve().GetEntitySystem(); - _clientLanguageSystem.OnLanguagesChanged += UpdateState; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - _clientLanguageSystem.OnLanguagesChanged -= UpdateState; - } - - protected override void Opened() - { - UpdateState(); - } - - private void UpdateState() - { - var languageSpeaker = _clientLanguageSystem.GetLocalSpeaker(); - if (languageSpeaker == null) - return; - - UpdateState(languageSpeaker.CurrentLanguage, languageSpeaker.SpokenLanguages); - } - - public void UpdateState(ProtoId currentLanguage, List> spokenLanguages) - { - var langName = Loc.GetString($"language-{currentLanguage}-name"); - CurrentLanguageLabel.Text = Loc.GetString("language-menu-current-language", ("language", langName)); - - OptionsList.RemoveAllChildren(); - _entries.Clear(); - - foreach (var language in spokenLanguages) - { - AddLanguageEntry(language); - } - - // Disable the button for the currently chosen language - foreach (var entry in _entries) - { - if (entry.Button != null) - entry.Button.Disabled = entry.Language == currentLanguage; - } - } - - private void AddLanguageEntry(ProtoId language) - { - var proto = _clientLanguageSystem.GetLanguagePrototype(language); - var state = new EntryState { Language = language }; - - var container = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical }; - - #region Header - var header = new BoxContainer - { - Orientation = BoxContainer.LayoutOrientation.Horizontal, - HorizontalExpand = true, - SeparationOverride = 2 - }; - - var name = new Label - { - Text = proto?.Name ?? Loc.GetString("generic-error"), - MinWidth = 50, - HorizontalExpand = true - }; - - var button = new Button { Text = "Choose" }; - button.OnPressed += _ => OnLanguageChosen(language); - state.Button = button; - - header.AddChild(name); - header.AddChild(button); - - container.AddChild(header); - #endregion - - #region Collapsible description - var body = new CollapsibleBody - { - HorizontalExpand = true, - Margin = new Thickness(4f, 4f) - }; - - var description = new RichTextLabel { HorizontalExpand = true }; - description.SetMessage(proto?.Description ?? Loc.GetString("generic-error")); - body.AddChild(description); - - var collapser = new Collapsible(Loc.GetString("language-menu-description-header"), body) - { - Orientation = BoxContainer.LayoutOrientation.Vertical, - HorizontalExpand = true - }; - - container.AddChild(collapser); - #endregion - - // 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(ProtoId id) - { - _clientLanguageSystem.RequestSetLanguage(id); - - // Predict the change - if (_clientLanguageSystem.GetLocalSpeaker()?.SpokenLanguages is {} languages) - UpdateState(id, languages); - } - - private struct EntryState - { - public ProtoId Language; - public Button? Button; - } -} diff --git a/Content.Client/_EinsteinEngine/Language/Systems/LanguageSystem.cs b/Content.Client/_EinsteinEngine/Language/Systems/LanguageSystem.cs deleted file mode 100644 index 974de159b714bb..00000000000000 --- a/Content.Client/_EinsteinEngine/Language/Systems/LanguageSystem.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Events; -using Content.Shared._EinsteinEngine.Language.Systems; -using Robust.Client.Player; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Client._EinsteinEngine.Language.Systems; - -public sealed class LanguageSystem : SharedLanguageSystem -{ - [Dependency] private readonly IPlayerManager _playerManager = default!; - - /// - /// Invoked when the languages of the local player entity change, for use in UI. - /// - public event Action? OnLanguagesChanged; - - public override void Initialize() - { - _playerManager.LocalPlayerAttached += NotifyUpdate; - SubscribeLocalEvent(OnHandleState); - } - - private void OnHandleState(Entity ent, ref ComponentHandleState args) - { - if (args.Current is not LanguageSpeakerComponent.State state) - return; - - ent.Comp.CurrentLanguage = state.CurrentLanguage; - ent.Comp.SpokenLanguages = state.SpokenLanguages; - ent.Comp.UnderstoodLanguages = state.UnderstoodLanguages; - - if (ent.Owner == _playerManager.LocalEntity) - NotifyUpdate(ent); - } - - /// - /// Returns the LanguageSpeakerComponent of the local player entity. - /// Will return null if the player does not have an entity, or if the client has not yet received the component state. - /// - public LanguageSpeakerComponent? GetLocalSpeaker() - { - return CompOrNull(_playerManager.LocalEntity); - } - - public void RequestSetLanguage(ProtoId language) - { - if (GetLocalSpeaker()?.CurrentLanguage?.Equals(language) == true) - return; - - RaiseNetworkEvent(new LanguagesSetMessage(language)); - } - - private void NotifyUpdate(EntityUid localPlayer) - { - RaiseLocalEvent(localPlayer, new LanguagesUpdateEvent(), broadcast: true); - OnLanguagesChanged?.Invoke(); - } -} diff --git a/Content.Client/_EinsteinEngine/UserInterface/Systems/Language/LanguageMenuUIController.cs b/Content.Client/_EinsteinEngine/UserInterface/Systems/Language/LanguageMenuUIController.cs deleted file mode 100644 index c5354930b9b8c6..00000000000000 --- a/Content.Client/_EinsteinEngine/UserInterface/Systems/Language/LanguageMenuUIController.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Content.Client._EinsteinEngine.Language; -using Content.Client.Gameplay; -using Content.Client.UserInterface.Controls; -using Content.Shared.Input; -using JetBrains.Annotations; -using Robust.Client.UserInterface.Controllers; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Input.Binding; -using Robust.Shared.Utility; -using static Robust.Client.UserInterface.Controls.BaseButton; - -namespace Content.Client._EinsteinEngine.UserInterface.Systems.Language; - -[UsedImplicitly] -public sealed class LanguageMenuUIController : UIController, IOnStateEntered, IOnStateExited -{ - public LanguageMenuWindow? LanguageWindow; - private MenuButton? LanguageButton => UIManager.GetActiveUIWidgetOrNull()?.LanguageButton; - - public void OnStateEntered(GameplayState state) - { - DebugTools.Assert(LanguageWindow == null); - - LanguageWindow = UIManager.CreateWindow(); - LayoutContainer.SetAnchorPreset(LanguageWindow, LayoutContainer.LayoutPreset.CenterTop); - - LanguageWindow.OnClose += () => - { - if (LanguageButton != null) - LanguageButton.Pressed = false; - }; - LanguageWindow.OnOpen += () => - { - if (LanguageButton != null) - LanguageButton.Pressed = true; - }; - - CommandBinds.Builder.Bind(ContentKeyFunctions.OpenLanguageMenu, - InputCmdHandler.FromDelegate(_ => ToggleWindow())).Register(); - } - - public void OnStateExited(GameplayState state) - { - if (LanguageWindow != null) - { - LanguageWindow.Dispose(); - LanguageWindow = null; - } - - CommandBinds.Unregister(); - } - - public void UnloadButton() - { - if (LanguageButton == null) - return; - - LanguageButton.OnPressed -= LanguageButtonPressed; - } - - public void LoadButton() - { - if (LanguageButton == null) - return; - - LanguageButton.OnPressed += LanguageButtonPressed; - } - - private void LanguageButtonPressed(ButtonEventArgs args) - { - ToggleWindow(); - } - - private void ToggleWindow() - { - if (LanguageWindow == null) - return; - - if (LanguageButton != null) - LanguageButton.SetClickPressed(!LanguageWindow.IsOpen); - - if (LanguageWindow.IsOpen) - LanguageWindow.Close(); - else - LanguageWindow.Open(); - } -} diff --git a/Content.Server/Administration/Commands/DSay.cs b/Content.Server/Administration/Commands/DSay.cs index 56599df2f656fd..8e7f0f4bf05abc 100644 --- a/Content.Server/Administration/Commands/DSay.cs +++ b/Content.Server/Administration/Commands/DSay.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Robust.Shared.Console; namespace Content.Server.Administration.Commands @@ -35,7 +34,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; var chat = _e.System(); - chat.TrySendInGameOOCMessage(entity, message, SharedChatSystem.InGameOOCChatType.Dead, false, shell, player); + chat.TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Dead, false, shell, player); } } } diff --git a/Content.Server/Administration/Commands/OSay.cs b/Content.Server/Administration/Commands/OSay.cs index 3362381ba49287..2f17bd9d70aa17 100644 --- a/Content.Server/Administration/Commands/OSay.cs +++ b/Content.Server/Administration/Commands/OSay.cs @@ -2,7 +2,6 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Content.Shared.Database; using Robust.Shared.Console; @@ -25,7 +24,7 @@ public override CompletionResult GetCompletion(IConsoleShell shell, string[] arg if (args.Length == 2) { - return CompletionResult.FromHintOptions( Enum.GetNames(typeof(SharedChatSystem.InGameICChatType)), + return CompletionResult.FromHintOptions( Enum.GetNames(typeof(InGameICChatType)), Loc.GetString("osay-command-arg-type")); } @@ -45,8 +44,7 @@ public override void Execute(IConsoleShell shell, string argStr, string[] args) return; } - var chatType = - (SharedChatSystem.InGameICChatType)Enum.Parse(typeof(SharedChatSystem.InGameICChatType), args[1]); + var chatType = (InGameICChatType) Enum.Parse(typeof(InGameICChatType), args[1]); if (!NetEntity.TryParse(args[0], out var sourceNet) || !_entityManager.TryGetEntity(sourceNet, out var source) || !_entityManager.EntityExists(source)) { diff --git a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs index c9cbc4635f7a6b..7f2e128183584a 100644 --- a/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs +++ b/Content.Server/Advertise/EntitySystems/AdvertiseSystem.cs @@ -1,7 +1,6 @@ using Content.Server.Advertise.Components; using Content.Server.Chat.Systems; using Content.Server.Power.Components; -using Content.Shared.Chat; using Content.Shared.VendingMachines; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -63,7 +62,7 @@ public void SayAdvertisement(EntityUid uid, AdvertiseComponent? advert = null) return; if (_prototypeManager.TryIndex(advert.Pack, out var advertisements)) - _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Values)), SharedChatSystem.InGameICChatType.Speak, hideChat: true); + _chat.TrySendInGameICMessage(uid, Loc.GetString(_random.Pick(advertisements.Values)), InGameICChatType.Speak, hideChat: true); } public override void Update(float frameTime) diff --git a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs index f1883c63b43ea5..a0a709e5faddf0 100644 --- a/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs +++ b/Content.Server/Advertise/EntitySystems/SpeakOnUIClosedSystem.cs @@ -1,6 +1,5 @@ using Content.Server.Advertise.Components; using Content.Server.Chat.Systems; -using Content.Shared.Chat; using Content.Shared.Dataset; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -43,7 +42,7 @@ public bool TrySpeak(Entity entity) return false; var message = Loc.GetString(_random.Pick(messagePack.Values), ("name", Name(entity))); - _chat.TrySendInGameICMessage(entity, message, SharedChatSystem.InGameICChatType.Speak, true); + _chat.TrySendInGameICMessage(entity, message, InGameICChatType.Speak, true); entity.Comp.Flag = false; return true; } diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index 6a413bd1fcd512..6209f00419d390 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -9,7 +9,6 @@ using Content.Shared.Atmos; using Content.Shared.Body.Components; using Content.Shared.Body.Prototypes; -using Content.Shared.Chat; using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; @@ -97,7 +96,7 @@ public override void Update(float frameTime) if (_gameTiming.CurTime >= respirator.LastGaspEmoteTime + respirator.GaspEmoteCooldown) { respirator.LastGaspEmoteTime = _gameTiming.CurTime; - _chat.TryEmoteWithChat(uid, respirator.GaspEmote, SharedChatSystem.ChatTransmitRange.HideChat, ignoreActionBlocker: true); + _chat.TryEmoteWithChat(uid, respirator.GaspEmote, ChatTransmitRange.HideChat, ignoreActionBlocker: true); } TakeSuffocationDamage((uid, respirator)); diff --git a/Content.Server/Chat/Commands/LOOCCommand.cs b/Content.Server/Chat/Commands/LOOCCommand.cs index d956ac596f13d7..e303b9766d84ed 100644 --- a/Content.Server/Chat/Commands/LOOCCommand.cs +++ b/Content.Server/Chat/Commands/LOOCCommand.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; @@ -36,7 +35,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) if (string.IsNullOrEmpty(message)) return; - _e.System().TrySendInGameOOCMessage(entity, message, SharedChatSystem.InGameOOCChatType.Looc, false, shell, player); + _e.System().TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Looc, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/MeCommand.cs b/Content.Server/Chat/Commands/MeCommand.cs index 2d75ff984bcea2..e763d5656e16da 100644 --- a/Content.Server/Chat/Commands/MeCommand.cs +++ b/Content.Server/Chat/Commands/MeCommand.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; @@ -38,7 +37,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; IoCManager.Resolve().GetEntitySystem() - .TrySendInGameICMessage(playerEntity, message, SharedChatSystem.InGameICChatType.Emote, SharedChatSystem.ChatTransmitRange.Normal, false, shell, player); + .TrySendInGameICMessage(playerEntity, message, InGameICChatType.Emote, ChatTransmitRange.Normal, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/SayCommand.cs b/Content.Server/Chat/Commands/SayCommand.cs index 36246954e84c7d..df6e548e5d9efc 100644 --- a/Content.Server/Chat/Commands/SayCommand.cs +++ b/Content.Server/Chat/Commands/SayCommand.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; @@ -38,7 +37,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; IoCManager.Resolve().GetEntitySystem() - .TrySendInGameICMessage(playerEntity, message, SharedChatSystem.InGameICChatType.Speak, SharedChatSystem.ChatTransmitRange.Normal, false, shell, player); + .TrySendInGameICMessage(playerEntity, message, InGameICChatType.Speak, ChatTransmitRange.Normal, false, shell, player); } } } diff --git a/Content.Server/Chat/Commands/WhisperCommand.cs b/Content.Server/Chat/Commands/WhisperCommand.cs index 0bbb67e955dde8..13effa34464ba8 100644 --- a/Content.Server/Chat/Commands/WhisperCommand.cs +++ b/Content.Server/Chat/Commands/WhisperCommand.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; -using Content.Shared.Chat; using Robust.Shared.Console; using Robust.Shared.Enums; @@ -38,7 +37,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; IoCManager.Resolve().GetEntitySystem() - .TrySendInGameICMessage(playerEntity, message, SharedChatSystem.InGameICChatType.Whisper, SharedChatSystem.ChatTransmitRange.Normal, false, shell, player); + .TrySendInGameICMessage(playerEntity, message, InGameICChatType.Whisper, ChatTransmitRange.Normal, false, shell, player); } } } diff --git a/Content.Server/Chat/Systems/AutoEmoteSystem.cs b/Content.Server/Chat/Systems/AutoEmoteSystem.cs index f8bc26c5a8771a..3d6bd5354011c5 100644 --- a/Content.Server/Chat/Systems/AutoEmoteSystem.cs +++ b/Content.Server/Chat/Systems/AutoEmoteSystem.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -47,7 +46,7 @@ public override void Update(float frameTime) if (autoEmotePrototype.WithChat) { - _chatSystem.TryEmoteWithChat(uid, autoEmotePrototype.EmoteId, autoEmotePrototype.HiddenFromChatWindow ? SharedChatSystem.ChatTransmitRange.HideChat : SharedChatSystem.ChatTransmitRange.Normal); + _chatSystem.TryEmoteWithChat(uid, autoEmotePrototype.EmoteId, autoEmotePrototype.HiddenFromChatWindow ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal); } else { diff --git a/Content.Server/Chat/Systems/ChatSystem.Emote.cs b/Content.Server/Chat/Systems/ChatSystem.Emote.cs index 971da2169af096..fddf453ff06481 100644 --- a/Content.Server/Chat/Systems/ChatSystem.Emote.cs +++ b/Content.Server/Chat/Systems/ChatSystem.Emote.cs @@ -93,8 +93,7 @@ public void TryEmoteWithChat( { // not all emotes are loc'd, but for the ones that are we pass in entity var action = Loc.GetString(_random.Pick(emote.ChatMessages), ("entity", source)); - var language = _language.GetLanguage(source); - SendEntityEmote(source, action, range, nameOverride, language, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); + SendEntityEmote(source, action, range, nameOverride, hideLog: hideLog, checkEmote: false, ignoreActionBlocker: ignoreActionBlocker); } // do the rest of emote event logic here diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index b6b6c91762294f..6e4b952f2e287a 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -34,9 +34,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Replays; -using Content.Shared._EinsteinEngine.Language; -using Content.Server._EinsteinEngine.Language; -using Content.Server.Speech; using Robust.Shared.Utility; namespace Content.Server.Chat.Systems; @@ -64,14 +61,12 @@ 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!; public const int VoiceRange = 10; // how far voice goes in world units public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg"; - public const float DefaultObfuscationFactor = 0.2f; // Percentage of symbols in a whispered message that can be seen even by "far" listeners - public readonly Color DefaultSpeakColor = Color.White; + private bool _loocEnabled = true; private bool _deadLoocEnabled; private bool _critLoocEnabled; @@ -151,9 +146,7 @@ public void TrySendInGameICMessage( IConsoleShell? shell = null, ICommonSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true, - bool ignoreActionBlocker = false, - LanguagePrototype? languageOverride = null - ) + bool ignoreActionBlocker = false) { TrySendInGameICMessage(source, message, desiredType, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, hideLog, shell, player, nameOverride, checkRadioPrefix, ignoreActionBlocker); } @@ -179,8 +172,7 @@ public void TrySendInGameICMessage( ICommonSession? player = null, string? nameOverride = null, bool checkRadioPrefix = true, - bool ignoreActionBlocker = false, - LanguagePrototype? languageOverride = null + bool ignoreActionBlocker = false ) { if (HasComp(source)) @@ -224,8 +216,6 @@ public void TrySendInGameICMessage( message = message[1..]; } - var language = languageOverride ?? _language.GetLanguage(source); - bool shouldCapitalize = (desiredType != InGameICChatType.Emote); bool shouldPunctuate = _configurationManager.GetCVar(CCVars.ChatPunctuation); // Capitalizing the word I only happens in English, so we check language here @@ -237,23 +227,19 @@ public void TrySendInGameICMessage( // Was there an emote in the message? If so, send it. if (player != null && emoteStr != message && emoteStr != null) { - SendEntityEmote(source, emoteStr, range, nameOverride, language, ignoreActionBlocker); + SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker); } // This can happen if the entire string is sanitized out. if (string.IsNullOrEmpty(message)) return; - // This is really terrible. I hate myself for doing this. - if (language.SpeechOverride.ChatTypeOverride is { } chatTypeOverride) - desiredType = chatTypeOverride; - // This message may have a radio prefix, and should then be whispered to the resolved radio channel if (checkRadioPrefix) { if (TryProccessRadioMessage(source, message, out var modMessage, out var channel)) { - SendEntityWhisper(source, modMessage, range, channel, nameOverride, language, hideLog, ignoreActionBlocker); + SendEntityWhisper(source, modMessage, range, channel, nameOverride, hideLog, ignoreActionBlocker); return; } } @@ -262,13 +248,13 @@ public void TrySendInGameICMessage( switch (desiredType) { case InGameICChatType.Speak: - SendEntitySpeak(source, message, range, nameOverride, language, hideLog, ignoreActionBlocker); + SendEntitySpeak(source, message, range, nameOverride, hideLog, ignoreActionBlocker); break; case InGameICChatType.Whisper: - SendEntityWhisper(source, message, range, null, nameOverride, language, hideLog, ignoreActionBlocker); + SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker); break; case InGameICChatType.Emote: - SendEntityEmote(source, message, range, nameOverride, language, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); + SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker); break; } } @@ -429,7 +415,6 @@ private void SendEntitySpeak( string originalMessage, ChatTransmitRange range, string? nameOverride, - LanguagePrototype language, bool hideLog = false, bool ignoreActionBlocker = false ) @@ -437,7 +422,7 @@ private void SendEntitySpeak( if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); + var message = TransformSpeech(source, originalMessage); if (message.Length == 0) return; @@ -461,16 +446,17 @@ private void SendEntitySpeak( } name = FormattedMessage.EscapeText(name); - // The chat message wrapped in a "x says y" string - var wrappedMessage = WrapPublicMessage(source, name, message, language: language); - // The chat message obfuscated via language obfuscation - var obfuscated = SanitizeInGameICMessage(source, _language.ObfuscateSpeech(message, language), out var emoteStr, true, _configurationManager.GetCVar(CCVars.ChatPunctuation), (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); - // The language-obfuscated message wrapped in a "x says y" string - var wrappedObfuscated = WrapPublicMessage(source, name, obfuscated, language: language); - SendInVoiceRange(ChatChannel.Local, name, message, wrappedMessage, obfuscated, wrappedObfuscated, source, range, languageOverride: language); + 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 ev = new EntitySpokeEvent(source, message, null, false, language); + SendInVoiceRange(ChatChannel.Local, message, wrappedMessage, source, range); + + var ev = new EntitySpokeEvent(source, message, null, null); RaiseLocalEvent(source, ev, true); // To avoid logging any messages sent by entities that are not players, like vendors, cloning, etc. @@ -502,7 +488,6 @@ private void SendEntityWhisper( ChatTransmitRange range, RadioChannelPrototype? channel, string? nameOverride, - LanguagePrototype language, bool hideLog = false, bool ignoreActionBlocker = false ) @@ -510,10 +495,12 @@ private void SendEntityWhisper( if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker) return; - var message = TransformSpeech(source, FormattedMessage.RemoveMarkup(originalMessage), language); + var message = TransformSpeech(source, FormattedMessage.RemoveMarkupOrThrow(originalMessage)); if (message.Length == 0) return; + var obfuscatedMessage = ObfuscateMessageReadability(message, 0.2f); + // get the entity's name by visual identity (if no override provided). string nameIdentity = FormattedMessage.EscapeText(nameOverride ?? Identity.Name(source, EntityManager)); // get the entity's name by voice (if no override provided). @@ -530,56 +517,40 @@ private void SendEntityWhisper( } name = FormattedMessage.EscapeText(name); - var languageObfuscatedMessage = SanitizeInGameICMessage(source, - _language.ObfuscateSpeech(message, language), - out var emoteStr, - true, - _configurationManager.GetCVar(CCVars.ChatPunctuation), - (!CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Parent.Name == "en") || (CultureInfo.CurrentCulture.IsNeutralCulture && CultureInfo.CurrentCulture.Name == "en")); + 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))); + foreach (var (session, data) in GetRecipients(source, WhisperMuffledRange)) { - if (session.AttachedEntity is not { Valid: true } listener) + EntityUid listener; + + if (session.AttachedEntity is not { Valid: true } playerEntity) continue; + listener = session.AttachedEntity.Value; 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 canUnderstandLanguage = _language.CanUnderstand(listener, language.ID); - // How the entity perceives the message depends on whether it can understand its language - var perceivedMessage = FormattedMessage.EscapeText(canUnderstandLanguage ? message : languageObfuscatedMessage); - - // Result is the intermediate message derived from the perceived one via obfuscation - // Wrapped message is the result wrapped in an "x says y" string - string result, wrappedMessage; - if (data.Range <= WhisperClearRange) - { - // Scenario 1: the listener can clearly understand the message - result = perceivedMessage; - wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", name, result, language); - } + _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)) - { - // Scenerio 2: if the listener is too far, they only hear fragments of the message - result = ObfuscateMessageReadability(perceivedMessage); - wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", nameIdentity, result, language); - } + _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 - { - // Scenario 3: If listener is too far and has no line of sight, they can't identify the whisperer's identity - result = ObfuscateMessageReadability(perceivedMessage); - wrappedMessage = WrapWhisperMessage(source, "chat-manager-entity-whisper-unknown-wrap-message", string.Empty, result, language); - } - - _chatManager.ChatMessageToOne(ChatChannel.Whisper, result, wrappedMessage, source, false, session.Channel); + _chatManager.ChatMessageToOne(ChatChannel.Whisper, obfuscatedMessage, wrappedUnknownMessage, source, false, session.Channel); } - var replayWrap = WrapWhisperMessage(source, "chat-manager-entity-whisper-wrap-message", name, FormattedMessage.EscapeText(message), language); - - _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, replayWrap, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); + _replay.RecordServerMessage(new ChatMessage(ChatChannel.Whisper, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); - var ev = new EntitySpokeEvent(source, message, channel, true, language); + var ev = new EntitySpokeEvent(source, message, channel, obfuscatedMessage); RaiseLocalEvent(source, ev, true); if (!hideLog) if (originalMessage == message) @@ -605,7 +576,6 @@ private void SendEntityEmote( string action, ChatTransmitRange range, string? nameOverride, - LanguagePrototype language, bool hideLog = false, bool checkEmote = true, bool ignoreActionBlocker = false, @@ -623,11 +593,11 @@ private void SendEntityEmote( var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message", ("entityName", name), ("entity", ent), - ("message", FormattedMessage.RemoveMarkup(action))); + ("message", FormattedMessage.RemoveMarkupOrThrow(action))); if (checkEmote) TryEmoteChatInput(source, action); - SendInVoiceRange(ChatChannel.Emotes, name, action, wrappedMessage, obfuscated: "", obfuscatedWrappedMessage: "", source, range, author); + SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author); if (!hideLog) if (name != Name(source)) _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {ToPrettyString(source):user} as {name}: {action}"); @@ -654,13 +624,7 @@ private void SendLOOC(EntityUid source, ICommonSession player, string message, b ("entityName", name), ("message", FormattedMessage.EscapeText(message))); - SendInVoiceRange(ChatChannel.LOOC, name, message, wrappedMessage, - obfuscated: string.Empty, - obfuscatedWrappedMessage: string.Empty, // will be skipped anyway - source, - hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, - player.UserId, - languageOverride: LanguageSystem.Universal); + SendInVoiceRange(ChatChannel.LOOC, message, wrappedMessage, source, hideChat ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal, player.UserId); _adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); } @@ -741,28 +705,15 @@ 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 name, string message, string wrappedMessage, string obfuscated, string obfuscatedWrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null, LanguagePrototype? languageOverride = null) + private void SendInVoiceRange(ChatChannel channel, string message, string wrappedMessage, EntityUid source, ChatTransmitRange range, NetUserId? author = null) { - var language = languageOverride ?? _language.GetLanguage(source); foreach (var (session, data) in GetRecipients(source, VoiceRange)) { var entRange = MessageRangeCheck(session, data, range); if (entRange == MessageRangeCheckResult.Disallowed) continue; var entHideChat = entRange == MessageRangeCheckResult.HideChat; - if (session.AttachedEntity is not { Valid: true } playerEntity) - continue; - EntityUid listener = session.AttachedEntity.Value; - - // If the channel does not support languages, or the entity can understand the message, send the original message, otherwise send the obfuscated version - if (channel == ChatChannel.LOOC || channel == ChatChannel.Emotes || _language.CanUnderstand(listener, language.ID)) - { - _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author); - } - else - { - _chatManager.ChatMessageToOne(channel, obfuscated, obfuscatedWrappedMessage, source, entHideChat, session.Channel, author: author); - } + _chatManager.ChatMessageToOne(channel, message, wrappedMessage, source, entHideChat, session.Channel, author: author); } _replay.RecordServerMessage(new ChatMessage(channel, message, wrappedMessage, GetNetEntity(source), null, MessageRangeHideChatForReplay(range))); @@ -822,11 +773,8 @@ private string SanitizeInGameOOCMessage(string message) return newMessage; } - public string TransformSpeech(EntityUid sender, string message, LanguagePrototype language) + public string TransformSpeech(EntityUid sender, string message) { - if (!language.SpeechOverride.RequireSpeech) - return message; // Do not apply speech accents if there's no speech involved. - var ev = new TransformSpeechEvent(sender, message); RaiseLocalEvent(ev); @@ -878,49 +826,6 @@ public string SanitizeMessageReplaceWords(string message) return msg; } - /// - /// Wraps a message sent by the specified entity into an "x says y" string. - /// - public string WrapPublicMessage(EntityUid source, string name, string message, LanguagePrototype? language = null) - { - var wrapId = GetSpeechVerb(source, message).Bold ? "chat-manager-entity-say-bold-wrap-message" : "chat-manager-entity-say-wrap-message"; - return WrapMessage(wrapId, InGameICChatType.Speak, source, name, message, language); - } - - /// - /// Wraps a message whispered by the specified entity into an "x whispers y" string. - /// - public string WrapWhisperMessage(EntityUid source, LocId defaultWrap, string entityName, string message, LanguagePrototype? language = null) - { - return WrapMessage(defaultWrap, InGameICChatType.Whisper, source, entityName, message, language); - } - - /// - /// Wraps a message sent by the specified entity into the specified wrap string. - /// - public string WrapMessage(LocId wrapId, InGameICChatType chatType, EntityUid source, string entityName, string message, LanguagePrototype? language) - { - language ??= _language.GetLanguage(source); - if (language.SpeechOverride.MessageWrapOverrides.TryGetValue(chatType, out var wrapOverride)) - wrapId = wrapOverride; - - var speech = GetSpeechVerb(source, message); - var verbId = language.SpeechOverride.SpeechVerbOverrides is { } verbsOverride - ? _random.Pick(verbsOverride).ToString() - : _random.Pick(speech.SpeechVerbStrings); - var color = DefaultSpeakColor; - if (language.SpeechOverride.Color is { } colorOverride) - color = Color.InterpolateBetween(color, colorOverride, colorOverride.A); - - return Loc.GetString(wrapId, - ("color", color), - ("entityName", entityName), - ("verb", Loc.GetString(verbId)), - ("fontType", language.SpeechOverride.FontId ?? speech.FontId), - ("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize), - ("message", message)); - } - /// /// Returns list of players and ranges for all players withing some range. Also returns observers with a range of -1. /// @@ -967,7 +872,7 @@ public readonly record struct ICChatRecipientData(float Range, bool Observer, bo { } - public string ObfuscateMessageReadability(string message, float chance = DefaultObfuscationFactor) + private string ObfuscateMessageReadability(string message, float chance) { var modifiedMessage = new StringBuilder(message); @@ -1042,8 +947,7 @@ public sealed class EntitySpokeEvent : EntityEventArgs { public readonly EntityUid Source; public readonly string Message; - public readonly bool IsWhisper; - public readonly LanguagePrototype Language; + public readonly string? ObfuscatedMessage; // not null if this was a whisper /// /// If the entity was trying to speak into a radio, this was the channel they were trying to access. If a radio @@ -1051,11 +955,46 @@ public sealed class EntitySpokeEvent : EntityEventArgs /// public RadioChannelPrototype? Channel; - public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? channel, bool isWhisper, LanguagePrototype language) { + public EntitySpokeEvent(EntityUid source, string message, RadioChannelPrototype? channel, string? obfuscatedMessage) + { Source = source; Message = message; Channel = channel; - IsWhisper = isWhisper; - Language = language; + ObfuscatedMessage = obfuscatedMessage; } } + +/// +/// InGame IC chat is for chat that is specifically ingame (not lobby) but is also in character, i.e. speaking. +/// +// ReSharper disable once InconsistentNaming +public enum InGameICChatType : byte +{ + Speak, + Emote, + Whisper +} + +/// +/// InGame OOC chat is for chat that is specifically ingame (not lobby) but is OOC, like deadchat or LOOC. +/// +public enum InGameOOCChatType : byte +{ + Looc, + Dead +} + +/// +/// Controls transmission of chat. +/// +public enum ChatTransmitRange : byte +{ + /// Acts normal, ghosts can hear across the map, etc. + Normal, + /// Normal but ghosts are still range-limited. + GhostRangeLimit, + /// Hidden from the chat window. + HideChat, + /// Ghosts can't hear or see it at all. Regular players can if in-range. + NoGhosts +} diff --git a/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs b/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs index 16b5ecd9c640f7..878c517d9242b6 100644 --- a/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs +++ b/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs @@ -1,5 +1,3 @@ -using Content.Shared.Chat; - namespace Content.Server.Chat.Systems; using Content.Shared.Chat.Prototypes; @@ -40,7 +38,7 @@ private void OnDamage(EntityUid uid, EmoteOnDamageComponent emoteOnDamage, Damag var emote = _random.Pick(emoteOnDamage.Emotes); if (emoteOnDamage.WithChat) { - _chatSystem.TryEmoteWithChat(uid, emote, emoteOnDamage.HiddenFromChatWindow ? SharedChatSystem.ChatTransmitRange.HideChat : SharedChatSystem.ChatTransmitRange.Normal); + _chatSystem.TryEmoteWithChat(uid, emote, emoteOnDamage.HiddenFromChatWindow ? ChatTransmitRange.HideChat : ChatTransmitRange.Normal); } else { diff --git a/Content.Server/Chat/Systems/SpeakOnUseSystem.cs b/Content.Server/Chat/Systems/SpeakOnUseSystem.cs index 3df8f093064e07..addec79e410081 100644 --- a/Content.Server/Chat/Systems/SpeakOnUseSystem.cs +++ b/Content.Server/Chat/Systems/SpeakOnUseSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Chat; -using Content.Shared.Chat; using Content.Shared.Dataset; using Content.Shared.Interaction.Events; using Content.Shared.Timing; @@ -37,7 +36,7 @@ public void OnUseInHand(EntityUid uid, SpeakOnUseComponent? component, UseInHand return; var message = Loc.GetString(_random.Pick(messagePack.Values)); - _chat.TrySendInGameICMessage(uid, message, SharedChatSystem.InGameICChatType.Speak, true); + _chat.TrySendInGameICMessage(uid, message, InGameICChatType.Speak, true); _useDelay.TryResetDelay((uid, useDelay)); } } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 0acbcb146129ac..65f92740016297 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -11,7 +11,6 @@ using Content.Server.Power.EntitySystems; using Content.Shared.Atmos; using Content.Shared.CCVar; -using Content.Shared.Chat; using Content.Shared.Chemistry.Components; using Content.Shared.Cloning; using Content.Shared.Damage; @@ -183,7 +182,7 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity 0 && clonePod.ConnectedConsole != null) - _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), SharedChatSystem.InGameICChatType.Speak, false); + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, Loc.GetString("cloning-console-cellular-warning", ("percent", Math.Round(100 - chance * 100))), InGameICChatType.Speak, false); if (_robustRandom.Prob(chance)) { diff --git a/Content.Server/Cluwne/CluwneSystem.cs b/Content.Server/Cluwne/CluwneSystem.cs index 6af8ef1a1e71a6..f24f0143f3164b 100644 --- a/Content.Server/Cluwne/CluwneSystem.cs +++ b/Content.Server/Cluwne/CluwneSystem.cs @@ -12,7 +12,6 @@ using Robust.Shared.Prototypes; using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; -using Content.Shared.Chat; using Content.Shared.Cluwne; using Content.Shared.Interaction.Components; using Robust.Shared.Audio.Systems; @@ -93,14 +92,14 @@ private void OnEmote(EntityUid uid, CluwneComponent component, ref EmoteEvent ar if (_robustRandom.Prob(component.GiggleRandomChance)) { _audio.PlayPvs(component.SpawnSound, uid); - _chat.TrySendInGameICMessage(uid, "honks", SharedChatSystem.InGameICChatType.Emote, SharedChatSystem.ChatTransmitRange.Normal); + _chat.TrySendInGameICMessage(uid, "honks", InGameICChatType.Emote, ChatTransmitRange.Normal); } else if (_robustRandom.Prob(component.KnockChance)) { _audio.PlayPvs(component.KnockSound, uid); _stunSystem.TryParalyze(uid, TimeSpan.FromSeconds(component.ParalyzeTime), true); - _chat.TrySendInGameICMessage(uid, "spasms", SharedChatSystem.InGameICChatType.Emote, SharedChatSystem.ChatTransmitRange.Normal); + _chat.TrySendInGameICMessage(uid, "spasms", InGameICChatType.Emote, ChatTransmitRange.Normal); } } diff --git a/Content.Server/EntityEffects/Effects/Emote.cs b/Content.Server/EntityEffects/Effects/Emote.cs index bbca76169736a2..00bdaec455c304 100644 --- a/Content.Server/EntityEffects/Effects/Emote.cs +++ b/Content.Server/EntityEffects/Effects/Emote.cs @@ -1,5 +1,4 @@ using Content.Server.Chat.Systems; -using Content.Shared.Chat; using Content.Shared.Chat.Prototypes; using Content.Shared.EntityEffects; using JetBrains.Annotations; @@ -34,7 +33,7 @@ public override void Effect(EntityEffectBaseArgs args) var chatSys = args.EntityManager.System(); if (ShowInChat) - chatSys.TryEmoteWithChat(args.TargetEntity, EmoteId, SharedChatSystem.ChatTransmitRange.GhostRangeLimit, forceEmote: Force); + chatSys.TryEmoteWithChat(args.TargetEntity, EmoteId, ChatTransmitRange.GhostRangeLimit, forceEmote: Force); else chatSys.TryEmoteWithoutChat(args.TargetEntity, EmoteId); diff --git a/Content.Server/EntityEffects/Effects/MakeSentient.cs b/Content.Server/EntityEffects/Effects/MakeSentient.cs index 6625649d9ee5a3..c4870438486f61 100644 --- a/Content.Server/EntityEffects/Effects/MakeSentient.cs +++ b/Content.Server/EntityEffects/Effects/MakeSentient.cs @@ -1,13 +1,7 @@ -using System.Linq; -using Content.Server._EinsteinEngine.Language; using Content.Server.Ghost.Roles.Components; using Content.Server.Speech.Components; using Content.Shared.EntityEffects; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Systems; using Content.Shared.Mind.Components; -using Content.Shared._EinsteinEngine.Language.Events; using Robust.Shared.Prototypes; namespace Content.Server.EntityEffects.Effects; @@ -28,18 +22,6 @@ public override void Effect(EntityEffectBaseArgs args) entityManager.RemoveComponent(uid); entityManager.RemoveComponent(uid); - var speaker = entityManager.EnsureComponent(uid); - var knowledge = entityManager.EnsureComponent(uid); - var fallback = SharedLanguageSystem.FallbackLanguagePrototype; - - if (!knowledge.UnderstoodLanguages.Contains(fallback)) - knowledge.UnderstoodLanguages.Add(fallback); - - if (!knowledge.SpokenLanguages.Contains(fallback)) - knowledge.SpokenLanguages.Add(fallback); - - IoCManager.Resolve().GetEntitySystem().UpdateEntityLanguages(uid); - // Stops from adding a ghost role to things like people who already have a mind if (entityManager.TryGetComponent(uid, out var mindContainer) && mindContainer.HasMind) { diff --git a/Content.Server/Magic/MagicSystem.cs b/Content.Server/Magic/MagicSystem.cs index cf78b6775bb899..2cf5136b42770f 100644 --- a/Content.Server/Magic/MagicSystem.cs +++ b/Content.Server/Magic/MagicSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Chat.Systems; -using Content.Shared.Chat; using Content.Shared.Magic; using Content.Shared.Magic.Events; @@ -18,6 +17,6 @@ public override void Initialize() private void OnSpellSpoken(ref SpeakSpellEvent args) { - _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), SharedChatSystem.InGameICChatType.Speak, false); + _chat.TrySendInGameICMessage(args.Performer, Loc.GetString(args.Speech), InGameICChatType.Speak, false); } } diff --git a/Content.Server/Medical/DefibrillatorSystem.cs b/Content.Server/Medical/DefibrillatorSystem.cs index b2549510376c1c..c9cb6cc58dc2e8 100644 --- a/Content.Server/Medical/DefibrillatorSystem.cs +++ b/Content.Server/Medical/DefibrillatorSystem.cs @@ -7,7 +7,6 @@ using Content.Server.Popups; using Content.Server.PowerCell; using Content.Server.Traits.Assorted; -using Content.Shared.Chat; using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Interaction; @@ -151,12 +150,12 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo if (_rotting.IsRotten(target)) { _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-rotten"), - SharedChatSystem.InGameICChatType.Speak, true); + InGameICChatType.Speak, true); } else if (HasComp(target)) { _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-unrevivable"), - SharedChatSystem.InGameICChatType.Speak, true); + InGameICChatType.Speak, true); } else { @@ -184,7 +183,7 @@ public void Zap(EntityUid uid, EntityUid target, EntityUid user, DefibrillatorCo else { _chatManager.TrySendInGameICMessage(uid, Loc.GetString("defibrillator-no-mind"), - SharedChatSystem.InGameICChatType.Speak, true); + InGameICChatType.Speak, true); } } diff --git a/Content.Server/Mind/Commands/MakeSentientCommand.cs b/Content.Server/Mind/Commands/MakeSentientCommand.cs index 04a26d698e91ee..5e19d135b6fd7b 100644 --- a/Content.Server/Mind/Commands/MakeSentientCommand.cs +++ b/Content.Server/Mind/Commands/MakeSentientCommand.cs @@ -1,11 +1,7 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Emoting; -using Content.Server._EinsteinEngine.Language; using Content.Shared.Examine; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Systems; using Content.Shared.Mind.Components; using Content.Shared.Movement.Components; using Content.Shared.Speech; @@ -59,15 +55,6 @@ public static void MakeSentient(EntityUid uid, IEntityManager entityManager, boo { entityManager.EnsureComponent(uid); entityManager.EnsureComponent(uid); - - - var language = IoCManager.Resolve().GetEntitySystem(); - var speaker = entityManager.EnsureComponent(uid); - // If the entity already speaks some language (like monkey or robot), we do nothing else - // Otherwise, we give them the fallback language - if (speaker.SpokenLanguages.Count == 0) - language.AddLanguage(uid, SharedLanguageSystem.FallbackLanguagePrototype); - } entityManager.EnsureComponent(uid); diff --git a/Content.Server/Mobs/CritMobActionsSystem.cs b/Content.Server/Mobs/CritMobActionsSystem.cs index 3ca5653f230f33..c897102dca76b8 100644 --- a/Content.Server/Mobs/CritMobActionsSystem.cs +++ b/Content.Server/Mobs/CritMobActionsSystem.cs @@ -2,7 +2,6 @@ using Content.Server.Chat.Systems; using Content.Server.Popups; using Content.Server.Speech.Muting; -using Content.Shared.Chat; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; @@ -77,7 +76,7 @@ private void OnLastWords(EntityUid uid, MobStateActionsComponent component, Crit } lastWords += "..."; - _chat.TrySendInGameICMessage(uid, lastWords, SharedChatSystem.InGameICChatType.Whisper, SharedChatSystem.ChatTransmitRange.Normal, checkRadioPrefix: false, ignoreActionBlocker: true); + _chat.TrySendInGameICMessage(uid, lastWords, InGameICChatType.Whisper, ChatTransmitRange.Normal, checkRadioPrefix: false, ignoreActionBlocker: true); _host.ExecuteCommand(actor.PlayerSession, "ghost"); }); diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs index 8219e2d52c6ab2..558b1fc04dc62b 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SayKeyOperator.cs @@ -1,5 +1,4 @@ using Content.Server.Chat.Systems; -using Content.Shared.Chat; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; @@ -35,7 +34,7 @@ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTi return HTNOperatorStatus.Failed; var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, @string, SharedChatSystem.InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + _chat.TrySendInGameICMessage(speaker, @string, InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); return base.Update(blackboard, frameTime); } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs index 5efb70d48cb3c8..8a4c655a39b5d1 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/SpeakOperator.cs @@ -1,5 +1,4 @@ using Content.Server.Chat.Systems; -using Content.Shared.Chat; namespace Content.Server.NPC.HTN.PrimitiveTasks.Operators; @@ -26,7 +25,7 @@ public override void Initialize(IEntitySystemManager sysManager) public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTime) { var speaker = blackboard.GetValue(NPCBlackboard.Owner); - _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), SharedChatSystem.InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); + _chat.TrySendInGameICMessage(speaker, Loc.GetString(Speech), InGameICChatType.Speak, hideChat: Hidden, hideLog: Hidden); return base.Update(blackboard, frameTime); } diff --git a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs index 38db27619a9d79..2cc735194f6a65 100644 --- a/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs +++ b/Content.Server/NPC/HTN/PrimitiveTasks/Operators/Specific/MedibotInjectOperator.cs @@ -1,6 +1,5 @@ using Content.Server.Chat.Systems; using Content.Server.NPC.Components; -using Content.Shared.Chat; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Damage; using Content.Shared.Emag.Components; @@ -83,7 +82,7 @@ public override HTNOperatorStatus Update(NPCBlackboard blackboard, float frameTi _solutionContainer.TryAddReagent(injectable.Value, treatment.Reagent, treatment.Quantity, out _); _popup.PopupEntity(Loc.GetString("hypospray-component-feel-prick-message"), target, target); _audio.PlayPvs(botComp.InjectSound, target); - _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), SharedChatSystem.InGameICChatType.Speak, hideChat: true, hideLog: true); + _chat.TrySendInGameICMessage(owner, Loc.GetString("medibot-finish-inject"), InGameICChatType.Speak, hideChat: true, hideLog: true); return HTNOperatorStatus.Finished; } } diff --git a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs index c5e051abae2a3b..d18b044205c0e0 100644 --- a/Content.Server/Radio/EntitySystems/HeadsetSystem.cs +++ b/Content.Server/Radio/EntitySystems/HeadsetSystem.cs @@ -1,11 +1,8 @@ using Content.Server.Chat.Systems; using Content.Server.Emp; -using Content.Server._EinsteinEngine.Language; using Content.Server.Radio.Components; using Content.Shared.Inventory.Events; using Content.Shared.Radio; -using Content.Server.Speech; -using Content.Shared.Chat; using Content.Shared.Radio.Components; using Content.Shared.Radio.EntitySystems; using Robust.Shared.Network; @@ -17,7 +14,6 @@ public sealed class HeadsetSystem : SharedHeadsetSystem { [Dependency] private readonly INetManager _netMan = default!; [Dependency] private readonly RadioSystem _radio = default!; - [Dependency] private readonly LanguageSystem _language = default!; public override void Initialize() { @@ -103,16 +99,8 @@ public void SetEnabled(EntityUid uid, bool value, HeadsetComponent? component = private void OnHeadsetReceive(EntityUid uid, HeadsetComponent component, ref RadioReceiveEvent args) { - var parent = Transform(uid).ParentUid; - if (TryComp(parent, out ActorComponent? actor)) - { - var canUnderstand = _language.CanUnderstand(parent, args.Language.ID); - var msg = new MsgChatMessage - { - Message = canUnderstand ? args.OriginalChatMsg : args.LanguageObfuscatedChatMsg - }; - _netMan.ServerSendMessage(msg, actor.PlayerSession.Channel); - } + if (TryComp(Transform(uid).ParentUid, out ActorComponent? actor)) + _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); } private void OnEmpPulse(EntityUid uid, HeadsetComponent component, ref EmpPulseEvent args) diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 5eb6e377d39df1..c8867744a40b6b 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -9,7 +9,6 @@ using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; -using Content.Server._EinsteinEngine.Language; using Content.Shared.Power; using Content.Shared.Radio; using Content.Shared.Chat; @@ -29,7 +28,6 @@ public sealed class RadioDeviceSystem : EntitySystem [Dependency] private readonly RadioSystem _radio = default!; [Dependency] private readonly InteractionSystem _interaction = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; - [Dependency] private readonly LanguageSystem _language = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); @@ -219,8 +217,7 @@ private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref ("originalName", nameEv.VoiceName)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios - var message = args.OriginalChatMsg.Message; // The chat system will handle the rest and re-obfuscate if needed. - _chat.TrySendInGameICMessage(uid, message, SharedChatSystem.InGameICChatType.Whisper, SharedChatSystem.ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false, languageOverride: args.Language); + _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false); } private void OnIntercomEncryptionChannelsChanged(Entity ent, ref EncryptionChannelsChangedEvent args) diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 4b8889a2e38a52..bdc368fa93299b 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -1,15 +1,12 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; -using Content.Server._EinsteinEngine.Language; using Content.Server.Power.Components; using Content.Server.Radio.Components; -using Content.Server.Speech; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Radio; using Content.Shared.Radio.Components; using Content.Shared.Speech; -using Content.Shared._EinsteinEngine.Language; using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Player; @@ -31,7 +28,6 @@ public sealed class RadioSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly LanguageSystem _language = default!; // set used to prevent radio feedback loops. private readonly HashSet _messages = new(); @@ -51,7 +47,7 @@ private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent { if (args.Channel != null && component.Channels.Contains(args.Channel.ID)) { - SendRadioMessage(uid, args.Message, args.Channel, uid, args.Language); + SendRadioMessage(uid, args.Message, args.Channel, uid); args.Channel = null; // prevent duplicate messages from other listeners. } } @@ -59,23 +55,15 @@ private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args) { if (TryComp(uid, out ActorComponent? actor)) - { - // Einstein-Engines - languages mechanic - var listener = component.Owner; - var msg = args.OriginalChatMsg; - if (listener != null && !_language.CanUnderstand(listener, args.Language.ID)) - msg = args.LanguageObfuscatedChatMsg; - - _netMan.ServerSendMessage(new MsgChatMessage { Message = msg}, actor.PlayerSession.Channel); - } + _netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel); } /// /// Send radio message to all active radio listeners /// - public void SendRadioMessage(EntityUid messageSource, string message, ProtoId channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true) + public void SendRadioMessage(EntityUid messageSource, string message, ProtoId channel, EntityUid radioSource, bool escapeMarkup = true) { - SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, language: language); + SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup); } /// @@ -83,14 +71,8 @@ public void SendRadioMessage(EntityUid messageSource, string message, ProtoId /// Entity that spoke the message /// Entity that picked up the message and will send it, e.g. headset - public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, LanguagePrototype? language = null, bool escapeMarkup = true) + public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true) { - if (language == null) - language = _language.GetLanguage(messageSource); - - if (!language.SpeechOverride.AllowRadio) - return; - // TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this. if (!_messages.Add(message)) return; @@ -101,7 +83,6 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann var name = evt.VoiceName; name = FormattedMessage.EscapeText(name); - // most radios are relayed to chat, so lets parse the chat message beforehand SpeechVerbPrototype speech; if (evt.SpeechVerb != null && _prototype.TryIndex(evt.SpeechVerb, out var evntProto)) speech = evntProto; @@ -112,15 +93,24 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann ? FormattedMessage.EscapeText(message) : message; - var wrappedMessage = WrapRadioMessage(messageSource, channel, name, content, language); - var msg = new ChatMessage(ChatChannel.Radio, content, wrappedMessage, NetEntity.Invalid, null); - - // ... you guess it - var obfuscated = _language.ObfuscateSpeech(content, language); - var obfuscatedWrapped = WrapRadioMessage(messageSource, channel, name, obfuscated, language); - var notUdsMsg = new ChatMessage(ChatChannel.Radio, obfuscated, obfuscatedWrapped, NetEntity.Invalid, null); + var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", + ("color", channel.Color), + ("fontType", speech.FontId), + ("fontSize", speech.FontSize), + ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), + ("channel", $"\\[{channel.LocalizedName}\\]"), + ("name", name), + ("message", content)); - var ev = new RadioReceiveEvent(messageSource, channel, msg, notUdsMsg, language, radioSource); + // most radios are relayed to chat, so lets parse the chat message beforehand + var chat = new ChatMessage( + ChatChannel.Radio, + message, + wrappedMessage, + NetEntity.Invalid, + null); + var chatMsg = new MsgChatMessage { Message = chat }; + var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg); var sendAttemptEv = new RadioSendAttemptEvent(channel, radioSource); RaiseLocalEvent(ref sendAttemptEv); @@ -165,29 +155,10 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann else _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Radio message from {ToPrettyString(messageSource):user} on {channel.LocalizedName}: {message}"); - _replay.RecordServerMessage(msg); + _replay.RecordServerMessage(chat); _messages.Remove(message); } - private string WrapRadioMessage(EntityUid source, RadioChannelPrototype channel, string name, string message, LanguagePrototype language) - { - // TODO: code duplication with ChatSystem.WrapMessage - var speech = _chat.GetSpeechVerb(source, message); - var languageColor = channel.Color; - if (language.SpeechOverride.Color is { } colorOverride) - languageColor = Color.InterpolateBetween(languageColor, colorOverride, colorOverride.A); - - return Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap", - ("color", channel.Color), - ("languageColor", languageColor), - ("fontType", language.SpeechOverride.FontId ?? speech.FontId), - ("fontSize", language.SpeechOverride.FontSize ?? speech.FontSize), - ("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))), - ("channel", $"\\[{channel.LocalizedName}\\]"), - ("name", name), - ("message", message)); - } - /// private bool HasActiveServer(MapId mapId, string channelId) { diff --git a/Content.Server/Radio/RadioEvent.cs b/Content.Server/Radio/RadioEvent.cs index 55c0d9b736aa9f..fafa66674e3d5e 100644 --- a/Content.Server/Radio/RadioEvent.cs +++ b/Content.Server/Radio/RadioEvent.cs @@ -1,24 +1,11 @@ using Content.Shared.Chat; -using Content.Shared._EinsteinEngine.Language; using Content.Shared.Radio; namespace Content.Server.Radio; -/// -/// The message to display when the speaker can understand "language" -/// The message to display when the speaker cannot understand "language" -/// [ByRefEvent] -public readonly record struct RadioReceiveEvent( - // Einstein-Engines - languages mechanic - EntityUid MessageSource, - RadioChannelPrototype Channel, - ChatMessage OriginalChatMsg, - ChatMessage LanguageObfuscatedChatMsg, - LanguagePrototype Language, - EntityUid RadioSource +public readonly record struct RadioReceiveEvent(string Message, EntityUid MessageSource, RadioChannelPrototype Channel, EntityUid RadioSource, MsgChatMessage ChatMsg); -); /// /// Use this event to cancel sending message per receiver /// diff --git a/Content.Server/RatKing/RatKingSystem.cs b/Content.Server/RatKing/RatKingSystem.cs index 1b3d520b7b3af9..4b82dba33590f4 100644 --- a/Content.Server/RatKing/RatKingSystem.cs +++ b/Content.Server/RatKing/RatKingSystem.cs @@ -6,7 +6,6 @@ using Content.Server.NPC.Systems; using Content.Server.Popups; using Content.Shared.Atmos; -using Content.Shared.Chat; using Content.Shared.Dataset; using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.EntitySystems; @@ -125,7 +124,7 @@ public override void DoCommandCallout(EntityUid uid, RatKingComponent component) return; var msg = Random.Pick(datasetPrototype.Values); - _chat.TrySendInGameICMessage(uid, msg, SharedChatSystem.InGameICChatType.Speak, true); + _chat.TrySendInGameICMessage(uid, msg, InGameICChatType.Speak, true); } } } diff --git a/Content.Server/Speech/EntitySystems/ListeningSystem.cs b/Content.Server/Speech/EntitySystems/ListeningSystem.cs index c2810c06f9c282..ea3569e055c9ed 100644 --- a/Content.Server/Speech/EntitySystems/ListeningSystem.cs +++ b/Content.Server/Speech/EntitySystems/ListeningSystem.cs @@ -8,7 +8,6 @@ namespace Content.Server.Speech.EntitySystems; /// public sealed class ListeningSystem : EntitySystem { - [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly SharedTransformSystem _xforms = default!; public override void Initialize() @@ -19,11 +18,10 @@ public override void Initialize() private void OnSpeak(EntitySpokeEvent ev) { - PingListeners(ev.Source, ev.Message, ev.IsWhisper); - + PingListeners(ev.Source, ev.Message, ev.ObfuscatedMessage); } - public void PingListeners(EntityUid source, string message, bool isWhisper) + public void PingListeners(EntityUid source, string message, string? obfuscatedMessage) { // TODO whispering / audio volume? Microphone sensitivity? // for now, whispering just arbitrarily reduces the listener's max range. @@ -34,7 +32,7 @@ public void PingListeners(EntityUid source, string message, bool isWhisper) var attemptEv = new ListenAttemptEvent(source); var ev = new ListenEvent(message, source); - var obfuscatedEv = !isWhisper ? null : new ListenEvent(_chat.ObfuscateMessageReadability(message), source); + var obfuscatedEv = obfuscatedMessage == null ? null : new ListenEvent(obfuscatedMessage, source); var query = EntityQueryEnumerator(); while(query.MoveNext(out var listenerUid, out var listener, out var xform)) diff --git a/Content.Server/Speech/Muting/MutingSystem.cs b/Content.Server/Speech/Muting/MutingSystem.cs index 5483991a3d461e..238d501e249d3b 100644 --- a/Content.Server/Speech/Muting/MutingSystem.cs +++ b/Content.Server/Speech/Muting/MutingSystem.cs @@ -1,4 +1,3 @@ -using Content.Server._EinsteinEngine.Language; using Content.Server.Abilities.Mime; using Content.Server.Chat.Systems; using Content.Server.Popups; @@ -13,7 +12,6 @@ namespace Content.Server.Speech.Muting { public sealed class MutingSystem : EntitySystem { - [Dependency] private readonly LanguageSystem _languages = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; public override void Initialize() { @@ -49,9 +47,7 @@ private void OnScreamAction(EntityUid uid, MutedComponent component, ScreamActio private void OnSpeakAttempt(EntityUid uid, MutedComponent component, SpeakAttemptEvent args) { - var language = _languages.GetLanguage(uid); - if (!language.SpeechOverride.RequireSpeech) - return; // Cannot mute if there's no speech involved + // TODO something better than this. if (HasComp(uid)) _popupSystem.PopupEntity(Loc.GetString("mime-cant-speak"), uid, uid); diff --git a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs index 003e2c5167270c..581ac197197f63 100644 --- a/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs +++ b/Content.Server/SurveillanceCamera/Systems/SurveillanceCameraSpeakerSystem.cs @@ -1,4 +1,3 @@ -using Content.Server._EinsteinEngine.Language; using Content.Server.Chat.Systems; using Content.Server.Speech; using Content.Shared.Speech; @@ -17,7 +16,6 @@ public sealed class SurveillanceCameraSpeakerSystem : EntitySystem [Dependency] private readonly SpeechSoundSystem _speechSound = default!; [Dependency] private readonly ChatSystem _chatSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly LanguageSystem _language = default!; /// public override void Initialize() @@ -54,6 +52,6 @@ private void OnSpeechSent(EntityUid uid, SurveillanceCameraSpeakerComponent comp ("originalName", nameEv.VoiceName)); // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios - _chatSystem.TrySendInGameICMessage(uid, args.Message, SharedChatSystem.InGameICChatType.Speak, SharedChatSystem.ChatTransmitRange.GhostRangeLimit, nameOverride: name, languageOverride: _language.GetLanguage(args.Speaker)); + _chatSystem.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, ChatTransmitRange.GhostRangeLimit, nameOverride: name); } } diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 4e11a1f0f9566a..ec462ae23e8f06 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -23,7 +23,6 @@ using Robust.Shared.Random; using System.Linq; using System.Numerics; -using Content.Shared.Chat; namespace Content.Server.Weapons.Melee; @@ -250,7 +249,7 @@ private void OnSpeechHit(EntityUid owner, MeleeSpeechComponent comp, MeleeHitEve if (comp.Battlecry != null)//If the battlecry is set to empty, doesn't speak { - _chat.TrySendInGameICMessage(args.User, comp.Battlecry, SharedChatSystem.InGameICChatType.Speak, true, true, checkRadioPrefix: false); //Speech that isn't sent to chat or adminlogs + _chat.TrySendInGameICMessage(args.User, comp.Battlecry, InGameICChatType.Speak, true, true, checkRadioPrefix: false); //Speech that isn't sent to chat or adminlogs } } diff --git a/Content.Server/_EinsteinEngine/Language/Commands/AdminLanguageCommand.cs b/Content.Server/_EinsteinEngine/Language/Commands/AdminLanguageCommand.cs deleted file mode 100644 index c53aac830033ba..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/Commands/AdminLanguageCommand.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Content.Server.Administration; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Content.Shared._EinsteinEngine.Language.Systems; -using Content.Shared.Administration; -using Robust.Shared.Prototypes; -using Robust.Shared.Toolshed; -using Robust.Shared.Toolshed.Syntax; -using Robust.Shared.Toolshed.TypeParsers; - -namespace Content.Server._EinsteinEngine.Language.Commands; - -[ToolshedCommand(Name = "language"), AdminCommand(AdminFlags.Admin)] -public sealed class AdminLanguageCommand : ToolshedCommand -{ - private LanguageSystem? _languagesField; - private LanguageSystem Languages => _languagesField ??= GetSys(); - - [CommandImplementation("add")] - public EntityUid AddLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref, - [CommandArgument] bool canSpeak = true, - [CommandArgument] bool canUnderstand = true - ) - { - var language = @ref.Evaluate(ctx)!; - - if (language == SharedLanguageSystem.UniversalPrototype) - { - EnsureComp(input); - Languages.UpdateEntityLanguages(input); - } - else - { - EnsureComp(input); - Languages.AddLanguage(input, language, canSpeak, canUnderstand); - } - - return input; - } - - [CommandImplementation("rm")] - public EntityUid RemoveLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref, - [CommandArgument] bool removeSpeak = true, - [CommandArgument] bool removeUnderstand = true - ) - { - var language = @ref.Evaluate(ctx)!; - if (language == SharedLanguageSystem.UniversalPrototype && HasComp(input)) - { - RemComp(input); - EnsureComp(input); - } - // We execute this branch even in case of universal so that it gets removed if it was added manually to the LanguageKnowledge - Languages.RemoveLanguage(input, language, removeSpeak, removeUnderstand); - - return input; - } - - [CommandImplementation("lsspoken")] - public IEnumerable> ListSpoken([PipedArgument] EntityUid input) - { - return Languages.GetSpokenLanguages(input); - } - - [CommandImplementation("lsunderstood")] - public IEnumerable> ListUnderstood([PipedArgument] EntityUid input) - { - return Languages.GetUnderstoodLanguages(input); - } -} diff --git a/Content.Server/_EinsteinEngine/Language/Commands/AdminTranslatorCommand.cs b/Content.Server/_EinsteinEngine/Language/Commands/AdminTranslatorCommand.cs deleted file mode 100644 index 242af0f3f6d63e..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/Commands/AdminTranslatorCommand.cs +++ /dev/null @@ -1,156 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Server.Administration; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Content.Shared._EinsteinEngine.Language.Systems; -using Content.Shared.Administration; -using Robust.Server.Containers; -using Robust.Shared.Prototypes; -using Robust.Shared.Toolshed; -using Robust.Shared.Toolshed.Syntax; -using Robust.Shared.Toolshed.TypeParsers; - -namespace Content.Server._EinsteinEngine.Language.Commands; - -[ToolshedCommand(Name = "translator"), AdminCommand(AdminFlags.Admin)] -public sealed class AdminTranslatorCommand : ToolshedCommand -{ - private LanguageSystem? _languagesField; - private ContainerSystem? _containersField; - - private ContainerSystem Containers => _containersField ??= GetSys(); - private LanguageSystem Languages => _languagesField ??= GetSys(); - - [CommandImplementation("addlang")] - public EntityUid AddLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref, - [CommandArgument] bool addSpeak = true, - [CommandArgument] bool addUnderstand = true - ) - { - var language = @ref.Evaluate(ctx)!; - // noob trap - needs a universallanguagespeakercomponent - if (language == SharedLanguageSystem.UniversalPrototype) - throw new ArgumentException(Loc.GetString("command-language-error-this-will-not-work")); - - if (!TryGetTranslatorComp(input, out var translator)) - throw new ArgumentException(Loc.GetString("command-language-error-not-a-translator", ("entity", input))); - - if (addSpeak && !translator.SpokenLanguages.Contains(language)) - translator.SpokenLanguages.Add(language); - if (addUnderstand && !translator.UnderstoodLanguages.Contains(language)) - translator.UnderstoodLanguages.Add(language); - - UpdateTranslatorHolder(input); - - return input; - } - - [CommandImplementation("rmlang")] - public EntityUid RemoveLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref, - [CommandArgument] bool removeSpeak = true, - [CommandArgument] bool removeUnderstand = true - ) - { - var language = @ref.Evaluate(ctx)!; - if (!TryGetTranslatorComp(input, out var translator)) - throw new ArgumentException(Loc.GetString("command-language-error-not-a-translator", ("entity", input))); - - if (removeSpeak) - translator.SpokenLanguages.Remove(language); - if (removeUnderstand) - translator.UnderstoodLanguages.Remove(language); - - UpdateTranslatorHolder(input); - - return input; - } - - [CommandImplementation("addrequired")] - public EntityUid AddRequiredLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref) - { - var language = @ref.Evaluate(ctx)!; - if (!TryGetTranslatorComp(input, out var translator)) - throw new ArgumentException(Loc.GetString("command-language-error-not-a-translator", ("entity", input))); - - if (!translator.RequiredLanguages.Contains(language)) - { - translator.RequiredLanguages.Add(language); - UpdateTranslatorHolder(input); - } - - return input; - } - - [CommandImplementation("rmrequired")] - public EntityUid RemoveRequiredLanguage( - [CommandInvocationContext] IInvocationContext ctx, - [PipedArgument] EntityUid input, - [CommandArgument] ValueRef> @ref) - { - var language = @ref.Evaluate(ctx)!; - if (!TryGetTranslatorComp(input, out var translator)) - throw new ArgumentException(Loc.GetString("command-language-error-not-a-translator", ("entity", input))); - - if (translator.RequiredLanguages.Remove(language)) - UpdateTranslatorHolder(input); - - return input; - } - - [CommandImplementation("lsspoken")] - public IEnumerable> ListSpoken([PipedArgument] EntityUid input) - { - if (!TryGetTranslatorComp(input, out var translator)) - return []; - return translator.SpokenLanguages; - } - - [CommandImplementation("lsunderstood")] - public IEnumerable> ListUnderstood([PipedArgument] EntityUid input) - { - if (!TryGetTranslatorComp(input, out var translator)) - return []; - return translator.UnderstoodLanguages; - } - - [CommandImplementation("lsrequired")] - public IEnumerable> ListRequired([PipedArgument] EntityUid input) - { - if (!TryGetTranslatorComp(input, out var translator)) - return []; - return translator.RequiredLanguages; - } - - private bool TryGetTranslatorComp(EntityUid uid, [NotNullWhen(true)] out BaseTranslatorComponent? translator) - { - if (TryComp(uid, out var handheld)) - translator = handheld; - else if (TryComp(uid, out var implant)) - translator = implant; - else if (TryComp(uid, out var intrinsic)) - translator = intrinsic; - else - translator = null; - - return translator != null; - } - - private void UpdateTranslatorHolder(EntityUid translator) - { - if (!Containers.TryGetContainingContainer(translator, out var cont) - || cont.Owner is not { Valid: true } holder) - return; - - Languages.UpdateEntityLanguages(holder); - } -} diff --git a/Content.Server/_EinsteinEngine/Language/Commands/ListLanguagesCommand.cs b/Content.Server/_EinsteinEngine/Language/Commands/ListLanguagesCommand.cs deleted file mode 100644 index f70b32e4755c08..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/Commands/ListLanguagesCommand.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Linq; -using Content.Shared.Administration; -using Robust.Shared.Console; -using Robust.Shared.Enums; - -namespace Content.Server._EinsteinEngine.Language.Commands; - -[AnyCommand] -public sealed class ListLanguagesCommand : IConsoleCommand -{ - public string Command => "languagelist"; - public string Description => Loc.GetString("command-list-langs-desc"); - public string Help => Loc.GetString("command-list-langs-help", ("command", Command)); - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (shell.Player is not { } player) - { - shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); - return; - } - - if (player.Status != SessionStatus.InGame) - return; - - if (player.AttachedEntity is not {} playerEntity) - { - shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); - return; - } - - var languages = IoCManager.Resolve().GetEntitySystem(); - var currentLang = languages.GetLanguage(playerEntity).ID; - - shell.WriteLine(Loc.GetString("command-language-spoken")); - var spoken = languages.GetSpokenLanguages(playerEntity); - for (int i = 0; i < spoken.Count; i++) - { - var lang = spoken[i]; - shell.WriteLine(lang == currentLang - ? Loc.GetString("command-language-current-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang))) - : Loc.GetString("command-language-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang)))); - } - - shell.WriteLine(Loc.GetString("command-language-understood")); - var understood = languages.GetUnderstoodLanguages(playerEntity); - for (int i = 0; i < understood.Count; i++) - { - var lang = understood[i]; - shell.WriteLine(Loc.GetString("command-language-entry", ("id", i + 1), ("language", lang), ("name", LanguageName(lang)))); - } - } - - private string LanguageName(string id) - { - return Loc.GetString($"language-{id}-name"); - } -} diff --git a/Content.Server/_EinsteinEngine/Language/Commands/SayLanguageCommand.cs b/Content.Server/_EinsteinEngine/Language/Commands/SayLanguageCommand.cs deleted file mode 100644 index 6cdabec5b9e311..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/Commands/SayLanguageCommand.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Content.Server.Chat.Systems; -using Content.Shared.Administration; -using Content.Shared.Chat; -using Robust.Shared.Console; -using Robust.Shared.Enums; - -namespace Content.Server._EinsteinEngine.Language.Commands; - -[AnyCommand] -public sealed class SayLanguageCommand : IConsoleCommand -{ - public string Command => "saylang"; - public string Description => Loc.GetString("command-saylang-desc"); - public string Help => Loc.GetString("command-saylang-help", ("command", Command)); - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (shell.Player is not { } player) - { - shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); - return; - } - - if (player.Status != SessionStatus.InGame) - return; - - if (player.AttachedEntity is not {} playerEntity) - { - shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); - return; - } - - if (args.Length < 2) - return; - - 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(); - - if (!SelectLanguageCommand.TryParseLanguageArgument(languages, playerEntity, args[0], out var failReason, out var language)) - { - shell.WriteError(failReason); - return; - } - - chats.TrySendInGameICMessage(playerEntity, message, SharedChatSystem.InGameICChatType.Speak, SharedChatSystem.ChatTransmitRange.Normal, false, shell, player, languageOverride: language); - } -} diff --git a/Content.Server/_EinsteinEngine/Language/Commands/SelectLanguageCommand.cs b/Content.Server/_EinsteinEngine/Language/Commands/SelectLanguageCommand.cs deleted file mode 100644 index d6fe399d6e19a7..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/Commands/SelectLanguageCommand.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared.Administration; -using Robust.Shared.Console; -using Robust.Shared.Enums; - -namespace Content.Server._EinsteinEngine.Language.Commands; - -[AnyCommand] -public sealed class SelectLanguageCommand : IConsoleCommand -{ - public string Command => "languageselect"; - public string Description => Loc.GetString("command-language-select-desc"); - public string Help => Loc.GetString("command-language-select-help", ("command", Command)); - - public void Execute(IConsoleShell shell, string argStr, string[] args) - { - if (shell.Player is not { } player) - { - shell.WriteError(Loc.GetString("shell-cannot-run-command-from-server")); - return; - } - - if (player.Status != SessionStatus.InGame) - return; - - if (player.AttachedEntity is not { } playerEntity) - { - shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); - return; - } - - if (args.Length < 1) - return; - - var languageId = args[0]; - - var languages = IoCManager.Resolve().GetEntitySystem(); - - if (!TryParseLanguageArgument(languages, playerEntity, args[0], out var failReason, out var language)) - { - shell.WriteError(failReason); - return; - } - - languages.SetLanguage(playerEntity, language.ID); - } - - - // TODO: find a better place for this method - /// - /// Tries to parse the input argument as either a language ID or the position of the language in the list of languages - /// the entity can speak. Returns true if sucessful. - /// - public static bool TryParseLanguageArgument( - LanguageSystem languageSystem, - EntityUid speaker, - string input, - [NotNullWhen(false)] out string? failureReason, - [NotNullWhen(true)] out LanguagePrototype? language) - { - failureReason = null; - language = null; - - if (int.TryParse(input, out var num)) - { - // The argument is a number - var spoken = languageSystem.GetSpokenLanguages(speaker); - if (num > 0 && num - 1 < spoken.Count) - language = languageSystem.GetLanguagePrototype(spoken[num - 1]); - - if (language != null) // the ability to speak it is implied - return true; - - failureReason = Loc.GetString("command-language-invalid-number", ("total", spoken.Count)); - return false; - } - else - { - // The argument is a language ID - language = languageSystem.GetLanguagePrototype(input); - - if (language != null && languageSystem.CanSpeak(speaker, language.ID)) - return true; - - failureReason = Loc.GetString("command-language-invalid-language", ("id", input)); - return false; - } - } -} diff --git a/Content.Server/_EinsteinEngine/Language/LanguageKnowledgeComponent.cs b/Content.Server/_EinsteinEngine/Language/LanguageKnowledgeComponent.cs deleted file mode 100644 index 4c0a7af92d6785..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/LanguageKnowledgeComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared._EinsteinEngine.Language; -using Robust.Shared.Prototypes; - -namespace Content.Server._EinsteinEngine.Language; - -/// -/// Stores data about entities' intrinsic language knowledge. -/// -[RegisterComponent] -public sealed partial class LanguageKnowledgeComponent : Component -{ - /// - /// List of languages this entity can speak without any external tools. - /// - [DataField("speaks", required: true)] - public List> SpokenLanguages = new(); - - /// - /// List of languages this entity can understand without any external tools. - /// - [DataField("understands", required: true)] - public List> UnderstoodLanguages = new(); -} diff --git a/Content.Server/_EinsteinEngine/Language/LanguageSystem.cs b/Content.Server/_EinsteinEngine/Language/LanguageSystem.cs deleted file mode 100644 index c5dd1e65cc1358..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/LanguageSystem.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System.Linq; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Events; -using Content.Shared._EinsteinEngine.Language.Systems; -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; - -namespace Content.Server._EinsteinEngine.Language; - -public sealed partial class LanguageSystem : SharedLanguageSystem -{ - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnInitLanguageKnowledge); - SubscribeLocalEvent(OnInitLanguageSpeaker); - SubscribeLocalEvent(OnGetLanguageState); - SubscribeLocalEvent(OnDetermineUniversalLanguages); - SubscribeNetworkEvent(OnClientSetLanguage); - - SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); - SubscribeLocalEvent((uid, _, _) => UpdateEntityLanguages(uid)); - } - - #region event handling - private void OnInitLanguageKnowledge(Entity ent, ref MapInitEvent args) - { - EnsureComp(ent.Owner); - } - - private void OnInitLanguageSpeaker(Entity ent, ref MapInitEvent args) - { - if (string.IsNullOrEmpty(ent.Comp.CurrentLanguage)) - ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); - - UpdateEntityLanguages(ent!); - } - - private void OnGetLanguageState(Entity entity, ref ComponentGetState args) - { - args.State = new LanguageSpeakerComponent.State - { - CurrentLanguage = entity.Comp.CurrentLanguage, - SpokenLanguages = entity.Comp.SpokenLanguages, - UnderstoodLanguages = entity.Comp.UnderstoodLanguages - }; - } - - private void OnDetermineUniversalLanguages(Entity entity, ref DetermineEntityLanguagesEvent ev) - { - // We only add it as a spoken language; CanUnderstand checks for ULSC itself. - if (entity.Comp.Enabled) - ev.SpokenLanguages.Add(UniversalPrototype); - } - - - private void OnClientSetLanguage(LanguagesSetMessage message, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity is not { Valid: true } uid) - return; - - var language = GetLanguagePrototype(message.CurrentLanguage); - if (language == null || !CanSpeak(uid, language.ID)) - return; - - SetLanguage(uid, language.ID); - } - - #endregion - - #region public api - - public bool CanUnderstand(Entity ent, ProtoId language) - { - if (language == UniversalPrototype || TryComp(ent, out var uni) && uni.Enabled) - return true; - - return Resolve(ent, ref ent.Comp, logMissing: false) && ent.Comp.UnderstoodLanguages.Contains(language); - } - - public bool CanSpeak(Entity ent, ProtoId language) - { - if (!Resolve(ent, ref ent.Comp, logMissing: false)) - return false; - - return ent.Comp.SpokenLanguages.Contains(language); - } - - /// - /// Returns the current language of the given entity, assumes Universal if it's not a language speaker. - /// - public LanguagePrototype GetLanguage(Entity ent) - { - if (!Resolve(ent, ref ent.Comp, logMissing: false) - || string.IsNullOrEmpty(ent.Comp.CurrentLanguage) - || !_prototype.TryIndex(ent.Comp.CurrentLanguage, out var proto) - ) - return Universal; - - return proto; - } - - /// - /// Returns the list of languages this entity can speak. - /// - /// This simply returns the value of . - public List> GetSpokenLanguages(EntityUid uid) - { - return TryComp(uid, out var component) ? component.SpokenLanguages : []; - } - - /// - /// Returns the list of languages this entity can understand. - /// This simply returns the value of . - public List> GetUnderstoodLanguages(EntityUid uid) - { - return TryComp(uid, out var component) ? component.UnderstoodLanguages : []; - } - - public void SetLanguage(Entity ent, ProtoId language) - { - if (!CanSpeak(ent, language) - || !Resolve(ent, ref ent.Comp) - || ent.Comp.CurrentLanguage == language) - return; - - ent.Comp.CurrentLanguage = language; - RaiseLocalEvent(ent, new LanguagesUpdateEvent(), true); - Dirty(ent); - } - - /// - /// Adds a new language to the respective lists of intrinsically known languages of the given entity. - /// - public void AddLanguage( - EntityUid uid, - ProtoId language, - bool addSpoken = true, - bool addUnderstood = true) - { - EnsureComp(uid, out var knowledge); - EnsureComp(uid, out var speaker); - - if (addSpoken && !knowledge.SpokenLanguages.Contains(language)) - knowledge.SpokenLanguages.Add(language); - - if (addUnderstood && !knowledge.UnderstoodLanguages.Contains(language)) - knowledge.UnderstoodLanguages.Add(language); - - UpdateEntityLanguages((uid, speaker)); - } - - /// - /// Removes a language from the respective lists of intrinsically known languages of the given entity. - /// - public void RemoveLanguage( - Entity ent, - ProtoId language, - bool removeSpoken = true, - bool removeUnderstood = true) - { - if (!Resolve(ent, ref ent.Comp, false)) - return; - - if (removeSpoken) - ent.Comp.SpokenLanguages.Remove(language); - - if (removeUnderstood) - ent.Comp.UnderstoodLanguages.Remove(language); - - // We don't ensure that the entity has a speaker comp. If it doesn't... Well, woe be the caller of this method. - UpdateEntityLanguages(ent.Owner); - } - - /// - /// 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. - /// - /// True if the current language was modified, false otherwise. - public bool EnsureValidLanguage(Entity ent) - { - if (!Resolve(ent, ref ent.Comp, false)) - return false; - - if (!ent.Comp.SpokenLanguages.Contains(ent.Comp.CurrentLanguage)) - { - ent.Comp.CurrentLanguage = ent.Comp.SpokenLanguages.FirstOrDefault(UniversalPrototype); - RaiseLocalEvent(ent, new LanguagesUpdateEvent()); - Dirty(ent); - return true; - } - - return false; - } - - /// - /// Immediately refreshes the cached lists of spoken and understood languages for the given entity. - /// - public void UpdateEntityLanguages(Entity ent) - { - if (!Resolve(ent, ref ent.Comp, false)) - return; - - var ev = new DetermineEntityLanguagesEvent(); - // We add the intrinsically known languages first so other systems can manipulate them easily - if (TryComp(ent, out var knowledge)) - { - foreach (var spoken in knowledge.SpokenLanguages) - ev.SpokenLanguages.Add(spoken); - - foreach (var understood in knowledge.UnderstoodLanguages) - ev.UnderstoodLanguages.Add(understood); - } - - RaiseLocalEvent(ent, ref ev); - - ent.Comp.SpokenLanguages.Clear(); - ent.Comp.UnderstoodLanguages.Clear(); - - ent.Comp.SpokenLanguages.AddRange(ev.SpokenLanguages); - ent.Comp.UnderstoodLanguages.AddRange(ev.UnderstoodLanguages); - - // If EnsureValidLanguage returns true, it also raises a LanguagesUpdateEvent, so we try to avoid raising it twice in that case. - if (!EnsureValidLanguage(ent)) - RaiseLocalEvent(ent, new LanguagesUpdateEvent()); - - Dirty(ent); - } - - #endregion -} diff --git a/Content.Server/_EinsteinEngine/Language/TranslatorImplantSystem.cs b/Content.Server/_EinsteinEngine/Language/TranslatorImplantSystem.cs deleted file mode 100644 index 64e33a302e34c5..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/TranslatorImplantSystem.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Events; -using Content.Shared.Implants.Components; -using Robust.Shared.Containers; - -namespace Content.Server._EinsteinEngine.Language; - -public sealed class TranslatorImplantSystem : EntitySystem -{ - [Dependency] private readonly LanguageSystem _language = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnImplant); - SubscribeLocalEvent(OnDeImplant); - SubscribeLocalEvent(OnDetermineLanguages); - } - - private void OnImplant(EntityUid uid, TranslatorImplantComponent component, EntGotInsertedIntoContainerMessage args) - { - if (args.Container.ID != ImplanterComponent.ImplantSlotId) - return; - - var implantee = Transform(uid).ParentUid; - if (implantee is not { Valid: true } || !TryComp(implantee, out var knowledge)) - return; - - component.Enabled = true; - // To operate an implant, you need to know its required language intrinsically, because like... it connects to your brain or something. - // So external translators or other implants can't help you operate it. - component.SpokenRequirementSatisfied = TranslatorSystem.CheckLanguagesMatch( - component.RequiredLanguages, knowledge.SpokenLanguages, component.RequiresAllLanguages); - - component.UnderstoodRequirementSatisfied = TranslatorSystem.CheckLanguagesMatch( - component.RequiredLanguages, knowledge.UnderstoodLanguages, component.RequiresAllLanguages); - - _language.UpdateEntityLanguages(implantee); - } - - private void OnDeImplant(EntityUid uid, TranslatorImplantComponent component, EntGotRemovedFromContainerMessage args) - { - // Even though the description of this event says it gets raised BEFORE reparenting, that's actually false... - component.Enabled = component.SpokenRequirementSatisfied = component.UnderstoodRequirementSatisfied = false; - - if (TryComp(uid, out var subdermal) && subdermal.ImplantedEntity is { Valid: true} implantee) - _language.UpdateEntityLanguages(implantee); - } - - private void OnDetermineLanguages(EntityUid uid, ImplantedComponent component, ref DetermineEntityLanguagesEvent args) - { - // TODO: might wanna find a better solution, i just can't come up with something viable - foreach (var implant in component.ImplantContainer.ContainedEntities) - { - if (!TryComp(implant, out var translator) || !translator.Enabled) - continue; - - if (translator.SpokenRequirementSatisfied) - foreach (var language in translator.SpokenLanguages) - args.SpokenLanguages.Add(language); - - if (translator.UnderstoodRequirementSatisfied) - foreach (var language in translator.UnderstoodLanguages) - args.UnderstoodLanguages.Add(language); - } - } -} diff --git a/Content.Server/_EinsteinEngine/Language/TranslatorSystem.cs b/Content.Server/_EinsteinEngine/Language/TranslatorSystem.cs deleted file mode 100644 index 415b8887d3d78f..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/TranslatorSystem.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Linq; -using Content.Server.Popups; -using Content.Server.PowerCell; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Content.Shared._EinsteinEngine.Language.Events; -using Content.Shared._EinsteinEngine.Language.Systems; -using Content.Shared.Interaction; -using Content.Shared.PowerCell; -using Robust.Shared.Containers; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; - -namespace Content.Server._EinsteinEngine.Language; - -public sealed class TranslatorSystem : SharedTranslatorSystem -{ - [Dependency] private readonly SharedContainerSystem _containers = default!; - [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly LanguageSystem _language = default!; - [Dependency] private readonly PowerCellSystem _powerCell = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnDetermineLanguages); - SubscribeLocalEvent(OnProxyDetermineLanguages); - - SubscribeLocalEvent(OnTranslatorInserted); - SubscribeLocalEvent(OnTranslatorParentChanged); - SubscribeLocalEvent(OnTranslatorToggle); - SubscribeLocalEvent(OnPowerCellSlotEmpty); - } - - private void OnDetermineLanguages(EntityUid uid, IntrinsicTranslatorComponent component, DetermineEntityLanguagesEvent ev) - { - if (!component.Enabled - || component.LifeStage >= ComponentLifeStage.Removing - || !TryComp(uid, out var knowledge) - || !_powerCell.HasActivatableCharge(uid)) - return; - - CopyLanguages(component, ev, knowledge); - } - - private void OnProxyDetermineLanguages(EntityUid uid, HoldsTranslatorComponent component, DetermineEntityLanguagesEvent ev) - { - if (!TryComp(uid, out var knowledge)) - return; - - foreach (var (translator, translatorComp) in component.Translators.ToArray()) - { - if (!translatorComp.Enabled || !_powerCell.HasActivatableCharge(uid)) - continue; - - if (!_containers.TryGetContainingContainer(translator, out var container) || container.Owner != uid) - { - component.Translators.RemoveWhere(it => it.Owner == translator); - continue; - } - - CopyLanguages(translatorComp, ev, knowledge); - } - } - - private void OnTranslatorInserted(EntityUid translator, HandheldTranslatorComponent component, EntGotInsertedIntoContainerMessage args) - { - if (args.Container.Owner is not {Valid: true} holder || !HasComp(holder)) - return; - - var intrinsic = EnsureComp(holder); - intrinsic.Translators.Add((translator, component)); - - _language.UpdateEntityLanguages(holder); - } - - private void OnTranslatorParentChanged(EntityUid translator, HandheldTranslatorComponent component, EntParentChangedMessage args) - { - if (!HasComp(args.OldParent)) - return; - - // Update the translator on the next tick - this is necessary because there's a good chance the removal from a container - // Was caused by the player moving the translator within their inventory rather than removing it. - // If that is not the case, then OnProxyDetermineLanguages will remove this translator from HoldsTranslatorComponent.Translators. - Timer.Spawn(0, () => - { - if (Exists(args.OldParent) && HasComp(args.OldParent)) - _language.UpdateEntityLanguages(args.OldParent.Value); - }); - } - - private void OnTranslatorToggle(EntityUid translator, HandheldTranslatorComponent translatorComp, ActivateInWorldEvent args) - { - if (!translatorComp.ToggleOnInteract) - return; - - // This will show a popup if false - var hasPower = _powerCell.HasDrawCharge(translator); - var isEnabled = !translatorComp.Enabled && hasPower; - - translatorComp.Enabled = isEnabled; - _powerCell.SetDrawEnabled(translator, isEnabled); - - if (_containers.TryGetContainingContainer(translator, out var holderCont) - && holderCont.Owner is var holder - && TryComp(holder, out var languageComp)) - { - // The first new spoken language added by this translator, or null - var firstNewLanguage = translatorComp.SpokenLanguages.FirstOrDefault(it => !languageComp.SpokenLanguages.Contains(it)); - _language.UpdateEntityLanguages(holder); - - // Update the current language of the entity if necessary - if (isEnabled && translatorComp.SetLanguageOnInteract && firstNewLanguage is {}) - _language.SetLanguage((holder, languageComp), firstNewLanguage); - } - - OnAppearanceChange(translator, translatorComp); - - if (hasPower) - { - var loc = isEnabled ? "translator-component-turnon" : "translator-component-shutoff"; - var message = Loc.GetString(loc, ("translator", translator)); - _popup.PopupEntity(message, translator, args.User); - } - } - - private void OnPowerCellSlotEmpty(EntityUid translator, HandheldTranslatorComponent component, PowerCellSlotEmptyEvent args) - { - component.Enabled = false; - _powerCell.SetDrawEnabled(translator, false); - OnAppearanceChange(translator, component); - - if (_containers.TryGetContainingContainer(translator, out var holderCont) && HasComp(holderCont.Owner)) - _language.UpdateEntityLanguages(holderCont.Owner); - } - - private void CopyLanguages(BaseTranslatorComponent from, DetermineEntityLanguagesEvent to, LanguageKnowledgeComponent knowledge) - { - var addSpoken = CheckLanguagesMatch(from.RequiredLanguages, knowledge.SpokenLanguages, from.RequiresAllLanguages); - var addUnderstood = CheckLanguagesMatch(from.RequiredLanguages, knowledge.UnderstoodLanguages, from.RequiresAllLanguages); - - if (addSpoken) - foreach (var language in from.SpokenLanguages) - to.SpokenLanguages.Add(language); - - if (addUnderstood) - foreach (var language in from.UnderstoodLanguages) - to.UnderstoodLanguages.Add(language); - } - - /// - /// Checks whether any OR all required languages are provided. Used for utility purposes. - /// - public static bool CheckLanguagesMatch(ICollection> required, ICollection> provided, bool requireAll) - { - if (required.Count == 0) - return true; - - return requireAll - ? required.All(provided.Contains) - : required.Any(provided.Contains); - } -} diff --git a/Content.Server/_EinsteinEngine/Language/UniversalLanguageSpeakerComponent.cs b/Content.Server/_EinsteinEngine/Language/UniversalLanguageSpeakerComponent.cs deleted file mode 100644 index 3159d90191021f..00000000000000 --- a/Content.Server/_EinsteinEngine/Language/UniversalLanguageSpeakerComponent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Content.Server._EinsteinEngine.Language; - -// -// Signifies that this entity can speak and understand any language. -// Applies to such entities as ghosts. -// -[RegisterComponent] -public sealed partial class UniversalLanguageSpeakerComponent : Component -{ - [DataField] - public bool Enabled = true; -} diff --git a/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitComponent.cs b/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitComponent.cs deleted file mode 100644 index 5cee2894d1ca06..00000000000000 --- a/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Systems; -using Robust.Shared.Prototypes; - -namespace Content.Server._EinsteinEngine.Traits.Assorted; - -/// -/// When applied to a not-yet-spawned player entity, removes from the lists of their languages -/// and gives them a translator instead. -/// -[RegisterComponent] -public sealed partial class ForeignerTraitComponent : Component -{ - /// - /// The "base" language that is to be removed and substituted with a translator. - /// By default, equals to the fallback language, which is GalacticCommon. - /// - [DataField] - public ProtoId BaseLanguage = SharedLanguageSystem.FallbackLanguagePrototype; - - /// - /// Whether this trait prevents the entity from understanding the base language. - /// - public bool CantUnderstand = true; - - /// - /// Whether this trait prevents the entity from speaking the base language. - /// - public bool CantSpeak = true; - - /// - /// The base translator prototype to use when creating a translator for the entity. - /// - [DataField(required: true)] - public EntProtoId BaseTranslator = default!; - -} diff --git a/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitSystem.cs b/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitSystem.cs deleted file mode 100644 index aea95a1fc58203..00000000000000 --- a/Content.Server/_EinsteinEngine/Traits/Assorted/ForeingerTraitSystem.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Linq; -using Content.Server._EinsteinEngine.Language; -using Content.Server.Hands.Systems; -using Content.Server.Storage.EntitySystems; -using Content.Shared._EinsteinEngine.Language; -using Content.Shared._EinsteinEngine.Language.Components; -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Content.Shared.Clothing.Components; -using Content.Shared.Inventory; -using Content.Shared.Storage; -using Robust.Shared.Prototypes; - -namespace Content.Server._EinsteinEngine.Traits.Assorted; - - -public sealed partial class ForeignerTraitSystem : EntitySystem -{ - [Dependency] private readonly EntityManager _entMan = default!; - [Dependency] private readonly HandsSystem _hands = default!; - [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly LanguageSystem _languages = default!; - [Dependency] private readonly StorageSystem _storage = default!; - - public override void Initialize() - { - SubscribeLocalEvent(OnSpawn); // TraitSystem adds it after PlayerSpawnCompleteEvent so it's fine - } - - private void OnSpawn(Entity entity, ref ComponentInit args) - { - if (entity.Comp.CantUnderstand && !entity.Comp.CantSpeak) - Log.Warning($"Allowing entity {entity.Owner} to speak a language but not understand it leads to undefined behavior."); - - if (!TryComp(entity, out var knowledge)) - { - Log.Warning($"Entity {entity.Owner} does not have a LanguageKnowledge but has a ForeignerTrait!"); - return; - } - - var alternateLanguage = knowledge.SpokenLanguages.Find(it => it != entity.Comp.BaseLanguage); - if (alternateLanguage == default) - { - Log.Warning($"Entity {entity.Owner} does not have an alternative language to choose from (must have at least one non-GC for ForeignerTrait)!"); - return; - } - - if (TryGiveTranslator(entity.Owner, entity.Comp.BaseTranslator, entity.Comp.BaseLanguage, alternateLanguage, out var translator)) - { - _languages.RemoveLanguage(entity.Owner, entity.Comp.BaseLanguage, entity.Comp.CantSpeak, entity.Comp.CantUnderstand); - } - } - - /// - /// Tries to create and give the entity a translator to translator that translates speech between the two specified languages. - /// - public bool TryGiveTranslator( - EntityUid uid, - string baseTranslatorPrototype, - ProtoId translatorLanguage, - ProtoId entityLanguage, - out EntityUid result) - { - result = EntityUid.Invalid; - if (translatorLanguage == entityLanguage) - return false; - - var translator = _entMan.SpawnNextToOrDrop(baseTranslatorPrototype, uid); - result = translator; - - if (!TryComp(translator, out var handheld)) - { - handheld = AddComp(translator); - handheld.ToggleOnInteract = true; - handheld.SetLanguageOnInteract = true; - } - - // Allows to speak the specified language and requires entities language. - handheld.SpokenLanguages = [translatorLanguage]; - handheld.UnderstoodLanguages = [translatorLanguage]; - handheld.RequiredLanguages = [entityLanguage]; - - // Try to put it in entities hand - if (_hands.TryPickupAnyHand(uid, translator, false, false, false)) - return true; - - // Try to find a valid clothing slot on the entity and equip the translator there - if (TryComp(translator, out var clothing) - && clothing.Slots != SlotFlags.NONE - && _inventory.TryGetSlots(uid, out var slots) - && slots.Any(it => _inventory.TryEquip(uid, translator, it.Name, true, false))) - return true; - - // Try to put the translator into entities bag, if it has one - if (_inventory.TryGetSlotEntity(uid, "back", out var bag) - && TryComp(bag, out var storage) - && _storage.Insert(bag.Value, translator, out _, null, storage, false, false)) - return true; - - // If all of the above has failed, just drop it at the same location as the entity - // This should ideally never happen, but who knows. - Transform(translator).Coordinates = Transform(uid).Coordinates; - - return true; - } -} diff --git a/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModiferSystem.cs b/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModiferSystem.cs deleted file mode 100644 index 88151808d6b5e5..00000000000000 --- a/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModiferSystem.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Content.Server._EinsteinEngine.Language; - -namespace Content.Server._EinsteinEngine.Traits.Assorted; - -public sealed class LanguageKnowledgeModifierSystem : EntitySystem -{ - [Dependency] private readonly LanguageSystem _languages = default!; - - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(OnStartup); - } - - private void OnStartup(Entity entity, ref ComponentInit args) - { - if (!TryComp(entity, out var knowledge)) - { - Log.Warning($"Entity {entity.Owner} does not have a LanguageKnowledge but has a LanguageKnowledgeModifier!"); - return; - } - - foreach (var spokenLanguage in entity.Comp.NewSpokenLanguages) - { - _languages.AddLanguage(entity, spokenLanguage, true, false); - } - - foreach (var understoodLanguage in entity.Comp.NewUnderstoodLanguages) - { - _languages.AddLanguage(entity, understoodLanguage, false, true); - } - } -} diff --git a/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModifierComponent.cs b/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModifierComponent.cs deleted file mode 100644 index 12c3a125a65567..00000000000000 --- a/Content.Server/_EinsteinEngine/Traits/Assorted/LanguageKnowledgeModifierComponent.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Content.Server._EinsteinEngine.Traits.Assorted; - -/// -/// Used for traits that modify entities' language knowledge. -/// -[RegisterComponent] -public sealed partial class LanguageKnowledgeModifierComponent : Component -{ - /// - /// List of languages this entity will learn to speak. - /// - [DataField("speaks")] - public List NewSpokenLanguages = new(); - - /// - /// List of languages this entity will learn to understand. - /// - [DataField("understands")] - public List NewUnderstoodLanguages = new(); -} diff --git a/Content.Server/_Goobstation/Heretic/Abilities/HereticAbilitySystem.cs b/Content.Server/_Goobstation/Heretic/Abilities/HereticAbilitySystem.cs index 1b7da2b175a3d6..804a5a4228ad78 100644 --- a/Content.Server/_Goobstation/Heretic/Abilities/HereticAbilitySystem.cs +++ b/Content.Server/_Goobstation/Heretic/Abilities/HereticAbilitySystem.cs @@ -28,7 +28,6 @@ using Content.Shared.StatusEffect; using Content.Shared.Throwing; using Content.Server.Station.Systems; -using Content.Shared.Chat; using Content.Shared.Localizations; using Robust.Shared.Audio; using Content.Shared.Mobs.Components; @@ -128,7 +127,7 @@ private bool TryUseAbility(EntityUid ent, BaseActionEvent args) // shout the spell out if (!string.IsNullOrWhiteSpace(actionComp.MessageLoc)) - _chat.TrySendInGameICMessage(ent, Loc.GetString(actionComp.MessageLoc!), SharedChatSystem.InGameICChatType.Speak, false); + _chat.TrySendInGameICMessage(ent, Loc.GetString(actionComp.MessageLoc!), InGameICChatType.Speak, false); return true; } diff --git a/Content.Server/_Goobstation/Heretic/EntitySystems/MansusGraspSystem.cs b/Content.Server/_Goobstation/Heretic/EntitySystems/MansusGraspSystem.cs index 809254a97f7673..30eea400654822 100644 --- a/Content.Server/_Goobstation/Heretic/EntitySystems/MansusGraspSystem.cs +++ b/Content.Server/_Goobstation/Heretic/EntitySystems/MansusGraspSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Speech.EntitySystems; using Content.Server.Temperature.Components; using Content.Server.Temperature.Systems; -using Content.Shared.Chat; using Content.Shared.Damage; using Content.Shared.Damage.Systems; using Content.Shared.DoAfter; @@ -121,7 +120,7 @@ private void OnAfterInteract(Entity ent, ref AfterInteract if (HasComp(target)) { - _chat.TrySendInGameICMessage(args.User, Loc.GetString("heretic-speech-mansusgrasp"), SharedChatSystem.InGameICChatType.Speak, false); + _chat.TrySendInGameICMessage(args.User, Loc.GetString("heretic-speech-mansusgrasp"), InGameICChatType.Speak, false); _audio.PlayPvs(new SoundPathSpecifier("/Audio/Items/welder.ogg"), target); _stun.TryKnockdown(target, TimeSpan.FromSeconds(3f), true); _stamina.TakeStaminaDamage(target, 65f); diff --git a/Content.Server/_Impstation/Chat/Commands/GBSay.cs b/Content.Server/_Impstation/Chat/Commands/GBSay.cs index 36bc11e1bc293a..303f5848f4e128 100644 --- a/Content.Server/_Impstation/Chat/Commands/GBSay.cs +++ b/Content.Server/_Impstation/Chat/Commands/GBSay.cs @@ -1,7 +1,6 @@ using Content.Server.Chat.Systems; using Content.Shared.Administration; using Content.Shared._Impstation.Ghost; -using Content.Shared.Chat; using Robust.Shared.Console; namespace Content.Server._Impstation.Commands @@ -39,7 +38,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) return; var chat = _e.System(); - chat.TrySendInGameOOCMessage(entity, message, SharedChatSystem.InGameOOCChatType.Dead, false, shell, player); + chat.TrySendInGameOOCMessage(entity, message, InGameOOCChatType.Dead, false, shell, player); } } } diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index f7e783f9781b11..e5f3d469974789 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -4,7 +4,6 @@ using Content.Shared.Radio; using Content.Shared.Speech; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Chat; @@ -290,44 +289,4 @@ public static string GetStringInsideTag(ChatMessage message, string tag) tagStart += tag.Length + 2; return rawmsg.Substring(tagStart, tagEnd - tagStart); } - - - /// - /// InGame IC chat is for chat that is specifically ingame (not lobby) but is also in character, i.e. speaking. - /// -// ReSharper disable once InconsistentNaming - [Serializable, NetSerializable] - public enum InGameICChatType : byte - { - Speak, - Emote, - Whisper, - Telepathic - } - - /// - /// InGame OOC chat is for chat that is specifically ingame (not lobby) but is OOC, like deadchat or LOOC. - /// - [Serializable, NetSerializable] - public enum InGameOOCChatType : byte - { - Looc, - Dead - } - - /// - /// Controls transmission of chat. - /// - [Serializable, NetSerializable] - public enum ChatTransmitRange : byte - { - /// Acts normal, ghosts can hear across the map, etc. - Normal, - /// Normal but ghosts are still range-limited. - GhostRangeLimit, - /// Hidden from the chat window. - HideChat, - /// Ghosts can't hear or see it at all. Regular players can if in-range. - NoGhosts - } } diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index c07e11e38a3e52..863d9da970f739 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -25,7 +25,6 @@ public static class ContentKeyFunctions public static readonly BoundKeyFunction CycleChatChannelBackward = "CycleChatChannelBackward"; public static readonly BoundKeyFunction EscapeContext = "EscapeContext"; public static readonly BoundKeyFunction OpenCharacterMenu = "OpenCharacterMenu"; - public static readonly BoundKeyFunction OpenLanguageMenu = "OpenLanguageMenu"; public static readonly BoundKeyFunction OpenEmotesMenu = "OpenEmotesMenu"; public static readonly BoundKeyFunction OpenCraftingMenu = "OpenCraftingMenu"; public static readonly BoundKeyFunction OpenGuidebook = "OpenGuidebook"; diff --git a/Content.Shared/_EinsteinEngine/Language/Components/LanguageSpeakerComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/LanguageSpeakerComponent.cs deleted file mode 100644 index d08a65083c85a5..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/LanguageSpeakerComponent.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; - -namespace Content.Shared._EinsteinEngine.Language.Components; - -/// -/// Stores the current state of the languages the entity can speak and understand. -/// -/// -/// All fields of this component are populated during a DetermineEntityLanguagesEvent. -/// They are not to be modified externally. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class LanguageSpeakerComponent : Component -{ - public override bool SendOnlyToOwner => true; - - /// - /// The current language the entity uses when speaking. - /// Other listeners will hear the entity speak in this language. - /// - [DataField] - public string CurrentLanguage = ""; // The language system will override it on mapinit - - /// - /// List of languages this entity can speak at the current moment. - /// - [DataField] - public List> SpokenLanguages = []; - - /// - /// List of languages this entity can understand at the current moment. - /// - [DataField] - public List> UnderstoodLanguages = []; - - [Serializable, NetSerializable] - public sealed class State : ComponentState - { - public string CurrentLanguage = default!; - public List> SpokenLanguages = default!; - public List> UnderstoodLanguages = default!; - } -} diff --git a/Content.Shared/_EinsteinEngine/Language/Components/TranslatorImplantComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/TranslatorImplantComponent.cs deleted file mode 100644 index 0343c77c751afe..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/TranslatorImplantComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; - -namespace Content.Shared._EinsteinEngine.Language.Components; - -/// -/// An implant that allows the implantee to speak and understand other languages. -/// -[RegisterComponent] -public sealed partial class TranslatorImplantComponent : BaseTranslatorComponent -{ - /// - /// Whether the implantee knows the languages necessary to speak using this implant. - /// - public bool SpokenRequirementSatisfied = false; - - /// - /// Whether the implantee knows the languages necessary to understand translations of this implant. - /// - public bool UnderstoodRequirementSatisfied = false; -} diff --git a/Content.Shared/_EinsteinEngine/Language/Components/Translators/BaseTranslatorComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/Translators/BaseTranslatorComponent.cs deleted file mode 100644 index 7702bf126101bc..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/Translators/BaseTranslatorComponent.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared._EinsteinEngine.Language.Components.Translators; - -public abstract partial class BaseTranslatorComponent : Component -{ - /// - /// The list of additional languages this translator allows the wielder to speak. - /// - [DataField("spoken")] - public List> SpokenLanguages = new(); - - /// - /// The list of additional languages this translator allows the wielder to understand. - /// - [DataField("understood")] - 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")] - 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("requiresAll")] - public bool RequiresAllLanguages = false; - - [DataField("enabled")] - public bool Enabled = true; -} diff --git a/Content.Shared/_EinsteinEngine/Language/Components/Translators/HandheldTranslatorComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/Translators/HandheldTranslatorComponent.cs deleted file mode 100644 index f7b82e5a62ef39..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/Translators/HandheldTranslatorComponent.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Content.Shared._EinsteinEngine.Language.Components.Translators; - -/// -/// 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] - public bool ToggleOnInteract = true; - - /// - /// If true, when this translator is turned on, the entities' current spoken language will be set - /// to the first new language added by this translator. - /// - /// - /// This should generally be used for translators that translate speech between two languages. - /// - [DataField] - public bool SetLanguageOnInteract = true; -} diff --git a/Content.Shared/_EinsteinEngine/Language/Components/Translators/HoldsTranslatorComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/Translators/HoldsTranslatorComponent.cs deleted file mode 100644 index 3be76ede6b5b2b..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/Translators/HoldsTranslatorComponent.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Content.Shared._EinsteinEngine.Language.Components.Translators; - -/// -/// Applied internally to the holder of [HandheldTranslatorComponent]. -/// Do not use directly. Use [HandheldTranslatorComponent] instead. -/// -[RegisterComponent] -public sealed partial class HoldsTranslatorComponent : Component -{ - [NonSerialized] - public HashSet> Translators = new(); - -} diff --git a/Content.Shared/_EinsteinEngine/Language/Components/Translators/IntrinsicTranslatorComponent.cs b/Content.Shared/_EinsteinEngine/Language/Components/Translators/IntrinsicTranslatorComponent.cs deleted file mode 100644 index e51a91d4054a95..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Components/Translators/IntrinsicTranslatorComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Content.Shared._EinsteinEngine.Language.Components.Translators; - -/// -/// 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 : Translators.BaseTranslatorComponent -{ -} diff --git a/Content.Shared/_EinsteinEngine/Language/Events/DetermineEntityLanguagesEvent.cs b/Content.Shared/_EinsteinEngine/Language/Events/DetermineEntityLanguagesEvent.cs deleted file mode 100644 index d146cae993f3f7..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Events/DetermineEntityLanguagesEvent.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared._EinsteinEngine.Language.Events; - -/// -/// Raised in order to determine the list of languages the entity can speak and understand at the given moment. -/// Typically raised on an entity after a language agent (e.g. a translator) has been added to or removed from them. -/// -[ByRefEvent] -public record struct DetermineEntityLanguagesEvent -{ - /// - /// The list of all languages the entity may speak. - /// By default, contains the languages this entity speaks intrinsically. - /// - public HashSet> SpokenLanguages = new(); - - /// - /// The list of all languages the entity may understand. - /// By default, contains the languages this entity understands intrinsically. - /// - public HashSet> UnderstoodLanguages = new(); - - public DetermineEntityLanguagesEvent() {} -} diff --git a/Content.Shared/_EinsteinEngine/Language/Events/LanguagesSetMessage.cs b/Content.Shared/_EinsteinEngine/Language/Events/LanguagesSetMessage.cs deleted file mode 100644 index a7067bfe84d2f1..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Events/LanguagesSetMessage.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared._EinsteinEngine.Language.Events -{ - /// - /// Sent from the client to the server when it needs to want to set his currentLangauge. - /// Yeah im using this instead of ExecuteCommand... Better right? - /// - [Serializable, NetSerializable] - public sealed class LanguagesSetMessage(string currentLanguage) : EntityEventArgs - { - public string CurrentLanguage = currentLanguage; - } -} diff --git a/Content.Shared/_EinsteinEngine/Language/Events/LanguagesUpdateEvent.cs b/Content.Shared/_EinsteinEngine/Language/Events/LanguagesUpdateEvent.cs deleted file mode 100644 index c1b6b55d75bb8a..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Events/LanguagesUpdateEvent.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Content.Shared._EinsteinEngine.Language.Events; - -/// -/// Raised on an entity when its list of languages changes. -/// -/// -/// This is raised both on the server and on the client. -/// The client raises it broadcast after receiving a new language comp state from the server. -/// -public sealed class LanguagesUpdateEvent : EntityEventArgs -{ -} diff --git a/Content.Shared/_EinsteinEngine/Language/LanguagePrototype.cs b/Content.Shared/_EinsteinEngine/Language/LanguagePrototype.cs deleted file mode 100644 index 4fb20961e1bd4d..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/LanguagePrototype.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Content.Shared.Chat; -using Robust.Shared.Prototypes; - -namespace Content.Shared._EinsteinEngine.Language; - -[Prototype("language")] -public sealed partial class LanguagePrototype : IPrototype -{ - [IdDataField] - public string ID { get; private set; } = default!; - - /// - /// Obfuscation method used by this language. By default, uses - /// - [DataField("obfuscation")] - public ObfuscationMethod Obfuscation = ObfuscationMethod.Default; - - /// - /// Speech overrides used for messages sent in this language. - /// - [DataField("speech")] - public SpeechOverrideInfo SpeechOverride = new(); - - #region utility - /// - /// The in-world name of this language, localized. - /// - public string Name => Loc.GetString($"language-{ID}-name"); - - /// - /// The in-world description of this language, localized. - /// - public string Description => Loc.GetString($"language-{ID}-description"); - #endregion utility -} - -[DataDefinition] -public sealed partial class SpeechOverrideInfo -{ - /// - /// Color which text in this language will be blended with. - /// Alpha blending is used, which means the alpha component of the color controls the intensity of this color. - /// - [DataField] - public Color? Color = null; - - [DataField] - public string? FontId; - - [DataField] - public int? FontSize; - - [DataField] - public bool AllowRadio = true; - - /// - /// If false, the entity can use this language even when it's unable to speak (i.e. muffled or muted), - /// and accents are not applied to messages in this language. - /// - [DataField] - public bool RequireSpeech = true; - - /// - /// If not null, all messages in this language will be forced to be spoken in this chat type. - /// - [DataField] - public SharedChatSystem.InGameICChatType? ChatTypeOverride; - - /// - /// Speech verb overrides. If not provided, the default ones for the entity are used. - /// - [DataField] - public List? SpeechVerbOverrides; - - /// - /// Overrides for different kinds chat message wraps. If not provided, the default ones are used. - /// - /// - /// Currently, only local chat and whispers support this. Radio and emotes are unaffected. - /// This is horrible. - /// - [DataField] - public Dictionary MessageWrapOverrides = new(); -} diff --git a/Content.Shared/_EinsteinEngine/Language/ObfuscationMethods.cs b/Content.Shared/_EinsteinEngine/Language/ObfuscationMethods.cs deleted file mode 100644 index 509d19a70fc75f..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/ObfuscationMethods.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Text; -using Content.Shared._EinsteinEngine.Language.Systems; - -namespace Content.Shared._EinsteinEngine.Language; - -[ImplicitDataDefinitionForInheritors] -public abstract partial class ObfuscationMethod -{ - /// - /// The fallback obfuscation method, replaces the message with the string "<?>". - /// - public static readonly ObfuscationMethod Default = new ReplacementObfuscation - { - Replacement = new List { "" } - }; - - /// - /// Obfuscates the provided message and writes the result into the provided StringBuilder. - /// Implementations should use the context's pseudo-random number generator and provide stable obfuscations. - /// - internal abstract void Obfuscate(StringBuilder builder, string message, SharedLanguageSystem context); - - /// - /// Obfuscates the provided message. This method should only be used for debugging purposes. - /// For all other purposes, use instead. - /// - public string Obfuscate(string message) - { - var builder = new StringBuilder(); - Obfuscate(builder, message, IoCManager.Resolve().GetEntitySystem()); - return builder.ToString(); - } -} - -/// -/// The most primitive method of obfuscation - replaces the entire message with one random replacement phrase. -/// Similar to ReplacementAccent. Base for all replacement-based obfuscation methods. -/// -public partial class ReplacementObfuscation : ObfuscationMethod -{ - /// - /// A list of replacement phrases used in the obfuscation process. - /// - [DataField(required: true)] - public List Replacement = []; - - internal override void Obfuscate(StringBuilder builder, string message, SharedLanguageSystem context) - { - var idx = context.PseudoRandomNumber(message.GetHashCode(), 0, Replacement.Count - 1); - builder.Append(Replacement[idx]); - } -} - -/// -/// Obfuscates the provided message by replacing each word with a random number of syllables in the range (min, max), -/// preserving the original punctuation to a resonable extent. -/// -/// -/// The words are obfuscated in a stable manner, such that every particular word will be obfuscated the same way throughout one round. -/// This means that particular words can be memorized within a round, but not across rounds. -/// -public sealed partial class SyllableObfuscation : ReplacementObfuscation -{ - [DataField] - public int MinSyllables = 1; - - [DataField] - public int MaxSyllables = 4; - - internal override void Obfuscate(StringBuilder builder, string message, SharedLanguageSystem context) - { - const char eof = (char) 0; // Special character to mark the end of the message in the code below - - var wordBeginIndex = 0; - var hashCode = 0; - - for (var i = 0; i <= message.Length; i++) - { - var ch = i < message.Length ? char.ToLower(message[i]) : eof; - var isWordEnd = char.IsWhiteSpace(ch) || IsPunctuation(ch) || ch == eof; - - // If this is a normal char, add it to the hash sum - if (!isWordEnd) - hashCode = hashCode * 31 + ch; - - // If a word ends before this character, construct a new word and append it to the new message. - if (isWordEnd) - { - var wordLength = i - wordBeginIndex; - if (wordLength > 0) - { - var newWordLength = context.PseudoRandomNumber(hashCode, MinSyllables, MaxSyllables); - - for (var j = 0; j < newWordLength; j++) - { - var index = context.PseudoRandomNumber(hashCode + j, 0, Replacement.Count - 1); - builder.Append(Replacement[index]); - } - } - - hashCode = 0; - wordBeginIndex = i + 1; - } - - // If this message concludes a word (i.e. is a whitespace or a punctuation mark), append it to the message - if (isWordEnd && ch != eof) - builder.Append(ch); - } - } - - private static bool IsPunctuation(char ch) - { - return ch is '.' or '!' or '?' or ',' or ':'; - } -} - -/// -/// Obfuscates each sentence in the message by concatenating a number of obfuscation phrases. -/// The number of phrases in the obfuscated message is proportional to the length of the original message. -/// -public sealed partial class PhraseObfuscation : ReplacementObfuscation -{ - [DataField] - public int MinPhrases = 1; - - [DataField] - public int MaxPhrases = 4; - - /// - /// A string used to separate individual phrases within one sentence. Default is a space. - /// - [DataField] - public string Separator = " "; - - /// - /// A power to which the number of characters in the original message is raised to determine the number of phrases in the result. - /// Default is 1/3, i.e. the cubic root of the original number. - /// - /// - /// Using the default proportion, you will need at least 27 characters for 2 phrases, at least 64 for 3, at least 125 for 4, etc. - /// Increasing the proportion to 1/4 will result in the numbers changing to 81, 256, 625, etc. - /// - [DataField] - public float Proportion = 1f / 3; - - internal override void Obfuscate(StringBuilder builder, string message, SharedLanguageSystem context) - { - var sentenceBeginIndex = 0; - var hashCode = 0; - - for (var i = 0; i < message.Length; i++) - { - var ch = char.ToLower(message[i]); - if (!IsPunctuation(ch) && i != message.Length - 1) - { - hashCode = hashCode * 31 + ch; - continue; - } - - var length = i - sentenceBeginIndex; - if (length > 0) - { - var newLength = (int) Math.Clamp(Math.Pow(length, Proportion) - 1, MinPhrases, MaxPhrases); - - for (var j = 0; j < newLength; j++) - { - var phraseIdx = context.PseudoRandomNumber(hashCode + j, 0, Replacement.Count - 1); - var phrase = Replacement[phraseIdx]; - builder.Append(phrase); - builder.Append(Separator); - } - } - sentenceBeginIndex = i + 1; - - if (IsPunctuation(ch)) - builder.Append(ch).Append(' '); // TODO: this will turn '...' into '. . . ' - } - } - - private static bool IsPunctuation(char ch) - { - return ch is '.' or '!' or '?'; // Doesn't include mid-sentence punctuation like the comma - } -} diff --git a/Content.Shared/_EinsteinEngine/Language/Systems/SharedLanguageSystem.cs b/Content.Shared/_EinsteinEngine/Language/Systems/SharedLanguageSystem.cs deleted file mode 100644 index 614549388af6e9..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Systems/SharedLanguageSystem.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Text; -using Content.Shared.GameTicking; -using Robust.Shared.Prototypes; - -namespace Content.Shared._EinsteinEngine.Language.Systems; - -public abstract class SharedLanguageSystem : EntitySystem -{ - /// - /// The language used as a fallback in cases where an entity suddenly becomes a language speaker (e.g. the usage of make-sentient) - /// - [ValidatePrototypeId] - public static readonly string FallbackLanguagePrototype = "GalacticCommon"; - - /// - /// The language whose speakers are assumed to understand and speak every language. Should never be added directly. - /// - [ValidatePrototypeId] - public static readonly string UniversalPrototype = "Universal"; - - /// - /// A cached instance of - /// - public static LanguagePrototype Universal { get; private set; } = default!; - - [Dependency] protected readonly IPrototypeManager _prototype = default!; - [Dependency] protected readonly SharedGameTicker _ticker = default!; - - public override void Initialize() - { - Universal = _prototype.Index("Universal"); - } - - public LanguagePrototype? GetLanguagePrototype(ProtoId id) - { - _prototype.TryIndex(id, out var proto); - return proto; - } - - /// - /// Obfuscate a message using the given language. - /// - public string ObfuscateSpeech(string message, LanguagePrototype language) - { - var builder = new StringBuilder(); - language.Obfuscation.Obfuscate(builder, message, this); - - return builder.ToString(); - } - - /// - /// Generates a stable pseudo-random number in the range (min, max) (inclusively) for the given seed. - /// One seed always corresponds to one number, however the resulting number also depends on the current round number. - /// This method is meant to be used in to provide stable obfuscation. - /// - internal int PseudoRandomNumber(int seed, int min, int max) - { - // Using RobustRandom or System.Random here is a bad idea because this method can get called hundreds of times per message. - // Each call would require us to allocate a new instance of random, which would lead to lots of unnecessary calculations. - // Instead, we use a simple but effective algorithm derived from the C language. - // It does not produce a truly random number, but for the purpose of obfuscating messages in an RP-based game it's more than alright. - seed = seed ^ (_ticker.RoundId * 127); - var random = seed * 1103515245 + 12345; - return min + Math.Abs(random) % (max - min + 1); - } -} diff --git a/Content.Shared/_EinsteinEngine/Language/Systems/SharedTranslatorSystem.cs b/Content.Shared/_EinsteinEngine/Language/Systems/SharedTranslatorSystem.cs deleted file mode 100644 index b9464c37849370..00000000000000 --- a/Content.Shared/_EinsteinEngine/Language/Systems/SharedTranslatorSystem.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Linq; -using Content.Shared._EinsteinEngine.Language.Components.Translators; -using Content.Shared.Examine; -using Content.Shared.Toggleable; - -namespace Content.Shared._EinsteinEngine.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 understoodLanguageNames = component.UnderstoodLanguages - .Select(it => Loc.GetString($"language-{it}-name")); - var spokenLanguageNames = component.SpokenLanguages - .Select(it => Loc.GetString($"language-{it}-name")); - var requiredLanguageNames = component.RequiredLanguages - .Select(it => Loc.GetString($"language-{it}-name")); - - args.PushMarkup(Loc.GetString("translator-examined-langs-understood", ("languages", string.Join(", ", understoodLanguageNames)))); - args.PushMarkup(Loc.GetString("translator-examined-langs-spoken", ("languages", string.Join(", ", spokenLanguageNames)))); - - args.PushMarkup(Loc.GetString(component.RequiresAllLanguages ? "translator-examined-requires-all" : "translator-examined-requires-any", - ("languages", string.Join(", ", requiredLanguageNames)))); - - args.PushMarkup(Loc.GetString(component.Enabled ? "translator-examined-enabled" : "translator-examined-disabled")); - } - - 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/Fonts/Copperplate.otf b/Resources/Fonts/Copperplate.otf deleted file mode 100644 index 40d45aa46b66ca82debde94961af8d35d803bdb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34436 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 diff --git a/Resources/Fonts/Noganas.ttf b/Resources/Fonts/Noganas.ttf deleted file mode 100644 index afa0c82f03e112f3334dcc457d4323715257ffea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 84396 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