From 332f54a3aebe669f6e50d26e7b047f0bdc28e0fb Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sun, 12 May 2024 09:18:21 +1000 Subject: [PATCH] Lobby refactor + species loadouts support (#27576) * Vox stuff * Species loadouts and lobby refactor The control flow for lobby is all over the shop so I pulled it all up from the individual controls so now they handle the bare minimum required and LobbyUIController handles the rest. * a * Bulk changes * a * weh * Character import / export * finalise * woops this stuff too * Also datafield exporting * comments * Review --- Content.Client/Entry/EntryPoint.cs | 2 +- Content.Client/IoC/ClientContentIoC.cs | 5 +- .../ClientPreferencesManager.cs | 4 +- .../IClientPreferencesManager.cs | 3 +- Content.Client/Lobby/LobbyState.cs | 106 +- Content.Client/Lobby/LobbyUIController.cs | 319 +++-- .../Lobby/UI/CharacterPickerButton.xaml | 22 + .../Lobby/UI/CharacterPickerButton.xaml.cs | 92 ++ .../UI/CharacterSetupGui.xaml | 4 - .../Lobby/UI/CharacterSetupGui.xaml.cs | 118 ++ .../UI/HighlightedContainer.xaml | 0 .../UI/HighlightedContainer.xaml.cs | 2 +- .../UI/HumanoidProfileEditor.xaml | 76 +- .../UI/HumanoidProfileEditor.xaml.cs | 1106 ++++++++++------- .../UI/Loadouts}/LoadoutContainer.xaml | 0 .../UI/Loadouts}/LoadoutContainer.xaml.cs | 4 +- .../UI/Loadouts}/LoadoutGroupContainer.xaml | 0 .../Loadouts}/LoadoutGroupContainer.xaml.cs | 11 +- .../UI/Loadouts}/LoadoutWindow.xaml | 0 .../UI/Loadouts}/LoadoutWindow.xaml.cs | 21 +- .../UI/LobbyCharacterPreviewPanel.xaml.cs | 22 +- Content.Client/Lobby/UI/LobbyGui.xaml.cs | 9 +- .../Lobby/UI/ObserveWarningWindow.xaml.cs | 25 +- .../Lobby/UI/Roles/RequirementsSelector.xaml | 9 + .../UI/Roles/RequirementsSelector.xaml.cs | 118 ++ .../UI/Roles/TraitPreferenceSelector.xaml | 7 + .../UI/Roles/TraitPreferenceSelector.xaml.cs | 36 + .../Preferences/UI/AntagPreferenceSelector.cs | 41 - .../Preferences/UI/CharacterSetupGui.xaml.cs | 276 ---- .../UI/HumanoidProfileEditor.Random.cs | 23 - .../Preferences/UI/JobPrioritySelector.cs | 46 - .../Preferences/UI/RequirementsSelector.cs | 222 ---- .../Tests/Lobby/CharacterCreationTest.cs | 1 - .../Tests/Preferences/ServerDbSqliteTests.cs | 29 +- Content.Server/Database/ServerDbBase.cs | 5 +- .../Humanoid/HumanoidCharacterAppearance.cs | 86 +- .../Humanoid/HumanoidProfileExport.cs | 19 + .../SharedHumanoidAppearanceSystem.cs | 42 + .../Preferences/HumanoidCharacterProfile.cs | 327 ++--- .../Loadouts/Effects/GroupLoadoutEffect.cs | 4 +- .../Effects/JobRequirementLoadoutEffect.cs | 2 +- .../Loadouts/Effects/LoadoutEffect.cs | 1 + .../Effects/PointsCostLoadoutEffect.cs | 1 + .../Loadouts/Effects/SpeciesLoadoutEffect.cs | 24 +- .../Preferences/Loadouts/Loadout.cs | 5 +- .../Preferences/Loadouts/RoleLoadout.cs | 37 +- .../preferences/ui/character-setup-gui.ftl | 1 - .../ui/humanoid-profile-editor.ftl | 1 + Resources/Prototypes/Species/vox.yml | 2 +- 49 files changed, 1810 insertions(+), 1506 deletions(-) rename Content.Client/{Preferences => Lobby}/ClientPreferencesManager.cs (97%) rename Content.Client/{Preferences => Lobby}/IClientPreferencesManager.cs (92%) create mode 100644 Content.Client/Lobby/UI/CharacterPickerButton.xaml create mode 100644 Content.Client/Lobby/UI/CharacterPickerButton.xaml.cs rename Content.Client/{Preferences => Lobby}/UI/CharacterSetupGui.xaml (91%) create mode 100644 Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs rename Content.Client/{Preferences => Lobby}/UI/HighlightedContainer.xaml (100%) rename Content.Client/{Preferences => Lobby}/UI/HighlightedContainer.xaml.cs (88%) rename Content.Client/{Preferences => Lobby}/UI/HumanoidProfileEditor.xaml (68%) rename Content.Client/{Preferences => Lobby}/UI/HumanoidProfileEditor.xaml.cs (50%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutContainer.xaml (100%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutContainer.xaml.cs (95%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutGroupContainer.xaml (100%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutGroupContainer.xaml.cs (84%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutWindow.xaml (100%) rename Content.Client/{Preferences/UI => Lobby/UI/Loadouts}/LoadoutWindow.xaml.cs (69%) create mode 100644 Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml create mode 100644 Content.Client/Lobby/UI/Roles/RequirementsSelector.xaml.cs create mode 100644 Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml create mode 100644 Content.Client/Lobby/UI/Roles/TraitPreferenceSelector.xaml.cs delete mode 100644 Content.Client/Preferences/UI/AntagPreferenceSelector.cs delete mode 100644 Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs delete mode 100644 Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs delete mode 100644 Content.Client/Preferences/UI/JobPrioritySelector.cs delete mode 100644 Content.Client/Preferences/UI/RequirementsSelector.cs create mode 100644 Content.Shared/Humanoid/HumanoidProfileExport.cs diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index 25490874e9..b28c6a11fb 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -10,10 +10,10 @@ using Content.Client.Info; using Content.Client.Input; using Content.Client.IoC; using Content.Client.Launcher; +using Content.Client.Lobby; using Content.Client.MainMenu; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; -using Content.Client.Preferences; using Content.Client.Radiation.Overlays; using Content.Client.Replay; using Content.Client.Screenshot; diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 65e95b76f0..4703915ae7 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -2,23 +2,20 @@ using Content.Client.Administration.Managers; using Content.Client.Changelog; using Content.Client.Chat.Managers; using Content.Client.Clickable; -using Content.Client.Options; using Content.Client.Eui; using Content.Client.GhostKick; using Content.Client.Info; using Content.Client.Launcher; using Content.Client.Parallax.Managers; using Content.Client.Players.PlayTimeTracking; -using Content.Client.Preferences; using Content.Client.Screenshot; using Content.Client.Fullscreen; using Content.Client.Stylesheets; using Content.Client.Viewport; using Content.Client.Voting; -using Content.Shared.Administration; using Content.Shared.Administration.Logs; -using Content.Shared.Module; using Content.Client.Guidebook; +using Content.Client.Lobby; using Content.Client.Replay; using Content.Shared.Administration.Managers; using Content.Shared.Players.PlayTimeTracking; diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Lobby/ClientPreferencesManager.cs similarity index 97% rename from Content.Client/Preferences/ClientPreferencesManager.cs rename to Content.Client/Lobby/ClientPreferencesManager.cs index 89cee7bf79..3f01e1a8f6 100644 --- a/Content.Client/Preferences/ClientPreferencesManager.cs +++ b/Content.Client/Lobby/ClientPreferencesManager.cs @@ -2,12 +2,10 @@ using System.Linq; using Content.Shared.Preferences; using Robust.Client; using Robust.Client.Player; -using Robust.Shared.Configuration; using Robust.Shared.Network; -using Robust.Shared.Prototypes; using Robust.Shared.Utility; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { /// /// Receives and from the server during the initial diff --git a/Content.Client/Preferences/IClientPreferencesManager.cs b/Content.Client/Lobby/IClientPreferencesManager.cs similarity index 92% rename from Content.Client/Preferences/IClientPreferencesManager.cs rename to Content.Client/Lobby/IClientPreferencesManager.cs index e55d6b600c..45a770b162 100644 --- a/Content.Client/Preferences/IClientPreferencesManager.cs +++ b/Content.Client/Lobby/IClientPreferencesManager.cs @@ -1,7 +1,6 @@ -using System; using Content.Shared.Preferences; -namespace Content.Client.Preferences +namespace Content.Client.Lobby { public interface IClientPreferencesManager { diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index 91730020a4..1aabc4ff38 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -3,8 +3,6 @@ using Content.Client.GameTicking.Managers; using Content.Client.LateJoin; using Content.Client.Lobby.UI; using Content.Client.Message; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; using Content.Client.UserInterface.Systems.Chat; using Content.Client.Voting; using Robust.Client; @@ -12,8 +10,6 @@ using Robust.Client.Console; using Robust.Client.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; -using Robust.Shared.Configuration; -using Robust.Shared.Prototypes; using Robust.Shared.Timing; @@ -25,20 +21,15 @@ namespace Content.Client.Lobby [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; - [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IVoteManager _voteManager = default!; - [Dependency] private readonly IConfigurationManager _configurationManager = default!; - - [ViewVariables] private CharacterSetupGui? _characterSetup; private ClientGameTicker _gameTicker = default!; private ContentAudioSystem _contentAudioSystem = default!; protected override Type? LinkedScreenType { get; } = typeof(LobbyGui); - private LobbyGui? _lobby; + public LobbyGui? Lobby; protected override void Startup() { @@ -47,45 +38,23 @@ namespace Content.Client.Lobby return; } - _lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; + Lobby = (LobbyGui) _userInterfaceManager.ActiveScreen; var chatController = _userInterfaceManager.GetUIController(); _gameTicker = _entityManager.System(); _contentAudioSystem = _entityManager.System(); _contentAudioSystem.LobbySoundtrackChanged += UpdateLobbySoundtrackInfo; - _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, - _prototypeManager, _configurationManager); - LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); - _lobby.CharacterSetupState.AddChild(_characterSetup); chatController.SetMainChat(true); - _voteManager.SetPopupContainer(_lobby.VoteContainer); - - _characterSetup.CloseButton.OnPressed += _ => - { - // Reset sliders etc. - _characterSetup?.UpdateControls(); - - var controller = _userInterfaceManager.GetUIController(); - controller.SetClothes(true); - controller.UpdateProfile(); - _lobby.SwitchState(LobbyGui.LobbyGuiState.Default); - }; - - _characterSetup.SaveButton.OnPressed += _ => - { - _characterSetup.Save(); - _userInterfaceManager.GetUIController().ReloadProfile(); - }; - - LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide); - _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... + _voteManager.SetPopupContainer(Lobby.VoteContainer); + LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide); + Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... UpdateLobbyUi(); - _lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; - _lobby.ReadyButton.OnPressed += OnReadyPressed; - _lobby.ReadyButton.OnToggled += OnReadyToggled; + Lobby.CharacterPreview.CharacterSetupButton.OnPressed += OnSetupPressed; + Lobby.ReadyButton.OnPressed += OnReadyPressed; + Lobby.ReadyButton.OnToggled += OnReadyToggled; _gameTicker.InfoBlobUpdated += UpdateLobbyUi; _gameTicker.LobbyStatusUpdated += LobbyStatusUpdated; @@ -103,20 +72,23 @@ namespace Content.Client.Lobby _voteManager.ClearPopupContainer(); - _lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; - _lobby!.ReadyButton.OnPressed -= OnReadyPressed; - _lobby!.ReadyButton.OnToggled -= OnReadyToggled; + Lobby!.CharacterPreview.CharacterSetupButton.OnPressed -= OnSetupPressed; + Lobby!.ReadyButton.OnPressed -= OnReadyPressed; + Lobby!.ReadyButton.OnToggled -= OnReadyToggled; - _lobby = null; + Lobby = null; + } - _characterSetup?.Dispose(); - _characterSetup = null; + public void SwitchState(LobbyGui.LobbyGuiState state) + { + // Yeah I hate this but LobbyState contains all the badness for now. + Lobby?.SwitchState(state); } private void OnSetupPressed(BaseButton.ButtonEventArgs args) { SetReady(false); - _lobby!.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); + Lobby?.SwitchState(LobbyGui.LobbyGuiState.CharacterSetup); } private void OnReadyPressed(BaseButton.ButtonEventArgs args) @@ -138,13 +110,13 @@ namespace Content.Client.Lobby { if (_gameTicker.IsGameStarted) { - _lobby!.StartTime.Text = string.Empty; + Lobby!.StartTime.Text = string.Empty; var roundTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan); - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-time", ("hours", roundTime.Hours), ("minutes", roundTime.Minutes)); return; } - _lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); + Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started"); string text; if (_gameTicker.Paused) @@ -153,7 +125,7 @@ namespace Content.Client.Lobby } else if (_gameTicker.StartTime < _gameTiming.CurTime) { - _lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-soon"); return; } else @@ -170,7 +142,7 @@ namespace Content.Client.Lobby } } - _lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); + Lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); } private void LobbyStatusUpdated() @@ -181,31 +153,31 @@ namespace Content.Client.Lobby private void LobbyLateJoinStatusUpdated() { - _lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; + Lobby!.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; } private void UpdateLobbyUi() { if (_gameTicker.IsGameStarted) { - _lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); - _lobby!.ReadyButton.ToggleMode = false; - _lobby!.ReadyButton.Pressed = false; - _lobby!.ObserveButton.Disabled = false; + Lobby!.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); + Lobby!.ReadyButton.ToggleMode = false; + Lobby!.ReadyButton.Pressed = false; + Lobby!.ObserveButton.Disabled = false; } else { - _lobby!.StartTime.Text = string.Empty; - _lobby!.ReadyButton.Text = Loc.GetString(_lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); - _lobby!.ReadyButton.ToggleMode = true; - _lobby!.ReadyButton.Disabled = false; - _lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; - _lobby!.ObserveButton.Disabled = true; + Lobby!.StartTime.Text = string.Empty; + Lobby!.ReadyButton.Text = Loc.GetString(Lobby!.ReadyButton.Pressed ? "lobby-state-player-status-ready": "lobby-state-player-status-not-ready"); + Lobby!.ReadyButton.ToggleMode = true; + Lobby!.ReadyButton.Disabled = false; + Lobby!.ReadyButton.Pressed = _gameTicker.AreWeReady; + Lobby!.ObserveButton.Disabled = true; } if (_gameTicker.ServerInfoBlob != null) { - _lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); + Lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); } } @@ -213,7 +185,7 @@ namespace Content.Client.Lobby { if (ev.SoundtrackFilename == null) { - _lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); + Lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text")); } else if ( ev.SoundtrackFilename != null @@ -234,7 +206,7 @@ namespace Content.Client.Lobby ("songTitle", title), ("songArtist", artist)); - _lobby!.LobbySong.SetMarkup(markup); + Lobby!.LobbySong.SetMarkup(markup); } } @@ -242,11 +214,11 @@ namespace Content.Client.Lobby { if (_gameTicker.LobbyBackground != null) { - _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); + Lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); } else { - _lobby!.Background.Texture = null; + Lobby!.Background.Texture = null; } } diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs index 9eb259657d..ae9196c110 100644 --- a/Content.Client/Lobby/LobbyUIController.cs +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -2,190 +2,292 @@ using System.Linq; using Content.Client.Humanoid; using Content.Client.Inventory; using Content.Client.Lobby.UI; -using Content.Client.Preferences; -using Content.Client.Preferences.UI; +using Content.Client.Players.PlayTimeTracking; using Content.Client.Station; +using Content.Shared.CCVar; using Content.Shared.Clothing; using Content.Shared.GameTicking; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; -using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Roles; +using Content.Shared.Traits; +using Robust.Client.Player; +using Robust.Client.ResourceManagement; using Robust.Client.State; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Configuration; using Robust.Shared.Map; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Utility; namespace Content.Client.Lobby; public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited { [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; - [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + [Dependency] private readonly IFileDialogManager _dialogManager = default!; + [Dependency] private readonly ILogManager _logManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IStateManager _stateManager = default!; + [Dependency] private readonly JobRequirementsManager _requirements = default!; + [Dependency] private readonly MarkingManager _markings = default!; [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [UISystemDependency] private readonly ClientInventorySystem _inventory = default!; [UISystemDependency] private readonly StationSpawningSystem _spawn = default!; - private LobbyCharacterPreviewPanel? _previewPanel; - - private bool _showClothes = true; - - /* - * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor - * that is shared too. - */ + private CharacterSetupGui? _characterSetup; + private HumanoidProfileEditor? _profileEditor; /// - /// Preview dummy for role gear. + /// This is the characher preview panel in the chat. This should only update if their character updates. /// - private EntityUid? _previewDummy; + private LobbyCharacterPreviewPanel? PreviewPanel => GetLobbyPreview(); /// - /// If we currently have a job prototype selected. + /// This is the modified profile currently being edited. /// - private JobPrototype? _dummyJob; - - // TODO: Load the species directly and don't update entity ever. - public event Action? PreviewDummyUpdated; + private HumanoidCharacterProfile? EditedProfile => _profileEditor?.Profile; - private HumanoidCharacterProfile? _profile; + private int? EditedSlot => _profileEditor?.CharacterSlot; public override void Initialize() { base.Initialize(); + _prototypeManager.PrototypesReloaded += OnProtoReload; _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; - } + _requirements.Updated += OnRequirementsUpdated; - private void PreferencesDataLoaded() - { - UpdateProfile(); + _configurationManager.OnValueChanged(CCVars.FlavorText, args => + { + _profileEditor?.RefreshFlavorText(); + }); + + _configurationManager.OnValueChanged(CCVars.GameRoleTimers, args => + { + _profileEditor?.RefreshAntags(); + _profileEditor?.RefreshJobs(); + _profileEditor?.RefreshLoadouts(); + }); } - public void OnStateEntered(LobbyState state) + private LobbyCharacterPreviewPanel? GetLobbyPreview() { + if (_stateManager.CurrentState is LobbyState lobby) + { + return lobby.Lobby?.CharacterPreview; + } + + return null; } - public void OnStateExited(LobbyState state) + private void OnRequirementsUpdated() { - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = null; + if (_profileEditor != null) + { + _profileEditor.RefreshAntags(); + _profileEditor.RefreshJobs(); + } } - public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel) + private void OnProtoReload(PrototypesReloadedEventArgs obj) { - _previewPanel = panel; - ReloadProfile(); + if (_profileEditor != null) + { + if (obj.WasModified()) + { + _profileEditor.RefreshAntags(); + } + + if (obj.WasModified() || + obj.WasModified()) + { + _profileEditor.RefreshJobs(); + } + + if (obj.WasModified() || + obj.WasModified() || + obj.WasModified()) + { + _profileEditor.RefreshLoadouts(); + } + + if (obj.WasModified()) + { + _profileEditor.RefreshSpecies(); + } + + if (obj.WasModified()) + { + _profileEditor.RefreshTraits(); + } + } } - public void SetClothes(bool value) + private void PreferencesDataLoaded() { - if (_showClothes == value) + PreviewPanel?.SetLoaded(true); + + if (_stateManager.CurrentState is not LobbyState) return; - _showClothes = value; - ReloadCharacterUI(); + ReloadCharacterSetup(); } - public void SetDummyJob(JobPrototype? job) + public void OnStateEntered(LobbyState state) { - _dummyJob = job; - ReloadCharacterUI(); + PreviewPanel?.SetLoaded(_preferencesManager.ServerDataLoaded); + ReloadCharacterSetup(); } - /// - /// Updates the character only with the specified profile change. - /// - public void ReloadProfile() + public void OnStateExited(LobbyState state) { - // Test moment - if (_profile == null || _stateManager.CurrentState is not LobbyState) - return; + PreviewPanel?.SetLoaded(false); + _profileEditor?.Dispose(); + _characterSetup?.Dispose(); - // Ignore job clothes and the likes so we don't spam entities out every frame of color changes. - var previewDummy = EnsurePreviewDummy(_profile); - _humanoid.LoadProfile(previewDummy, _profile); + _characterSetup = null; + _profileEditor = null; } /// - /// Updates the currently selected character's preview. + /// Reloads every single character setup control. /// - public void ReloadCharacterUI() + public void ReloadCharacterSetup() { - // Test moment - if (_profile == null || _stateManager.CurrentState is not LobbyState) - return; - - EntityManager.DeleteEntity(_previewDummy); - _previewDummy = null; - _previewDummy = EnsurePreviewDummy(_profile); - _previewPanel?.SetSprite(_previewDummy.Value); - _previewPanel?.SetSummaryText(_profile.Summary); - _humanoid.LoadProfile(_previewDummy.Value, _profile); - - if (_showClothes) - GiveDummyJobClothesLoadout(_previewDummy.Value, _profile); + RefreshLobbyPreview(); + var (characterGui, profileEditor) = EnsureGui(); + characterGui.ReloadCharacterPickers(); + profileEditor.SetProfile( + (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter, + _preferencesManager.Preferences?.SelectedCharacterIndex); } /// - /// Updates character profile to the default. + /// Refreshes the character preview in the lobby chat. /// - public void UpdateProfile() + private void RefreshLobbyPreview() { - if (!_preferencesManager.ServerDataLoaded) - { - _profile = null; + if (PreviewPanel == null) return; - } - if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter) - { - _profile = selectedCharacter; - _previewPanel?.SetLoaded(true); - } - else + // Get selected character, load it, then set it + var character = _preferencesManager.Preferences?.SelectedCharacter; + + if (character is not HumanoidCharacterProfile humanoid) { - _previewPanel?.SetSummaryText(string.Empty); - _previewPanel?.SetLoaded(false); + PreviewPanel.SetSprite(EntityUid.Invalid); + PreviewPanel.SetSummaryText(string.Empty); + return; } - ReloadCharacterUI(); + var dummy = LoadProfileEntity(humanoid, null, true); + PreviewPanel.SetSprite(dummy); + PreviewPanel.SetSummaryText(humanoid.Summary); } - public void UpdateProfile(HumanoidCharacterProfile? profile) + private void SaveProfile() { - if (_profile?.Equals(profile) == true) + DebugTools.Assert(EditedProfile != null); + + if (EditedProfile == null || EditedSlot == null) return; - if (_stateManager.CurrentState is not LobbyState) + var selected = _preferencesManager.Preferences?.SelectedCharacterIndex; + + if (selected == null) return; - _profile = profile; + _preferencesManager.UpdateCharacter(EditedProfile, EditedSlot.Value); + ReloadCharacterSetup(); } - private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile) + private (CharacterSetupGui, HumanoidProfileEditor) EnsureGui() { - if (_previewDummy != null) - return _previewDummy.Value; + if (_characterSetup != null && _profileEditor != null) + { + _characterSetup.Visible = true; + _profileEditor.Visible = true; + return (_characterSetup, _profileEditor); + } + + _profileEditor = new HumanoidProfileEditor( + _preferencesManager, + _configurationManager, + EntityManager, + _dialogManager, + _logManager, + _playerManager, + _prototypeManager, + _requirements, + _markings); + + _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor); + + _characterSetup.CloseButton.OnPressed += _ => + { + // Reset sliders etc. + _profileEditor.SetProfile(null, null); + _profileEditor.Visible = false; + + if (_stateManager.CurrentState is LobbyState lobbyGui) + { + lobbyGui.SwitchState(LobbyGui.LobbyGuiState.Default); + } + }; + + _profileEditor.Save += SaveProfile; + + _characterSetup.SelectCharacter += args => + { + _preferencesManager.SelectCharacter(args); + ReloadCharacterSetup(); + }; + + _characterSetup.DeleteCharacter += args => + { + _preferencesManager.DeleteCharacter(args); - _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace); - PreviewDummyUpdated?.Invoke(_previewDummy.Value); - return _previewDummy.Value; + // Reload everything + if (EditedSlot == args) + { + ReloadCharacterSetup(); + } + else + { + // Only need to reload character pickers + _characterSetup?.ReloadCharacterPickers(); + } + }; + + if (_stateManager.CurrentState is LobbyState lobby) + { + lobby.Lobby?.CharacterSetupState.AddChild(_characterSetup); + } + + return (_characterSetup, _profileEditor); } + #region Helpers + /// /// Applies the highest priority job's clothes to the dummy. /// - public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile) + public void GiveDummyJobClothesLoadout(EntityUid dummy, JobPrototype? jobProto, HumanoidCharacterProfile profile) { - var job = _dummyJob ?? GetPreferredJob(profile); + var job = jobProto ?? GetPreferredJob(profile); GiveDummyJobClothes(dummy, profile, job); if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) { - var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager); + var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), profile.Species, EntityManager, _prototypeManager); GiveDummyLoadout(dummy, loadout); } } @@ -279,8 +381,39 @@ public sealed class LobbyUIController : UIController, IOnStateEntered + /// Loads the profile onto a dummy entity. + /// + public EntityUid LoadProfileEntity(HumanoidCharacterProfile? humanoid, JobPrototype? job, bool jobClothes) { - return _previewDummy; + EntityUid dummyEnt; + + if (humanoid is not null) + { + var dummy = _prototypeManager.Index(humanoid.Species).DollPrototype; + dummyEnt = EntityManager.SpawnEntity(dummy, MapCoordinates.Nullspace); + } + else + { + dummyEnt = EntityManager.SpawnEntity(_prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies).DollPrototype, MapCoordinates.Nullspace); + } + + _humanoid.LoadProfile(dummyEnt, humanoid); + + if (humanoid != null && jobClothes) + { + job ??= GetPreferredJob(humanoid); + GiveDummyJobClothes(dummyEnt, humanoid, job); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) + { + var loadout = humanoid.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), humanoid.Species, EntityManager, _prototypeManager); + GiveDummyLoadout(dummyEnt, loadout); + } + } + + return dummyEnt; } + + #endregion } diff --git a/Content.Client/Lobby/UI/CharacterPickerButton.xaml b/Content.Client/Lobby/UI/CharacterPickerButton.xaml new file mode 100644 index 0000000000..af1e640aad --- /dev/null +++ b/Content.Client/Lobby/UI/CharacterPickerButton.xaml @@ -0,0 +1,22 @@ + + + +