diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index de51b2fb192..8512107b69d 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() => for (var i = 0; i < N; i++) { _entity = server.EntMan.SpawnAttachedTo(Mob, _coords); - _spawnSys.EquipStartingGear(_entity, _gear, null); + _spawnSys.EquipStartingGear(_entity, _gear); server.EntMan.DeleteEntity(_entity); } }); diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index 956f2fd0351..02fefa5cf20 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -26,6 +26,9 @@ + + + diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index 70fe1916584..65e95b76f08 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -21,6 +21,7 @@ using Content.Client.Guidebook; using Content.Client.Replay; using Content.Shared.Administration.Managers; +using Content.Shared.Players.PlayTimeTracking; namespace Content.Client.IoC @@ -29,26 +30,29 @@ internal static class ClientContentIoC { public static void Register() { - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); + var collection = IoCManager.Instance!; + + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); + collection.Register(); } } } diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index fe31dce0629..91730020a4e 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -64,13 +64,19 @@ protected override void Startup() _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(); - _lobby.CharacterPreview.UpdateUI(); + _userInterfaceManager.GetUIController().ReloadProfile(); }; LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide); @@ -84,10 +90,6 @@ protected override void Startup() _gameTicker.InfoBlobUpdated += UpdateLobbyUi; _gameTicker.LobbyStatusUpdated += LobbyStatusUpdated; _gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated; - - _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; - - _lobby.CharacterPreview.UpdateUI(); } protected override void Shutdown() @@ -109,13 +111,6 @@ protected override void Shutdown() _characterSetup?.Dispose(); _characterSetup = null; - - _preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded; - } - - private void PreferencesDataLoaded() - { - _lobby?.CharacterPreview.UpdateUI(); } private void OnSetupPressed(BaseButton.ButtonEventArgs args) diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs new file mode 100644 index 00000000000..9eb259657dc --- /dev/null +++ b/Content.Client/Lobby/LobbyUIController.cs @@ -0,0 +1,286 @@ +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.Station; +using Content.Shared.Clothing; +using Content.Shared.GameTicking; +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 Robust.Client.State; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; + +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 IPrototypeManager _prototypeManager = 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. + */ + + /// + /// Preview dummy for role gear. + /// + private EntityUid? _previewDummy; + + /// + /// If we currently have a job prototype selected. + /// + private JobPrototype? _dummyJob; + + // TODO: Load the species directly and don't update entity ever. + public event Action? PreviewDummyUpdated; + + private HumanoidCharacterProfile? _profile; + + public override void Initialize() + { + base.Initialize(); + _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded; + } + + private void PreferencesDataLoaded() + { + UpdateProfile(); + } + + public void OnStateEntered(LobbyState state) + { + } + + public void OnStateExited(LobbyState state) + { + EntityManager.DeleteEntity(_previewDummy); + _previewDummy = null; + } + + public void SetPreviewPanel(LobbyCharacterPreviewPanel? panel) + { + _previewPanel = panel; + ReloadProfile(); + } + + public void SetClothes(bool value) + { + if (_showClothes == value) + return; + + _showClothes = value; + ReloadCharacterUI(); + } + + public void SetDummyJob(JobPrototype? job) + { + _dummyJob = job; + ReloadCharacterUI(); + } + + /// + /// Updates the character only with the specified profile change. + /// + public void ReloadProfile() + { + // Test moment + if (_profile == null || _stateManager.CurrentState is not LobbyState) + return; + + // 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); + } + + /// + /// Updates the currently selected character's preview. + /// + public void ReloadCharacterUI() + { + // 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); + } + + /// + /// Updates character profile to the default. + /// + public void UpdateProfile() + { + if (!_preferencesManager.ServerDataLoaded) + { + _profile = null; + return; + } + + if (_preferencesManager.Preferences?.SelectedCharacter is HumanoidCharacterProfile selectedCharacter) + { + _profile = selectedCharacter; + _previewPanel?.SetLoaded(true); + } + else + { + _previewPanel?.SetSummaryText(string.Empty); + _previewPanel?.SetLoaded(false); + } + + ReloadCharacterUI(); + } + + public void UpdateProfile(HumanoidCharacterProfile? profile) + { + if (_profile?.Equals(profile) == true) + return; + + if (_stateManager.CurrentState is not LobbyState) + return; + + _profile = profile; + } + + private EntityUid EnsurePreviewDummy(HumanoidCharacterProfile profile) + { + if (_previewDummy != null) + return _previewDummy.Value; + + _previewDummy = EntityManager.SpawnEntity(_prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace); + PreviewDummyUpdated?.Invoke(_previewDummy.Value); + return _previewDummy.Value; + } + + /// + /// Applies the highest priority job's clothes to the dummy. + /// + public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile) + { + var job = _dummyJob ?? GetPreferredJob(profile); + GiveDummyJobClothes(dummy, profile, job); + + if (_prototypeManager.HasIndex(LoadoutSystem.GetJobPrototype(job.ID))) + { + var loadout = profile.GetLoadoutOrDefault(LoadoutSystem.GetJobPrototype(job.ID), EntityManager, _prototypeManager); + GiveDummyLoadout(dummy, loadout); + } + } + + /// + /// Gets the highest priority job for the profile. + /// + public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile) + { + var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key; + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?) + return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob); + } + + public void GiveDummyLoadout(EntityUid uid, RoleLoadout? roleLoadout) + { + if (roleLoadout == null) + return; + + foreach (var group in roleLoadout.SelectedLoadouts.Values) + { + foreach (var loadout in group) + { + if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto)) + continue; + + _spawn.EquipStartingGear(uid, _prototypeManager.Index(loadoutProto.Equipment)); + } + } + } + + /// + /// Applies the specified job's clothes to the dummy. + /// + public void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile, JobPrototype job) + { + if (!_inventory.TryGetSlots(dummy, out var slots)) + return; + + // Apply loadout + if (profile.Loadouts.TryGetValue(job.ID, out var jobLoadout)) + { + foreach (var loadouts in jobLoadout.SelectedLoadouts.Values) + { + foreach (var loadout in loadouts) + { + if (!_prototypeManager.TryIndex(loadout.Prototype, out var loadoutProto)) + continue; + + // TODO: Need some way to apply starting gear to an entity coz holy fucking shit dude. + var loadoutGear = _prototypeManager.Index(loadoutProto.Equipment); + + foreach (var slot in slots) + { + var itemType = loadoutGear.GetGear(slot.Name); + + if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false)) + { + EntityManager.DeleteEntity(unequippedItem.Value); + } + + if (itemType != string.Empty) + { + var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace); + _inventory.TryEquip(dummy, item, slot.Name, true, true); + } + } + } + } + } + + if (job.StartingGear == null) + return; + + var gear = _prototypeManager.Index(job.StartingGear); + + foreach (var slot in slots) + { + var itemType = gear.GetGear(slot.Name); + + if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false)) + { + EntityManager.DeleteEntity(unequippedItem.Value); + } + + if (itemType != string.Empty) + { + var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace); + _inventory.TryEquip(dummy, item, slot.Name, true, true); + } + } + } + + public EntityUid? GetPreviewDummy() + { + return _previewDummy; + } +} diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs deleted file mode 100644 index f9481caa3bb..00000000000 --- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Linq; -using System.Numerics; -using Content.Client.Alerts; -using Content.Client.Humanoid; -using Content.Client.Inventory; -using Content.Client.Preferences; -using Content.Client.UserInterface.Controls; -using Content.Shared.GameTicking; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Inventory; -using Content.Shared.Preferences; -using Content.Shared.Roles; -using Robust.Client.GameObjects; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Shared.Map; -using Robust.Shared.Prototypes; -using static Robust.Client.UserInterface.Controls.BoxContainer; - -namespace Content.Client.Lobby.UI -{ - public sealed class LobbyCharacterPreviewPanel : Control - { - [Dependency] private readonly IEntityManager _entityManager = default!; - [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - - private EntityUid? _previewDummy; - private readonly Label _summaryLabel; - private readonly BoxContainer _loaded; - private readonly BoxContainer _viewBox; - private readonly Label _unloaded; - - public LobbyCharacterPreviewPanel() - { - IoCManager.InjectDependencies(this); - var header = new NanoHeading - { - Text = Loc.GetString("lobby-character-preview-panel-header") - }; - - CharacterSetupButton = new Button - { - Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"), - HorizontalAlignment = HAlignment.Center, - Margin = new Thickness(0, 5, 0, 0), - }; - - _summaryLabel = new Label - { - HorizontalAlignment = HAlignment.Center, - Margin = new Thickness(3, 3), - }; - - var vBox = new BoxContainer - { - Orientation = LayoutOrientation.Vertical - }; - _unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") }; - - _loaded = new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Visible = false - }; - _viewBox = new BoxContainer - { - Orientation = LayoutOrientation.Horizontal, - HorizontalAlignment = HAlignment.Center, - }; - var _vSpacer = new VSpacer(); - - _loaded.AddChild(_summaryLabel); - _loaded.AddChild(_viewBox); - _loaded.AddChild(_vSpacer); - _loaded.AddChild(CharacterSetupButton); - - vBox.AddChild(header); - vBox.AddChild(_loaded); - vBox.AddChild(_unloaded); - AddChild(vBox); - - UpdateUI(); - } - - public Button CharacterSetupButton { get; } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (!disposing) return; - if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value); - _previewDummy = default; - } - - public void UpdateUI() - { - if (!_preferencesManager.ServerDataLoaded) - { - _loaded.Visible = false; - _unloaded.Visible = true; - } - else - { - _loaded.Visible = true; - _unloaded.Visible = false; - if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter) - { - _summaryLabel.Text = string.Empty; - } - else - { - _previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace); - _viewBox.DisposeAllChildren(); - var spriteView = new SpriteView - { - OverrideDirection = Direction.South, - Scale = new Vector2(4f, 4f), - MaxSize = new Vector2(112, 112), - Stretch = SpriteView.StretchMode.Fill, - }; - spriteView.SetEntity(_previewDummy.Value); - _viewBox.AddChild(spriteView); - _summaryLabel.Text = selectedCharacter.Summary; - _entityManager.System().LoadProfile(_previewDummy.Value, selectedCharacter); - GiveDummyJobClothes(_previewDummy.Value, selectedCharacter); - } - } - } - - public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile) - { - var protoMan = IoCManager.Resolve(); - var entMan = IoCManager.Resolve(); - var invSystem = EntitySystem.Get(); - - var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key; - - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?) - var job = protoMan.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob); - - if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots)) - { - var gear = protoMan.Index(job.StartingGear); - - foreach (var slot in slots) - { - var itemType = gear.GetGear(slot.Name, profile); - - if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false)) - { - entMan.DeleteEntity(unequippedItem.Value); - } - - if (itemType != string.Empty) - { - var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace); - invSystem.TryEquip(dummy, item, slot.Name, true, true); - } - } - } - } - } -} diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml new file mode 100644 index 00000000000..997507414cd --- /dev/null +++ b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.xaml @@ -0,0 +1,22 @@ + + + + + + +