using System.Linq; using System.Text; using Content.Client.Materials; using Content.Shared.DeltaV.Salvage.Components; // DeltaV using Content.Shared.DeltaV.Salvage.Systems; // DeltaV using Content.Shared.Lathe; using Content.Shared.Lathe.Prototypes; using Content.Shared.Research.Prototypes; using Robust.Client.AutoGenerated; using Robust.Client.GameObjects; using Robust.Client.Player; // DeltaV using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; using Robust.Shared.Utility; using Robust.Shared.Timing; // DeltaV namespace Content.Client.Lathe.UI; [GenerateTypedNameReferences] public sealed partial class LatheMenu : DefaultWindow { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPlayerManager _player = default!; // DeltaV [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private readonly SpriteSystem _spriteSystem; private readonly LatheSystem _lathe; private readonly MaterialStorageSystem _materialStorage; private readonly MiningPointsSystem _miningPoints; // DeltaV public event Action? OnServerListButtonPressed; public event Action? RecipeQueueAction; public event Action? QueueDeleteAction; public event Action? QueueMoveUpAction; public event Action? QueueMoveDownAction; public event Action? DeleteFabricatingAction; public event Action? OnClaimMiningPoints; // DeltaV public List> Recipes = new(); public List>? Categories; public ProtoId? CurrentCategory; public EntityUid Entity; private uint? _lastMiningPoints; // DeltaV: used to avoid Loc.GetString every frame public LatheMenu() { RobustXamlLoader.Load(this); IoCManager.InjectDependencies(this); _spriteSystem = _entityManager.System(); _lathe = _entityManager.System(); _materialStorage = _entityManager.System(); _miningPoints = _entityManager.System(); // DeltaV SearchBar.OnTextChanged += _ => { PopulateRecipes(); }; AmountLineEdit.OnTextChanged += _ => { if (int.TryParse(AmountLineEdit.Text, out var amount)) { if (amount > LatheSystem.MaxItemsPerRequest) AmountLineEdit.Text = LatheSystem.MaxItemsPerRequest.ToString(); else if (amount < 0) AmountLineEdit.Text = "0"; } PopulateRecipes(); }; FilterOption.OnItemSelected += OnItemSelected; ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a); DeleteFabricating.OnPressed += _ => DeleteFabricatingAction?.Invoke(); } public void SetEntity(EntityUid uid) { Entity = uid; if (_entityManager.TryGetComponent(Entity, out var latheComponent)) { if (!latheComponent.DynamicPacks.Any()) { ServerListButton.Visible = false; } AmountLineEdit.SetText(latheComponent.DefaultProductionAmount.ToString()); } // Begin DeltaV Additions: Mining points UI MiningPointsContainer.Visible = _entityManager.TryGetComponent(Entity, out var points); MiningPointsClaimButton.OnPressed += _ => OnClaimMiningPoints?.Invoke(); if (points != null) UpdateMiningPoints(points.Points); // End DeltaV Additions MaterialsList.SetOwner(Entity); } /// /// DeltaV: Updates the UI elements for mining points. /// private void UpdateMiningPoints(uint points) { MiningPointsClaimButton.Disabled = points == 0 || _player.LocalSession?.AttachedEntity is not {} player || _miningPoints.TryFindIdCard(player) == null; if (points == _lastMiningPoints) return; _lastMiningPoints = points; MiningPointsLabel.Text = Loc.GetString("lathe-menu-mining-points", ("points", points)); } protected override void Opened() { base.Opened(); if (_entityManager.TryGetComponent(Entity, out var latheComp)) { AmountLineEdit.SetText(latheComp.DefaultProductionAmount.ToString()); } } /// /// DeltaV: Update mining points UI whenever it changes. /// protected override void FrameUpdate(FrameEventArgs args) { base.FrameUpdate(args); if (_entityManager.TryGetComponent(Entity, out var points)) UpdateMiningPoints(points.Points); } /// /// Populates the list of all the recipes /// public void PopulateRecipes() { var recipesToShow = new List(); foreach (var recipe in Recipes) { if (!_prototypeManager.Resolve(recipe, out var proto)) continue; // Category filtering if (CurrentCategory != null) { if (proto.Categories.Count <= 0) continue; var validRecipe = proto.Categories.Any(category => category == CurrentCategory); if (!validRecipe) continue; } if (SearchBar.Text.Trim().Length != 0) { if (_lathe.GetRecipeName(recipe).ToLowerInvariant().Contains(SearchBar.Text.Trim().ToLowerInvariant())) recipesToShow.Add(proto); } else { recipesToShow.Add(proto); } } if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0) quantity = 1; RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count)); var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName); // Get the existing list of queue controls var oldChildCount = RecipeList.ChildCount; _entityManager.TryGetComponent(Entity, out LatheComponent? lathe); int idx = 0; foreach (var prototype in sortedRecipesToShow) { var canProduce = _lathe.CanProduce(Entity, prototype, quantity, component: lathe); var tooltipFunction = () => GenerateTooltipText(prototype); if (idx >= oldChildCount) { var control = new RecipeControl(_lathe, prototype, tooltipFunction, canProduce, GetRecipeDisplayControl(prototype)); control.OnButtonPressed += s => { if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0) amount = 1; RecipeQueueAction?.Invoke(s, amount); }; RecipeList.AddChild(control); } else { var child = RecipeList.GetChild(idx) as RecipeControl; if (child == null) { DebugTools.Assert($"Lathe menu recipe control at {idx} is not of type RecipeControl"); // Something's gone terribly wrong. continue; } child.SetRecipe(prototype); child.SetTooltipSupplier(tooltipFunction); child.SetCanProduce(canProduce); child.SetDisplayControl(GetRecipeDisplayControl(prototype)); } idx++; } // Shrink list if new list is shorter than old list. for (var childIdx = oldChildCount - 1; idx <= childIdx; childIdx--) { RecipeList.RemoveChild(childIdx); } } private string GenerateTooltipText(LatheRecipePrototype prototype) { StringBuilder sb = new(); var multiplier = _entityManager.GetComponent(Entity).MaterialUseMultiplier; foreach (var (id, amount) in prototype.Materials) { if (!_prototypeManager.Resolve(id, out var proto)) continue; var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, multiplier); var sheetVolume = _materialStorage.GetSheetVolume(proto); var unit = Loc.GetString(proto.Unit); var sheets = adjustedAmount / (float) sheetVolume; var availableAmount = _materialStorage.GetMaterialAmount(Entity, id); var missingAmount = Math.Max(0, adjustedAmount - availableAmount); var missingSheets = missingAmount / (float) sheetVolume; var name = Loc.GetString(proto.Name); string tooltipText; if (missingSheets > 0) { tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name)); } else { var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit)); tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)); } sb.AppendLine(tooltipText); } var desc = _lathe.GetRecipeDescription(prototype); if (!string.IsNullOrWhiteSpace(desc)) sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", desc))); // Remove last newline if (sb.Length > 0) sb.Remove(sb.Length - 1, 1); return sb.ToString(); } public void UpdateCategories() { // Get categories from recipes var currentCategories = new List>(); foreach (var recipeId in Recipes) { var recipe = _prototypeManager.Index(recipeId); if (recipe.Categories.Count <= 0) continue; foreach (var category in recipe.Categories) { if (currentCategories.Contains(category)) continue; currentCategories.Add(category); } } if (Categories != null && (Categories.Count == currentCategories.Count || !Categories.All(currentCategories.Contains))) return; Categories = currentCategories; var sortedCategories = currentCategories .Select(p => _prototypeManager.Index(p)) .OrderBy(p => Loc.GetString(p.Name)) .ToList(); FilterOption.Clear(); FilterOption.AddItem(Loc.GetString("lathe-menu-category-all"), -1); foreach (var category in sortedCategories) { FilterOption.AddItem(Loc.GetString(category.Name), Categories.IndexOf(category.ID)); } FilterOption.SelectId(-1); } /// /// Populates the build queue list with all queued items /// /// public void PopulateQueueList(IReadOnlyCollection queue) { // Get the existing list of queue controls var oldChildCount = QueueList.ChildCount; var idx = 0; foreach (var batch in queue) { var recipe = _prototypeManager.Index(batch.Recipe); var itemName = _lathe.GetRecipeName(batch.Recipe); string displayText; if (batch.ItemsRequested > 1) displayText = Loc.GetString("lathe-menu-item-batch", ("index", idx + 1), ("name", itemName), ("printed", batch.ItemsPrinted), ("total", batch.ItemsRequested)); else displayText = Loc.GetString("lathe-menu-item-single", ("index", idx + 1), ("name", itemName)); if (idx >= oldChildCount) { var queuedRecipeBox = new QueuedRecipeControl(displayText, idx, GetRecipeDisplayControl(recipe)); queuedRecipeBox.OnDeletePressed += s => QueueDeleteAction?.Invoke(s); queuedRecipeBox.OnMoveUpPressed += s => QueueMoveUpAction?.Invoke(s); queuedRecipeBox.OnMoveDownPressed += s => QueueMoveDownAction?.Invoke(s); QueueList.AddChild(queuedRecipeBox); } else { var child = QueueList.GetChild(idx) as QueuedRecipeControl; if (child == null) { DebugTools.Assert($"Lathe menu queued recipe control at {idx} is not of type QueuedRecipeControl"); // Something's gone terribly wrong. continue; } child.SetDisplayText(displayText); child.SetIndex(idx); child.SetDisplayControl(GetRecipeDisplayControl(recipe)); } idx++; } // Shrink list if new list is shorter than old list. for (var childIdx = oldChildCount - 1; idx <= childIdx; childIdx--) { QueueList.RemoveChild(childIdx); } } public void SetQueueInfo(ProtoId? recipeProto) { FabricatingContainer.Visible = recipeProto != null; if (recipeProto == null) return; var recipe = _prototypeManager.Index(recipeProto.Value); FabricatingDisplayContainer.Children.Clear(); FabricatingDisplayContainer.AddChild(GetRecipeDisplayControl(recipe)); NameLabel.Text = _lathe.GetRecipeName(recipe); } public Control GetRecipeDisplayControl(LatheRecipePrototype recipe) { if (recipe.Icon != null) { var textRect = new TextureRect(); textRect.Texture = _spriteSystem.Frame0(recipe.Icon); return textRect; } if (recipe.Result is { } result) { var entProtoView = new EntityPrototypeView(); entProtoView.SetPrototype(result); return entProtoView; } return new Control(); } private void OnItemSelected(OptionButton.ItemSelectedEventArgs obj) { FilterOption.SelectId(obj.Id); if (obj.Id == -1) { CurrentCategory = null; } else { CurrentCategory = Categories?[obj.Id]; } PopulateRecipes(); } }