diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml index 84c5b75d1cb..81334cc762d 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml @@ -4,6 +4,7 @@ xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" xmlns:ui="clr-namespace:Content.Client.Lobby.UI" xmlns:profile="clr-namespace:Content.Client.Lobby.UI.ProfileEditorControls" + xmlns:loadouts="clr-namespace:Content.Client._DEN.Lobby.UI.Loadouts" HorizontalExpand="True"> @@ -130,6 +131,9 @@ + + + diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 3b444bf4162..c9034cd00dc 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -286,6 +286,12 @@ public HumanoidProfileEditor( #endregion Markings + #region Den Loadouts + + TabContainer.SetTabTitle(5, "Loadouts"); // TODO: localize + + #endregion + RefreshFlavorText(); #region Dummy @@ -379,7 +385,7 @@ public void SetProfile(HumanoidCharacterProfile? profile, int? slot) RefreshAntags(); RefreshJobs(); - RefreshLoadouts(); + RefreshDenLoadouts(); RefreshSpecies(); RefreshTraits(); RefreshFlavorText(); diff --git a/Content.Client/_DEN/Lobby/UI/HumanoidProfileEditor.Loadouts.cs b/Content.Client/_DEN/Lobby/UI/HumanoidProfileEditor.Loadouts.cs new file mode 100644 index 00000000000..a0fe5ba76a0 --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/HumanoidProfileEditor.Loadouts.cs @@ -0,0 +1,17 @@ +namespace Content.Client.Lobby.UI; + +public sealed partial class HumanoidProfileEditor +{ + private void RefreshDenLoadouts() + { + DenLoadouts.OnPreferenceUpdated += profile => + { + Profile = profile; + SetDirty(); + }; + + DenLoadouts.SetProfile(Profile); + DenLoadouts.RefreshCategories(); + DenLoadouts.RefreshLoadoutItemCategories(); + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.JobLoadouts.cs b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.JobLoadouts.cs new file mode 100644 index 00000000000..4f2d8a086ed --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.JobLoadouts.cs @@ -0,0 +1,11 @@ +using Content.Shared._DEN.Loadout; + +namespace Content.Client._DEN.Lobby.UI.Loadouts; + +public sealed partial class LoadoutProfilesContainer +{ + public void OnClickEditJobLoadouts(DenLoadoutProfile loadoutProfile) + { + + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.LoadoutProfile.cs b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.LoadoutProfile.cs new file mode 100644 index 00000000000..2735cbbff06 --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.LoadoutProfile.cs @@ -0,0 +1,19 @@ +using Content.Shared._DEN.Loadout; + +namespace Content.Client._DEN.Lobby.UI.Loadouts; + +public sealed partial class LoadoutProfilesContainer +{ + private void OnClickEditLoadoutProfile(DenLoadoutProfile loadoutProfile) + { + OnTryCreateLoadout(loadoutProfile); + } + + private void OnClickDeleteLoadoutProfile(DenLoadoutProfile loadoutProfile) + { + _profile = _profile?.WithoutLoadoutProfile(loadoutProfile); + SetDirty(_profile); + + RefreshCategories(); + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Loadouts.cs b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Loadouts.cs new file mode 100644 index 00000000000..ab521057cfa --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Loadouts.cs @@ -0,0 +1,128 @@ +using System.Linq; +using Content.Client.Stylesheets; +using Content.Shared._DEN.Loadout; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; + +namespace Content.Client._DEN.Lobby.UI.Loadouts; + +public sealed partial class LoadoutProfilesContainer +{ + private readonly Dictionary, LoadoutCategoryPrototype> _loadoutCategories = new(); + + private readonly Dictionary, ProtoId> + _parents = new(); + private readonly Dictionary, EntityLoadoutPrototype> _loadoutEntities = new(); + private readonly HashSet> _root = new(); + private readonly Dictionary, Button> _categoryButtons = new(); + + public ProtoId? CurrentCategory = null; + + private void OnClickEditLoadouts(DenLoadoutProfile loadoutProfile) + { + LoadoutItemSelection.Visible = !LoadoutContainer.Visible; + LoadoutProfileSelection.Visible = !LoadoutContainer.Visible; + } + + private void CacheLoadouts() + { + _loadoutCategories.Clear(); + _parents.Clear(); + _loadoutEntities.Clear(); + + foreach (var category in _protoMan.EnumeratePrototypes()) + { + foreach (var child in category.SubCategories) + { + _parents[child] = category; + } + + if (category.Root && !_parents.ContainsKey(category)) + _root.Add(category); + + _loadoutCategories.Add(category, category); + } + + foreach (var loadoutItem in _protoMan.EnumeratePrototypes()) + { + _loadoutEntities.Add(loadoutItem, loadoutItem); + } + } + + public void RefreshLoadoutItemCategories() + { + HashSet allCategories; + + foreach (var button in _categoryButtons.Values) + { + button.Orphan(); + } + + _categoryButtons.Clear(); + + if (CurrentCategory != null) + { + allCategories = _loadoutCategories[CurrentCategory.Value] + .SubCategories + .Select(c => _loadoutCategories[c]) + .ToHashSet(); + } + else + { + allCategories = _root + .Select(c => _loadoutCategories[c]) + .ToHashSet(); + } + + var (itemCategories, subCategories) = GetCategoryTypes(allCategories); + + foreach (var category in itemCategories) + { + var button = new Button + { + Text = category.Name, + Margin = new Thickness(5, 1), + HorizontalExpand = true, + SetHeight = 25 + }; + + LoadoutItemSelection.LoadoutItemCategoryContainer.AddChild(button); + _categoryButtons.Add(category, button); + } + + foreach (var category in subCategories) + { + var button = new Button + { + Text = "boo", + Margin = new Thickness(5, 1), + HorizontalExpand = true, + SetHeight = 25, + StyleClasses = { "ButtonColorGreen" } + }; + + LoadoutItemSelection.LoadoutItemCategoryContainer.AddChild(button); + _categoryButtons.Add(category, button); + } + } + + private (HashSet ItemCategories, + HashSet SubCategories) GetCategoryTypes(HashSet allCategories) + { + var itemCategories = new HashSet(); + var subCategories = new HashSet(); + + foreach (var category in allCategories) + { + if (category.SubCategories.Count > 0) + { + subCategories.Add(category); + continue; + } + + itemCategories.Add(category); + } + + return (itemCategories, subCategories); + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Popups.cs b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Popups.cs new file mode 100644 index 00000000000..0e063df638f --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.Popups.cs @@ -0,0 +1,106 @@ +using Content.Client._DEN.Lobby.UI.Loadouts.Popups; +using Content.Shared._DEN.Loadout; +using Robust.Shared.Prototypes; + +namespace Content.Client._DEN.Lobby.UI.Loadouts; + +public sealed partial class LoadoutProfilesContainer +{ + private void OnTryCreateCategory(LoadoutProfileCategory? existingCategory = null) + { + if (_createCategoryPopup != null) + return; + + _createCategoryPopup = new Popups.CreateCategoryPopup(existingCategory); + _createCategoryPopup.OnSubmit += OnSubmitNewCategory; + _createCategoryPopup.OnClose += OnCloseCategoryPopup; + _createCategoryPopup.OpenCentered(); + } + + private void OnSubmitNewCategory(CategoryCreationRequest request) + { + if (string.IsNullOrEmpty(request.CategoryName)) + return; + + var guid = request.ExistingCategoryId ?? Guid.NewGuid(); + var priority = 100; + var members = new HashSet(); + + if (_profile?.LoadoutCategories.TryGetValue(guid, out var loadout) == true) + { + priority = loadout.Priority; + members.UnionWith(loadout.Members); + } + + var category = new LoadoutProfileCategory + { + Id = guid, + Color = request.CategoryColor.ToHex(), + Name = request.CategoryName, + Priority = priority, + Members = members + }; + + _profile = _profile?.WithLoadoutCategory(guid, category); + OnPreferenceUpdated?.Invoke(_profile); + + RefreshCategories(); + OnCloseCategoryPopup(); + } + + private void OnTryCreateLoadout(DenLoadoutProfile? loadout = null) + { + if (_createLoadoutPopup != null) + return; + + _createLoadoutPopup = new Popups.CreateLoadoutPopup(_popupCategories, loadout); + _createLoadoutPopup.OnSubmit += OnSubmitNewLoadout; + _createLoadoutPopup.OnClose += OnCloseLoadoutPopup; + _createLoadoutPopup.OpenCentered(); + } + + private void OnSubmitNewLoadout(LoadoutCreationRequest request) + { + if (string.IsNullOrEmpty(request.LoadoutName)) + return; + + var guid = Guid.NewGuid(); + var priority = 100; + var loadouts = new HashSet>(); + + if (request.ExistingLoadoutId is { } loadoutProfileId + && _profile?.LoadoutProfiles.TryGetValue(loadoutProfileId, out var loadoutProfile) == true) + { + guid = loadoutProfile.Id; + priority = loadoutProfile.Priority; + loadouts.UnionWith(loadoutProfile.Loadouts); + } + + var loadout = new DenLoadoutProfile + { + Id = guid, + Name = request.LoadoutName, + LoadoutCategory = request.CategoryId, + Priority = priority, + Loadouts = loadouts, + }; + + _profile = _profile?.WithLoadoutProfile(guid, request.CategoryId, loadout); + SetDirty(_profile); + + RefreshCategories(); + OnCloseLoadoutPopup(); + } + + private void OnCloseCategoryPopup() + { + _createCategoryPopup?.Orphan(); + _createCategoryPopup = null; + } + + private void OnCloseLoadoutPopup() + { + _createLoadoutPopup?.Orphan(); + _createLoadoutPopup = null; + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.xaml b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.xaml new file mode 100644 index 00000000000..728e2de9b6d --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/LoadoutProfilesContainer.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenCategoryContainer.xaml.cs b/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenCategoryContainer.xaml.cs new file mode 100644 index 00000000000..10b3a8dff2f --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenCategoryContainer.xaml.cs @@ -0,0 +1,107 @@ +using Content.Shared._DEN.Loadout; +using Robust.Client.AutoGenerated; +using Robust.Client.Graphics; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._DEN.Lobby.UI.Loadouts.Subcontainers; + +[GenerateTypedNameReferences] +public sealed partial class DenCategoryContainer : PanelContainer +{ + public event Action? OnClickLoadoutAction; + public readonly Dictionary LoadoutContainers = new(); + public List Loadouts; + private bool _isExpanded; + + private LoadoutProfileCategory _profileCategory; + + public DenCategoryContainer(LoadoutProfileCategory profileCategory, + List loadouts) + { + RobustXamlLoader.Load(this); + + Loadouts = loadouts; + _profileCategory = profileCategory; + + RefreshCategoryName(profileCategory); + RefreshCategoryColor(profileCategory); + RefreshLoadoutContainers(loadouts); + + HeaderButton.OnPressed += _ => ToggleExpanded(); + UpdateExpandedState(); + } + + public void UpdateCategory(LoadoutProfileCategory profileCategory, List loadouts) + { + RefreshCategoryName(profileCategory); + RefreshCategoryColor(profileCategory); + RefreshLoadoutContainers(loadouts); + + Loadouts = loadouts; + _profileCategory = profileCategory; + } + + private void RefreshCategoryName(LoadoutProfileCategory profileCategory) + { + CategoryNameLabel.Text = profileCategory.Name; + } + + private void RefreshCategoryColor(LoadoutProfileCategory profileCategory) + { + var color = Color.TryFromHex(profileCategory.Color); + + AccentBar.PanelOverride = new StyleBoxFlat + { + BackgroundColor = color ?? Color.White + }; + } + + private void RefreshLoadoutContainers(List loadoutProfiles) + { + foreach (var loadoutProfile in loadoutProfiles) + { + DenLoadoutContainer container; + + if (LoadoutContainers.TryGetValue(loadoutProfile.Id, out var cachedContainer)) + { + container = cachedContainer; + } + else + { + container = new DenLoadoutContainer(loadoutProfile); + container.Visible = true; + container.OnClickLoadoutAction += DoLoadoutAction; + + LoadoutContainers.Add(loadoutProfile.Id, container); + LoadoutContainer.AddChild(container); + } + + container.UpdateLoadout(loadoutProfile); + } + + var countText = $"{LoadoutContainers.Count} loadout"; + + if (LoadoutContainers.Count != 1) + countText += "s"; + + CategoryLoadoutCount.Text = countText; + } + + private void DoLoadoutAction(DenLoadoutProfile profile, DenLoadoutAction action) + { + OnClickLoadoutAction?.Invoke(profile, action); + } + + public void ToggleExpanded() + { + _isExpanded = !_isExpanded; + UpdateExpandedState(); + } + + private void UpdateExpandedState() + { + ContentPanel.Visible = _isExpanded; + ExpandIcon.Text = _isExpanded ? "▼" : "▶"; + } +} diff --git a/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenLoadoutContainer.xaml b/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenLoadoutContainer.xaml new file mode 100644 index 00000000000..da08bc6d911 --- /dev/null +++ b/Content.Client/_DEN/Lobby/UI/Loadouts/Subcontainers/DenLoadoutContainer.xaml @@ -0,0 +1,35 @@ + + + + + + +