/* * This file is sublicensed under MIT License * https://github.com/space-wizards/space-station-14/blob/master/LICENSE.TXT */ using System.Linq; using Content.Client._CP14.Skill; using Content.Shared._CP14.Workbench; using Content.Shared._CP14.Workbench.Prototypes; using Robust.Client.AutoGenerated; using Robust.Client.Player; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; namespace Content.Client._CP14.Workbench; [GenerateTypedNameReferences] public sealed partial class CP14WorkbenchWindow : DefaultWindow { private const int AllCategoryId = -1; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ILogManager _log = default!; private CP14ClientSkillSystem _skill; public event Action? OnCraft; /// /// Used for category dropdown filtering. /// private readonly Dictionary _categoryIndexes = new(); private Dictionary> _categories = new(); private List _uncategorized = new(); private CP14WorkbenchUiRecipesState? _cachedState; private CP14WorkbenchUiRecipesEntry? _selectedEntry; private string _searchFilter = string.Empty; private ISawmill Sawmill { get; init; } public CP14WorkbenchWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _skill = _entManager.System(); Sawmill = _log.GetSawmill("cp14_workbench_window"); SearchBar.OnTextChanged += OnSearchChanged; CraftButton.OnPressed += OnCraftPressed; OptionCategories.OnItemSelected += OnCategoryItemSelected; } private void UpdateRecipesVisibility() { if (_cachedState is null) return; CraftsContainer.RemoveAllChildren(); if (_uncategorized.Count > 0 && OptionCategories.SelectedId == AllCategoryId) { var uncategorizedGridContainer = new GridContainer(); uncategorizedGridContainer.Columns = 5; uncategorizedGridContainer.VerticalExpand = true; CraftsContainer.AddChild(uncategorizedGridContainer); AddRecipeListToGrid(_uncategorized, uncategorizedGridContainer); } foreach (var category in _categories) { if (_categoryIndexes.TryGetValue(OptionCategories.SelectedId, out var selectedCategory) && category.Key != selectedCategory) continue; var categoryLabel = new RichTextLabel(); categoryLabel.Margin = new Thickness(5); categoryLabel.Text = Loc.GetString(category.Key); CraftsContainer.AddChild(categoryLabel); var gridContainer = new GridContainer(); gridContainer.Columns = 5; gridContainer.VerticalExpand = true; CraftsContainer.AddChild(gridContainer); AddRecipeListToGrid(category.Value, gridContainer); } if (_selectedEntry is not null && !_cachedState.Recipes.Contains(_selectedEntry.Value)) RecipeSelectNull(); } private void AddRecipeListToGrid(List category, GridContainer gridContainer) { foreach (var entry in category) { if (!_prototype.TryIndex(entry.ProtoId, out var indexedEntry)) { Sawmill.Error($"No recipe prototype {entry.ProtoId} retrieved from cache found"); continue; } if (!ProcessSearchFilter(entry, indexedEntry)) continue; if (!ProcessSearchCategoryFilter(indexedEntry)) continue; if (_player.LocalEntity is not null) { var skilled = true; foreach (var skill in indexedEntry.RequiredSkills) { if (!_skill.HaveSkill(_player.LocalEntity.Value, skill)) { skilled = false; break; } } if (!skilled) continue; } var control = new CP14WorkbenchRecipeControl(entry); control.OnSelect += RecipeSelect; gridContainer.AddChild(control); } } public void UpdateState(CP14WorkbenchUiRecipesState recipesState) { if (_player.LocalEntity is null) return; _cachedState = recipesState; _categoryIndexes.Clear(); _categories.Clear(); _uncategorized.Clear(); OptionCategories.Clear(); OptionCategories.AddItem(Loc.GetString("cp14-recipe-category-all"), AllCategoryId); // First, we sort all the recipes by priority and category. var sortedRecipes = recipesState.Recipes .OrderByDescending(e => e.Craftable) .ThenByDescending(e => { if (!_prototype.TryIndex(e.ProtoId, out CP14WorkbenchRecipePrototype? recipe)) return 0; return recipe.Priority; }) .ThenBy(e => { if (!_prototype.TryIndex(e.ProtoId, out CP14WorkbenchRecipePrototype? recipe) || !_prototype.TryIndex(recipe.Category, out CP14WorkbenchRecipeCategoryPrototype? category)) return string.Empty; return category.ID; }); foreach (var entry in sortedRecipes) { if (!_prototype.TryIndex(entry.ProtoId, out var indexedEntry)) continue; if (!_prototype.TryIndex(indexedEntry.Category, out var indexedCategory)) { _uncategorized.Add(entry); continue; } if (!_categories.TryGetValue(indexedCategory.Name, out var entries)) { entries = new List(); _categories[indexedCategory.Name] = entries; } entries.Add(entry); } // Sort categories by priority var sortedCategories = _categories .OrderByDescending(c => { var categoryProto = _prototype.EnumeratePrototypes() .FirstOrDefault(p => p.Name == c.Key); return categoryProto?.Priority ?? 0; }) .ToList(); _categories = sortedCategories.ToDictionary(pair => pair.Key, pair => pair.Value); var count = 0; foreach (var category in _categories) { OptionCategories.AddItem(Loc.GetString(category.Key), count); _categoryIndexes.Add(count, category.Key); count++; } UpdateRecipesVisibility(); } private void OnSearchChanged(LineEdit.LineEditEventArgs _) { _searchFilter = SearchBar.Text.Trim().ToLowerInvariant(); UpdateRecipesVisibility(); } private void OnCraftPressed(BaseButton.ButtonEventArgs _) { if (_selectedEntry is null) return; OnCraft?.Invoke(_selectedEntry.Value); } private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs obj) { OptionCategories.SelectId(obj.Id); UpdateRecipesVisibility(); } private bool ProcessSearchFilter(CP14WorkbenchUiRecipesEntry entry, CP14WorkbenchRecipePrototype indexedEntry) { if (_searchFilter == string.Empty) return true; // Skip the iteration, because the desired result does not match the filter if (_prototype.TryIndex(indexedEntry.Result, out var indexedResult)) return indexedResult.Name.Contains(_searchFilter); Sawmill.Error($"No result entity prototype {entry.ProtoId} retrieved from cache found"); return false; } private bool ProcessSearchCategoryFilter(CP14WorkbenchRecipePrototype indexedEntry) { // If we are searching through all categories, we simply skip the current filter if (OptionCategories.SelectedId == AllCategoryId) return true; if (!_categoryIndexes.TryGetValue(OptionCategories.SelectedId, out var selectedCategory)) { Sawmill.Error($"Non-existent {OptionCategories.SelectedId} category id selected. Filter skipped"); return true; } if (indexedEntry.Category is null) return false; if (!_prototype.TryIndex(indexedEntry.Category, out var indexedCategory)) { Sawmill.Error($"Non-existent {indexedEntry.Category} category prototype id. Filter skipped"); return true; } return indexedCategory.Name == selectedCategory; } private void RecipeSelect(CP14WorkbenchUiRecipesState recipesState) { foreach (var entry in recipesState.Recipes) { RecipeSelect(entry, _prototype.Index(entry.ProtoId)); break; } } private void RecipeSelect(CP14WorkbenchUiRecipesEntry cachedEntry) { if (_cachedState is null) return; if (_cachedState.Recipes.Contains(cachedEntry)) { Sawmill.Warning($"The selected cache option {cachedEntry} isn't found in recipes"); return; } RecipeSelect(cachedEntry, _prototype.Index(cachedEntry.ProtoId)); } private void RecipeSelect(CP14WorkbenchUiRecipesEntry entry, CP14WorkbenchRecipePrototype recipe) { _selectedEntry = entry; var result = _prototype.Index(recipe.Result); // TODO: Make it through the localization? var counter = recipe.ResultCount > 1 ? $" x{recipe.ResultCount}" : string.Empty; ItemView.SetPrototype(recipe.Result); ItemName.Text = result.Name + counter; ItemDescription.Text = result.Description; ItemRequirements.RemoveAllChildren(); foreach (var requirement in recipe.Requirements) { ItemRequirements.AddChild(new CP14WorkbenchRequirementControl(requirement)); } CraftButton.Disabled = !entry.Craftable; } private void RecipeSelectNull() { _selectedEntry = null; ItemView.SetPrototype(null); ItemName.Text = string.Empty; ItemDescription.Text = string.Empty; ItemRequirements.RemoveAllChildren(); CraftButton.Disabled = true; } }