diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs index c42e7cd0e0..774eae3f02 100644 --- a/Content.Client/Ghost/GhostSystem.cs +++ b/Content.Client/Ghost/GhostSystem.cs @@ -181,5 +181,11 @@ public void ToggleGhostVisibility() { GhostVisibility = !GhostVisibility; } + + public void ReturnToRound() + { + var msg = new GhostReturnToRoundRequest(); + RaiseNetworkEvent(msg); + } } } diff --git a/Content.Client/Language/LanguageMenuWindow.xaml.cs b/Content.Client/Language/LanguageMenuWindow.xaml.cs index ed6ec6b3e2..b72d79d6de 100644 --- a/Content.Client/Language/LanguageMenuWindow.xaml.cs +++ b/Content.Client/Language/LanguageMenuWindow.xaml.cs @@ -1,48 +1,51 @@ using Content.Client.Language.Systems; +using Content.Shared.Language; +using Content.Shared.Language.Components; using Content.Shared.Language.Events; 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.Language; [GenerateTypedNameReferences] -public sealed partial class LanguageMenuWindow : DefaultWindow, IEntityEventSubscriber +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 += OnUpdateState; + _clientLanguageSystem.OnLanguagesChanged += UpdateState; } protected override void Dispose(bool disposing) { base.Dispose(disposing); - _clientLanguageSystem.OnLanguagesChanged -= OnUpdateState; + + if (disposing) + _clientLanguageSystem.OnLanguagesChanged -= UpdateState; } protected override void Opened() { - // Refresh the window when it gets opened. - // This actually causes two refreshes: one immediately, and one after the server sends a state message. - UpdateState(_clientLanguageSystem.CurrentLanguage, _clientLanguageSystem.SpokenLanguages); - _clientLanguageSystem.RequestStateUpdate(); + UpdateState(); } - - private void OnUpdateState(object? sender, LanguagesUpdatedMessage args) + private void UpdateState() { - UpdateState(args.CurrentLanguage, args.Spoken); + var languageSpeaker = _clientLanguageSystem.GetLocalSpeaker(); + if (languageSpeaker == null) + return; + + UpdateState(languageSpeaker.CurrentLanguage, languageSpeaker.SpokenLanguages); } - public void UpdateState(string currentLanguage, List 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)); @@ -58,15 +61,15 @@ public void UpdateState(string currentLanguage, List spokenLanguages) // Disable the button for the currently chosen language foreach (var entry in _entries) { - if (entry.button != null) - entry.button.Disabled = entry.language == currentLanguage; + if (entry.Button != null) + entry.Button.Disabled = entry.Language == currentLanguage; } } - private void AddLanguageEntry(string language) + private void AddLanguageEntry(ProtoId language) { var proto = _clientLanguageSystem.GetLanguagePrototype(language); - var state = new EntryState { language = language }; + var state = new EntryState { Language = language }; var container = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical }; @@ -87,7 +90,7 @@ private void AddLanguageEntry(string language) var button = new Button { Text = "Choose" }; button.OnPressed += _ => OnLanguageChosen(language); - state.button = button; + state.Button = button; header.AddChild(name); header.AddChild(button); @@ -125,21 +128,18 @@ private void AddLanguageEntry(string language) _entries.Add(state); } - - private void OnLanguageChosen(string id) + private void OnLanguageChosen(ProtoId id) { - var proto = _clientLanguageSystem.GetLanguagePrototype(id); - if (proto == null) - return; + _clientLanguageSystem.RequestSetLanguage(id); - _clientLanguageSystem.RequestSetLanguage(proto); - UpdateState(id, _clientLanguageSystem.SpokenLanguages); + // Predict the change + if (_clientLanguageSystem.GetLocalSpeaker()?.SpokenLanguages is {} languages) + UpdateState(id, languages); } - private struct EntryState { - public string language; - public Button? button; + public ProtoId Language; + public Button? Button; } } diff --git a/Content.Client/Language/Systems/LanguageSystem.cs b/Content.Client/Language/Systems/LanguageSystem.cs index cb6bb60512..efdf1f7d7d 100644 --- a/Content.Client/Language/Systems/LanguageSystem.cs +++ b/Content.Client/Language/Systems/LanguageSystem.cs @@ -1,81 +1,61 @@ using Content.Shared.Language; +using Content.Shared.Language.Components; using Content.Shared.Language.Events; using Content.Shared.Language.Systems; -using Robust.Client; +using Robust.Client.Player; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Client.Language.Systems; -/// -/// Client-side language system. -/// -/// -/// Unlike the server, the client is not aware of other entities' languages; it's only notified about the entity that it posesses. -/// Due to that, this system stores such information in a static manner. -/// public sealed class LanguageSystem : SharedLanguageSystem { - [Dependency] private readonly IBaseClient _client = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; /// - /// The current language of the entity currently possessed by the player. + /// Invoked when the languages of the local player entity change, for use in UI. /// - public string CurrentLanguage { get; private set; } = default!; - /// - /// The list of languages the currently possessed entity can speak. - /// - public List SpokenLanguages { get; private set; } = new(); - /// - /// The list of languages the currently possessed entity can understand. - /// - public List UnderstoodLanguages { get; private set; } = new(); - - public event EventHandler? OnLanguagesChanged; + public event Action? OnLanguagesChanged; public override void Initialize() { - base.Initialize(); - - SubscribeNetworkEvent(OnLanguagesUpdated); - _client.RunLevelChanged += OnRunLevelChanged; + _playerManager.LocalPlayerAttached += NotifyUpdate; + SubscribeLocalEvent(OnHandleState); } - private void OnLanguagesUpdated(LanguagesUpdatedMessage message) + private void OnHandleState(Entity ent, ref ComponentHandleState args) { - // TODO this entire thing is horrible. If someone is willing to refactor this, LanguageSpeakerComponent should become shared with SendOnlyToOwner = true - // That way, this system will be able to use the existing networking infrastructure instead of relying on this makeshift... whatever this is. - CurrentLanguage = message.CurrentLanguage; - SpokenLanguages = message.Spoken; - UnderstoodLanguages = message.Understood; + if (args.Current is not LanguageSpeakerComponent.State state) + return; - OnLanguagesChanged?.Invoke(this, message); - } + ent.Comp.CurrentLanguage = state.CurrentLanguage; + ent.Comp.SpokenLanguages = state.SpokenLanguages; + ent.Comp.UnderstoodLanguages = state.UnderstoodLanguages; - private void OnRunLevelChanged(object? sender, RunLevelChangedEventArgs args) - { - // Request an update when entering a game - if (args.NewLevel == ClientRunLevel.InGame) - RequestStateUpdate(); + if (ent.Owner == _playerManager.LocalEntity) + NotifyUpdate(ent); } /// - /// Sends a network request to the server to update this system's state. - /// The server may ignore the said request if the player is not possessing an entity. + /// 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 void RequestStateUpdate() + public LanguageSpeakerComponent? GetLocalSpeaker() { - RaiseNetworkEvent(new RequestLanguagesMessage()); + return CompOrNull(_playerManager.LocalEntity); } - public void RequestSetLanguage(LanguagePrototype language) + public void RequestSetLanguage(ProtoId language) { - if (language.ID == CurrentLanguage) + if (GetLocalSpeaker()?.CurrentLanguage?.Equals(language) == true) return; - RaiseNetworkEvent(new LanguagesSetMessage(language.ID)); + RaiseNetworkEvent(new LanguagesSetMessage(language)); + } - // May cause some minor desync... - // So to reduce the probability of desync, we replicate the change locally too - if (SpokenLanguages.Contains(language.ID)) - CurrentLanguage = language.ID; + private void NotifyUpdate(EntityUid localPlayer) + { + RaiseLocalEvent(localPlayer, new LanguagesUpdateEvent(), broadcast: true); + OnLanguagesChanged?.Invoke(); } } diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml index ea9c63d6cd..777b3a40a5 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml +++ b/Content.Client/Lobby/UI/LobbyGui.xaml @@ -14,7 +14,7 @@ Stretch="KeepAspectCovered" /> - + diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml index e7fecf81f4..3440c16b82 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml @@ -61,6 +61,12 @@ ToolTip="{Loc 'humanoid-profile-editor-guidebook-button-tooltip'}"/> + + +