using Content.Client._CP14.Workbench; using Content.Shared._CP14.Skill; using Content.Shared._CP14.Skill.Prototypes; using Content.Shared._CP14.Skill.Restrictions; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; namespace Content.Client._CP14.ResearchTable; [GenerateTypedNameReferences] public sealed partial class CP14ResearchTableWindow : DefaultWindow { private const int AllCategoryId = -1; [Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly ILogManager _log = default!; [Dependency] private readonly IEntityManager _entity = default!; private readonly SpriteSystem _sprite; private readonly CP14SharedSkillSystem _skillSystem; public event Action? OnResearch; private readonly Dictionary _categories = new(); private CP14ResearchTableUiState? _cachedState; private CP14ResearchUiEntry? _selectedEntry; private string _searchFilter = string.Empty; private ISawmill Sawmill { get; init; } public CP14ResearchTableWindow() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _sprite = _entity.System(); _skillSystem = _entity.System(); Sawmill = _log.GetSawmill("cp14_research_table_window"); SearchBar.OnTextChanged += OnSearchChanged; CraftButton.OnPressed += OnCraftPressed; OptionCategories.OnItemSelected += OnCategoryItemSelected; } public void UpdateRecipesVisibility() { if (_cachedState is null) return; CraftsContainer.RemoveAllChildren(); var recipes = new List(); foreach (var entry in _cachedState.Skills) { if (!_prototype.TryIndex(entry.ProtoId, out var indexedEntry)) { Sawmill.Error($"No recipe prototype {entry.ProtoId} retrieved from cache found"); continue; } if (!ProcessSearchFilter(indexedEntry)) continue; if (!ProcessSearchCategoryFilter(indexedEntry)) continue; recipes.Add(entry); } recipes.Sort(CP14ResearchUiEntry.CompareTo); foreach (var recipe in recipes) { var control = new CP14ResearchRecipeControl(recipe); control.OnResearch += RecipeSelect; CraftsContainer.AddChild(control); } if (_selectedEntry is not null && !recipes.Contains(_selectedEntry.Value)) RecipeSelectNull(); } public void UpdateState(CP14ResearchTableUiState recipesState) { _cachedState = recipesState; _categories.Clear(); OptionCategories.Clear(); OptionCategories.AddItem(Loc.GetString("cp14-recipe-category-all"), AllCategoryId); var categories = new List(); var count = 0; foreach (var skill in recipesState.Skills) { if (!_prototype.TryIndex(skill.ProtoId, out var indexedSkill)) continue; if (!_prototype.TryIndex(indexedSkill.Tree, out var indexedTree)) continue; if (categories.Contains(indexedTree.Name)) continue; categories.Add(indexedTree.Name); } categories.Sort((a, b) => string.Compare(Loc.GetString(a), Loc.GetString(b), StringComparison.Ordinal)); foreach (var category in categories) { OptionCategories.AddItem(Loc.GetString(category), count); _categories.Add(count, category); count++; } UpdateRecipesVisibility(); } private void OnSearchChanged(LineEdit.LineEditEventArgs _) { _searchFilter = SearchBar.Text.Trim().ToLowerInvariant(); UpdateRecipesVisibility(); } private void OnCraftPressed(BaseButton.ButtonEventArgs _) { if (_selectedEntry is null) return; OnResearch?.Invoke(_selectedEntry.Value); } private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs obj) { OptionCategories.SelectId(obj.Id); UpdateRecipesVisibility(); } private bool ProcessSearchFilter(CP14SkillPrototype indexedEntry) { if (_searchFilter == string.Empty) return true; return Loc.GetString(_skillSystem.GetSkillName(indexedEntry)).Contains(_searchFilter); } private bool ProcessSearchCategoryFilter(CP14SkillPrototype indexedEntry) { // If we are searching through all categories, we simply skip the current filter if (OptionCategories.SelectedId == AllCategoryId) return true; if (!_categories.TryGetValue(OptionCategories.SelectedId, out var selectedCategory)) { Sawmill.Error($"Non-existent {OptionCategories.SelectedId} category id selected. Filter skipped"); return true; } if (!_prototype.TryIndex(indexedEntry.Tree, out var indexedTree)) { Sawmill.Error($"Non-existent {indexedEntry.Tree} category prototype id. Filter skipped"); return true; } return indexedTree.Name == selectedCategory; } private void RecipeSelect(CP14ResearchTableUiState recipesState) { foreach (var skill in recipesState.Skills) { RecipeSelect(skill, _prototype.Index(skill.ProtoId)); break; } } private void RecipeSelect(CP14ResearchUiEntry cachedEntry) { if (_cachedState is null) return; if (_cachedState.Skills.Contains(cachedEntry)) { Sawmill.Warning($"The selected cache option {cachedEntry} isn't found in recipes"); return; } RecipeSelect(cachedEntry, _prototype.Index(cachedEntry.ProtoId)); } private void RecipeSelect(CP14ResearchUiEntry entry, CP14SkillPrototype skill) { _selectedEntry = entry; ItemView.Texture = _sprite.Frame0(skill.Icon); ItemName.Text = _skillSystem.GetSkillName(skill); ItemDescription.Text = _skillSystem.GetSkillDescription(skill); ItemRequirements.RemoveAllChildren(); foreach (var restriction in skill.Restrictions) { switch (restriction) { case Researched researched: foreach (var requirement in researched.Requirements) { ItemRequirements.AddChild(new CP14WorkbenchRequirementControl(requirement)); } break; } } CraftButton.Disabled = !entry.Craftable; } private void RecipeSelectNull() { _selectedEntry = null; ItemView.Texture = null; ItemName.Text = string.Empty; ItemDescription.Text = string.Empty; ItemRequirements.RemoveAllChildren(); CraftButton.Disabled = true; } }