3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
6 using System.Collections.Generic;
7 using System.Globalization;
14 private GUIListBox itemList;
16 private GUIFrame selectedItemFrame;
17 private GUIFrame selectedItemReqsFrame;
19 private GUITextBlock amountTextMax;
20 private GUIScrollBar amountInput;
24 get {
return activateButton; }
31 private GUIComponent inputInventoryHolder, outputInventoryHolder;
33 private readonly List<GUIButton> itemCategoryButtons =
new List<GUIButton>();
38 get {
return selectedItem; }
58 private ToolTip tooltip;
85 TextAlignment = Alignment.
Center,
86 AutoScaleVertical =
true
91 RelativeSpacing = 0.01f,
96 List<MapEntityCategory> itemCategories = Enum.GetValues<
MapEntityCategory>().ToList();
98 itemCategories.RemoveAll(c => fabricationRecipes.None(f => f.Value?.TargetItem is
ItemPrefab ti && ti.
Category.HasFlag(c)));
99 itemCategoryButtons.Clear();
102 if (itemCategories.Count > 2)
107 RelativeSpacing = 0.01f
110 int buttonSize = Math.Min(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Height / itemCategories.Count);
112 var categoryButton =
new GUIButton(
new RectTransform(
new Point(buttonSize), categoryButtonContainer.RectTransform), style:
"CategoryButton.All")
114 ToolTip = TextManager.Get(
"MapEntityCategory.All"),
115 OnClicked = OnClickedCategoryButton
117 itemCategoryButtons.Add(categoryButton);
120 categoryButton =
new GUIButton(
new RectTransform(
new Point(buttonSize), categoryButtonContainer.RectTransform),
121 style:
"CategoryButton." + category)
123 ToolTip = TextManager.Get(
"MapEntityCategory." + category),
125 OnClicked = OnClickedCategoryButton
127 itemCategoryButtons.Add(categoryButton);
129 bool OnClickedCategoryButton(
GUIButton button,
object userData)
132 if (newCategory.HasValue) { itemFilterBox.
Text =
""; }
133 selectedItemCategory = newCategory;
134 FilterEntities(newCategory, itemFilterBox.
Text);
137 foreach (var btn
in itemCategoryButtons)
139 btn.RectTransform.SizeChanged += () =>
141 if (btn.Frame.sprites ==
null || !btn.Frame.sprites.TryGetValue(
GUIComponent.
ComponentState.None, out var spriteList)) {
return; }
142 var sprite = spriteList?.First();
143 if (sprite ==
null) {
return; }
144 btn.RectTransform.NonScaledSize =
new Point(btn.Rect.Width, (
int)(btn.Rect.Width * ((
float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
151 RelativeSpacing = 0.02f,
157 var topFrame =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.65f), mainFrame.RectTransform), style:
"InnerFrameDark");
164 RelativeSpacing = 0.03f
166 var filterArea =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal:
true)
169 RelativeSpacing = 0.03f,
170 UserData =
"filterarea"
172 new GUITextBlock(
new RectTransform(
new Vector2(0.2f, 1f), filterArea.RectTransform), TextManager.Get(
"serverlog.filter"), font: GUIStyle.SubHeadingFont)
174 Padding = Vector4.Zero,
175 AutoScaleVertical =
true
177 itemFilterBox =
new GUITextBox(
new RectTransform(
new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton:
true)
183 FilterEntities(selectedItemCategory, text);
186 filterArea.RectTransform.MaxSize =
new Point(
int.MaxValue, itemFilterBox.
Rect.Height);
188 itemList =
new GUIListBox(
new RectTransform(
new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style:
null)
190 PlaySoundOnSelect =
true,
191 OnSelected = (component, userdata) =>
207 outputSlot =
new GUIFrame(
new RectTransform(
new Vector2(0.4f, 1f), outputTopArea.RectTransform), style:
null);
211 selectedItemFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.6f, 1f), outputTopArea.RectTransform), style:
null);
213 selectedItemReqsFrame =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.5f), paddedOutputArea.RectTransform), style:
null);
216 var bottomFrame =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.3f), mainFrame.RectTransform), style:
null);
224 RelativeSpacing = 0.03f
226 var inputLabel =
new GUITextBlock(
new RectTransform(Vector2.One, separatorArea.RectTransform), TextManager.Get(
"fabricator.input",
"uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
227 inputLabel.
RectTransform.
Resize(
new Point((
int)inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
234 inputInventoryHolder =
new GUIFrame(
new RectTransform(
new Vector2(0.7f, 1f), inputArea.RectTransform), style:
null);
241 RelativeSpacing = 0.05f
244 var amountInputHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.4f), buttonFrame.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
249 new GUITextBlock(
new RectTransform(
new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform),
"1", textAlignment: Alignment.Center);
251 amountInput =
new GUIScrollBar(
new RectTransform(
new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style:
"GUISlider")
255 scrollBar.
Step = 1.0f / Math.Max(scrollBar.
Range.Y - 1, 1);
257 RefreshActivateButtonText();
260 pendingFabricatedItem =
null;
261 item.CreateClientEvent(
this);
267 amountTextMax =
new GUITextBlock(
new RectTransform(
new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform),
"1", textAlignment: Alignment.Center);
272 OnClicked = StartButtonClicked,
273 UserData = selectedItem,
282 bottomFrame.RectTransform.RelativeSize =
new Vector2(1.0f, 0.1f);
286 OnClicked = StartButtonClicked,
287 UserData = selectedItem,
293 TextManager.Get(
"FabricatorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style:
"OuterGlow", wrap:
true)
295 HoverColor = Color.Black,
296 IgnoreLayoutGroups =
true,
303 private void RefreshActivateButtonText()
305 if (amountInput ==
null)
311 activateButton.
Text =
313 $
"{TextManager.Get("FabricatorCancel
")} ({amountRemaining})" :
314 $
"{TextManager.Get(CreateButtonText)} ({AmountToFabricate})";
318 partial
void CreateRecipes()
320 itemList.Content.RectTransform.ClearChildren();
322 foreach (FabricationRecipe fi
in fabricationRecipes.Values)
324 var frame =
new GUIFrame(
new RectTransform(
new Point(itemList.Content.Rect.Width, (
int)(40 * GUI.yScale)), itemList.Content.RectTransform), style:
null)
327 HoverColor = Color.Gold * 0.2f,
328 SelectedColor = Color.Gold * 0.5f,
329 ToolTip = RichString.Rich(fi.TargetItem.Description)
332 var container =
new GUILayoutGroup(
new RectTransform(Vector2.One, frame.RectTransform),
333 childAnchor:
Anchor.CenterLeft, isHorizontal:
true) { RelativeSpacing = 0.02f };
335 var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.Sprite;
336 if (itemIcon !=
null)
338 new GUIImage(
new RectTransform(
new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
339 itemIcon, scaleToFit:
true)
341 Color = fi.TargetItem.InventoryIconColor,
342 ToolTip = RichString.Rich(fi.TargetItem.Description)
346 new GUITextBlock(
new RectTransform(
new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
348 Padding = Vector4.Zero,
349 AutoScaleVertical =
true,
350 ToolTip = RichString.Rich(fi.TargetItem.Description)
353 new GUITextBlock(
new RectTransform(
new Vector2(0.85f, 1f), frame.RectTransform,
Anchor.BottomRight),
362 private void InitInventoryUIs()
364 if (inputInventoryHolder !=
null)
375 private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
377 if (fabricationRecipe ==
null) {
return ""; }
378 if (fabricationRecipe.Amount > 1)
380 return TextManager.GetWithVariables(
"fabricationrecipenamewithamount",
381 (
"[name]", fabricationRecipe.DisplayName), (
"[amount]", fabricationRecipe.Amount.ToString()));
385 return fabricationRecipe.DisplayName;
389 partial
void OnItemLoadedProjSpecific()
395 partial
void SelectProjSpecific(Character character)
397 if (character !=
Character.Controlled) {
return; }
399 var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
400 nonItems.ForEach(i => itemList.Content.RemoveChild(i));
402 itemList.Content.RectTransform.SortChildren((c1, c2) =>
404 var item1 = c1.GUIComponent.UserData as FabricationRecipe;
405 var item2 = c2.GUIComponent.UserData as FabricationRecipe;
407 int itemPlacement1 = calculatePlacement(item1);
408 int itemPlacement2 = calculatePlacement(item2);
409 if (itemPlacement1 != itemPlacement2)
411 return itemPlacement1 > itemPlacement2 ? -1 : 1;
414 int calculatePlacement(FabricationRecipe recipe)
416 if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
424 return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
427 var sufficientSkillsText =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
428 TextManager.Get(
"fabricatorsufficientskills"), textColor: GUIStyle.Green, font: GUIStyle.SubHeadingFont)
430 AutoScaleHorizontal =
true,
433 sufficientSkillsText.RectTransform.SetAsFirstChild();
435 var insufficientSkillsText =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
436 TextManager.Get(
"fabricatorinsufficientskills"), textColor: Color.Orange, font: GUIStyle.SubHeadingFont)
438 AutoScaleHorizontal =
true,
441 var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem &&
FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
442 if (firstinSufficient !=
null)
444 insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
448 sufficientSkillsText.Visible = insufficientSkillsText.Visible =
false;
449 sufficientSkillsText.Enabled = insufficientSkillsText.Enabled =
false;
452 var requiresRecipeText =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
453 TextManager.Get(
"fabricatorrequiresrecipe"), textColor: Color.Red, font: GUIStyle.SubHeadingFont)
455 AutoScaleHorizontal =
true,
458 var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c =>
459 c.UserData is FabricationRecipe fabricableItem &&
460 fabricableItem.RequiresRecipe && !AnyOneHasRecipeForItem(character, fabricableItem.TargetItem));
461 if (firstRequiresRecipe !=
null)
463 requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
466 FilterEntities(selectedItemCategory, itemFilterBox?.Text ??
string.Empty);
467 HideEmptyItemListCategories();
470 private readonly Dictionary<FabricationRecipe.RequiredItem,
int> missingIngredientCounts =
new Dictionary<FabricationRecipe.RequiredItem,
int>();
471 private float ingredientHighlightTimer;
473 private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
475 overlayComponent.RectTransform.SetAsLastChild();
477 missingIngredientCounts.Clear();
479 FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
480 if (targetItem !=
null)
482 foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
484 if (missingIngredientCounts.ContainsKey(requiredItem))
486 missingIngredientCounts[requiredItem] += requiredItem.
Amount;
490 missingIngredientCounts[requiredItem] = requiredItem.Amount;
495 var missingIngredient = missingIngredientCounts.Keys.FirstOrDefault(mi => mi.MatchesItem(
item));
496 if (missingIngredient ==
null) {
continue; }
498 if (missingIngredientCounts[missingIngredient] == 1)
500 missingIngredientCounts.Remove(missingIngredient);
504 missingIngredientCounts[missingIngredient]--;
508 if (ingredientHighlightTimer <= 0.0f)
511 foreach (var inventory
in linkedInventories)
513 if (inventory.visualSlots ==
null) {
continue; }
514 for (
int i = 0; i < inventory.Capacity; i++)
516 if (inventory.visualSlots[i].HighlightTimer > 0.0f) {
continue; }
517 var availableItem = inventory.GetItemAt(i);
518 if (availableItem ==
null) {
continue; }
520 if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableItem)))
522 inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
525 if (availableItem.OwnInventory !=
null)
527 for (
int j = 0; j < availableItem.OwnInventory.Capacity; j++)
529 var availableContainedItem = availableItem.OwnInventory.GetItemAt(i);
530 if (availableContainedItem ==
null) {
continue; }
531 if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableContainedItem)))
533 inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
540 ingredientHighlightTimer = 1.0f;
544 foreach (var kvp
in missingIngredientCounts)
548 var requiredItem = kvp.Key;
549 int missingCount = kvp.Value;
556 if (slotIndex >= inputContainer.
Capacity) {
break; }
558 if (slotIndex < inputContainer.
Capacity &&
560 availableIngredients.Any(i => i.Value.Any() && requiredItem.MatchesItem(i.Value.First())))
567 var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
569 float iconAlpha = 0.0f;
570 ItemPrefab requiredItemToDisplay = requiredItem.DefaultItem.IsEmpty ? null : requiredItem.ItemPrefabs.FirstOrDefault(p => p.Identifier == requiredItem.DefaultItem);
571 if (requiredItemToDisplay ==
null && requiredItem.ItemPrefabs.Multiple())
573 float iconCycleSpeed = 0.75f;
574 float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
575 int iconIndex = (
int)(iconCycleT % requiredItem.ItemPrefabs.Count());
577 requiredItemToDisplay = requiredItem.ItemPrefabs.Skip(iconIndex).FirstOrDefault();
578 iconAlpha = Math.Min(Math.Abs(MathF.Sin(iconCycleT * MathHelper.Pi)) * 2.0f, 1.0f);
582 requiredItemToDisplay ??= requiredItem.ItemPrefabs.FirstOrDefault();
585 if (iconAlpha > 0.0f)
587 var itemIcon = requiredItemToDisplay.InventoryIcon ?? requiredItemToDisplay.Sprite;
590 slotRect.Center.ToVector2(),
591 color: requiredItemToDisplay.InventoryIconColor * 0.3f * iconAlpha,
592 scale: Math.Min(slotRect.Width * 0.9f / itemIcon.size.X, slotRect.Height * 0.9f / itemIcon.size.Y));
595 if (missingCount > 1)
597 Vector2 stackCountPos =
new Vector2(slotRect.Right, slotRect.Bottom);
598 string stackCountText =
"x" + missingCount;
599 stackCountPos -= GUIStyle.SmallFont.MeasureString(stackCountText) +
new Vector2(4, 2);
600 GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black);
601 GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
604 if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
606 DrawConditionBar(spriteBatch, requiredItem.MinCondition);
608 else if (requiredItem.MaxCondition < 1.0f)
610 DrawConditionBar(spriteBatch, requiredItem.MaxCondition);
613 void DrawConditionBar(SpriteBatch sb,
float condition)
615 int spacing = GUI.IntScale(4);
616 int height = GUI.IntScale(10);
617 GUI.DrawRectangle(spriteBatch,
new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, slotRect.Width - spacing * 2, height), Color.Black * 0.8f,
true);
618 GUI.DrawRectangle(spriteBatch,
619 new Rectangle(slotRect.X + spacing, slotRect.Bottom - spacing - height, (
int)((slotRect.Width - spacing * 2) * condition), height),
620 GUIStyle.Green * 0.8f,
true);
623 if (slotRect.Contains(PlayerInput.MousePosition))
625 LocalizedString toolTipText = requiredItem.OverrideHeader;
626 if (requiredItem.OverrideHeader.IsNullOrEmpty())
628 var suitableIngredients = requiredItem.ItemPrefabs.Where(ip => !ip.HideInMenus).OrderBy(ip => ip.DefaultPrice?.Price ?? 0).Select(ip => ip.Name).Distinct();
629 toolTipText = GetSuitableIngredientText(suitableIngredients);
631 if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
633 toolTipText +=
" " + (int)Math.Round(requiredItem.MinCondition * 100) +
"%";
635 else if (requiredItem.MaxCondition < 1.0f)
637 if (requiredItem.MaxCondition <= 0.0f)
639 toolTipText +=
" " + (int)Math.Round(requiredItem.MaxCondition * 100) +
"%";
643 toolTipText +=
" 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) +
"%";
646 else if (requiredItem.MaxCondition <= 0.0f)
648 toolTipText = TextManager.GetWithVariable(
"displayname.emptyitem",
"[itemname]", toolTipText);
651 toolTipText = $
"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
652 if (!requiredItem.OverrideDescription.IsNullOrEmpty())
654 toolTipText +=
'\n' + requiredItem.OverrideDescription;
656 else if (!requiredItemPrefab.Description.IsNullOrEmpty())
658 toolTipText +=
'\n' + requiredItemPrefab.Description;
660 tooltip =
new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
668 private LocalizedString GetSuitableIngredientText(IEnumerable<LocalizedString> itemNameList)
670 int count = itemNameList.Count();
677 return itemNameList.First();
682 return TextManager.GetWithVariables(
683 "DialogRequiredTreatmentOptionsLast",
684 (
"[treatment1]", itemNameList.ElementAt(0)),
685 (
"[treatment2]", itemNameList.ElementAt(1)));
690 LocalizedString itemListStr = TextManager.GetWithVariables(
691 "DialogRequiredTreatmentOptionsFirst",
692 (
"[treatment1]", itemNameList.ElementAt(0)),
693 (
"[treatment2]", itemNameList.ElementAt(1)));
696 bool isTruncated =
false;
697 for (i = 2; i < count - 1; i++)
699 if (itemListStr.Length > 50)
704 itemListStr = TextManager.GetWithVariables(
705 "DialogRequiredTreatmentOptionsFirst",
706 (
"[treatment1]", itemListStr),
707 (
"[treatment2]", itemNameList.ElementAt(i)));
709 itemListStr = TextManager.GetWithVariables(
710 "DialogRequiredTreatmentOptionsLast",
711 (
"[treatment1]", itemListStr),
712 (
"[treatment2]", itemNameList.ElementAt(i)));
716 itemListStr += TextManager.Get(
"ellipsis");
722 private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
724 overlayComponent.RectTransform.SetAsLastChild();
726 FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
730 if (fabricatedItem !=
null)
732 float clampedProgressState = Math.Clamp(progressState, 0f, 1f);
733 GUI.DrawRectangle(spriteBatch,
735 slotRect.X, slotRect.Y + (
int)(slotRect.Height * (1.0f - clampedProgressState)),
736 slotRect.Width, (
int)(slotRect.Height * clampedProgressState)),
737 GUIStyle.Green * 0.5f, isFilled:
true);
742 var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.Sprite;
745 slotRect.Center.ToVector2(),
746 color: targetItem.TargetItem.InventoryIconColor * 0.4f,
747 scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
753 GUIComponent.DrawToolTip(spriteBatch, RichString.Rich(tooltip.Tooltip), tooltip.TargetElement);
760 foreach (GUIComponent child
in itemList.Content.Children)
762 FabricationRecipe recipe = child.UserData as FabricationRecipe;
763 if (recipe?.DisplayName ==
null) {
continue; }
765 if (recipe.HideForNonTraitors)
767 if (
Character.Controlled is not { IsTraitor: true })
769 child.Visible =
false;
775 (
string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
776 (!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
779 foreach (GUIButton btn
in itemCategoryButtons)
783 HideEmptyItemListCategories();
788 private void HideEmptyItemListCategories()
790 bool visibleElementsChanged =
false;
792 bool recipeVisible =
false;
793 foreach (GUIComponent child
in itemList.Content.Children.Reverse())
795 if (child.UserData is not FabricationRecipe recipe)
799 if (child.Visible != recipeVisible)
801 child.Visible = recipeVisible;
802 visibleElementsChanged =
true;
805 recipeVisible =
false;
809 recipeVisible |= child.Visible;
813 if (visibleElementsChanged)
815 itemList.UpdateScrollBarSize();
816 itemList.BarScroll = 0.0f;
822 FilterEntities(selectedItemCategory,
"");
823 itemList.UpdateScrollBarSize();
824 itemList.BarScroll = 0.0f;
825 itemFilterBox.
Text =
"";
830 private Option<SelectedRecipe> LastSelectedRecipe = Option.None;
834 this.selectedItem = selectedItem;
835 displayingForCharacter = user;
836 var selectedRecipe =
new SelectedRecipe(user, selectedItem, overrideRequiredTime is
null ? Option.None : Option.Some(overrideRequiredTime.Value));
837 LastSelectedRecipe = Option.Some(selectedRecipe);
838 CreateSelectedItemUI(selectedRecipe);
842 private void CreateSelectedItemUI(SelectedRecipe recipe)
844 var (user, selectedItem, overrideRequiredTime) = recipe;
847 if (amountInput !=
null)
849 float prevBarScroll = amountInput.BarScroll;
850 amountInput.Range =
new Vector2(1, max);
851 amountInput.BarScroll = prevBarScroll;
853 amountTextMax.Text = max.ToString();
854 amountInput.Enabled = amountTextMax.Enabled = max > 1;
857 RefreshActivateButtonText();
859 selectedItemFrame.ClearChildren();
860 selectedItemReqsFrame.ClearChildren();
863 var paddedReqFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform,
Anchor.Center)) { RelativeSpacing = 0.03f };
865 LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
866 LocalizedString
name = itemName;
868 QualityResult result = GetFabricatedItemQuality(selectedItem, user);
870 float quality = selectedItem.
Quality ?? result.Quality;
871 if (quality > 0 || result.HasRandomQualityRollChance)
873 name = TextManager.GetWithVariable(
"itemname.quality" + (
int)quality,
"[itemname]", itemName +
'\n')
874 .Fallback(TextManager.GetWithVariable(
"itemname.quality3",
"[itemname]", itemName +
'\n'));
877 var nameBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
878 RichString.Rich(
name), textAlignment: Alignment.TopLeft, textColor: Color.Aqua, font: GUIStyle.SubHeadingFont)
880 AutoScaleHorizontal =
true
883 if (result.HasRandomQualityRollChance)
885 var iconLayout =
new GUIFrame(
new RectTransform(
new Vector2(0.4f, 1f), selectedItemFrame.RectTransform, anchor:
Anchor.TopRight), style:
null);
886 var icon = GameSession.CreateNotificationIcon(iconLayout, offset:
true);
888 float percentage1 = result.TotalPlusOnePercentage;
889 float percentage2 = result.TotalPlusTwoPercentage;
891 string chance1text = percentage1.ToString(
"F1", CultureInfo.InvariantCulture);
892 string chance2text = percentage2.ToString(
"F1", CultureInfo.InvariantCulture);
894 int quality1 = Math.Clamp(result.Quality + 1, min: 0, max: 3);
895 int quality2 = Math.Clamp(result.Quality + 2, min: 0, max: 3);
897 LocalizedString quality1Text = TextManager.Get($
"quality{quality1}");
898 LocalizedString quality2Text = TextManager.Get($
"quality{quality2}");
900 string localizationTag = percentage2 > 0f && percentage1 > 0 && quality1 != quality2 ?
"meetsbonusrequirementtwice" :
"meetsbonusrequirement";
902 var variables =
new (
string Key, LocalizedString Value)[]
904 (
"[chance]", chance1text), (
"[quality]", quality1Text),
905 (
"[chance2]", chance2text), (
"[quality2]", quality2Text)
908 if (MathUtils.NearlyEqual(percentage1, 0))
910 variables =
new[] { (
"[chance]", chance2text), (
"[quality]", quality2Text) };
913 if (quality1 == quality2)
915 LocalizedString rawPercentage = result.PlusOnePercentage.ToString(
"F1", CultureInfo.InvariantCulture);
916 variables =
new[] { (
"[chance]", rawPercentage), (
"[quality]", quality1Text) };
919 LocalizedString qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
921 icon.ToolTip = RichString.Rich(qualityTooltip);
922 icon.Visible = icon.CanBeFocused =
true;
925 nameBlock.Padding =
new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
926 if (nameBlock.TextScale < 0.7f)
928 nameBlock.AutoScaleHorizontal =
false;
929 nameBlock.TextScale = 0.7f;
930 nameBlock.Wrap =
true;
931 nameBlock.SetTextPos();
932 nameBlock.RectTransform.MinSize =
new Point(0, (
int)(nameBlock.TextSize.Y * nameBlock.TextScale));
937 var description =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
939 font: GUIStyle.SmallFont, wrap:
true);
940 description.Padding =
new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
942 while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
944 var lines = description.WrappedText.Split(
'\n');
945 if (lines.Count <= 1) {
break; }
946 var newString =
string.Join(
'\n', lines.Take(lines.Count - 1));
947 description.Text = newString.Substring(0, newString.Length - 4) +
"...";
948 description.CalculateHeightFromText();
953 IEnumerable<Skill> inadequateSkills = Enumerable.Empty<Skill>();
961 LocalizedString text =
"";
962 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
963 TextManager.Get(
"FabricatorRequiredSkills"), textColor: inadequateSkills.Any() ? GUIStyle.Red : GUIStyle.Green, font: GUIStyle.SubHeadingFont)
965 AutoScaleHorizontal =
true,
966 ToolTip = TextManager.Get(
"fabricatorrequiredskills.tooltip")
970 text += TextManager.Get(
"SkillName." + skill.Identifier) +
" " + TextManager.Get(
"Lvl").ToLower() +
" " + Math.Round(skill.Level *
SkillRequirementMultiplier);
971 if (skill != selectedItem.
RequiredSkills.Last()) { text +=
"\n"; }
973 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUIStyle.SmallFont);
977 if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
979 float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
981 : (user ==
null ? selectedItem.
RequiredTime : GetRequiredTime(selectedItem, user));
983 if ((
int)requiredTime > 0)
985 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
986 TextManager.Get(
"FabricatorRequiredTime") , textColor: ToolBox.GradientLerp(degreeOfSuccess, GUIStyle.Red, Color.Yellow, GUIStyle.Green), font: GUIStyle.SubHeadingFont)
988 AutoScaleHorizontal =
true,
990 requiredTimeBlock =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime),
991 font: GUIStyle.SmallFont);
996 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
997 TextManager.Get(
"subeditor.price"), textColor: ToolBox.GradientLerp(degreeOfSuccess, GUIStyle.Red, Color.Yellow, GUIStyle.Green), font: GUIStyle.SubHeadingFont)
999 AutoScaleHorizontal =
true,
1001 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), TextManager.FormatCurrency(
SelectedItem.
RequiredMoney),
1002 font: GUIStyle.SmallFont);
1012 if (recipe?.DisplayName ==
null) {
continue; }
1016 child.
Flash(color, 1.5f,
false);
1020 var grandChild = child.
GetChild(i);
1022 grandChild.
Flash(color, 1.5f,
false);
1030 private bool StartButtonClicked(
GUIButton button,
object obj)
1032 if (selectedItem ==
null) {
return false; }
1033 if (fabricatedItem ==
null &&
1036 outputSlot.
Flash(GUIStyle.Red);
1044 pendingFabricatedItem = fabricatedItem !=
null ? null : selectedItem;
1045 item.CreateClientEvent(
this);
1049 if (fabricatedItem ==
null)
1051 StartFabricating(selectedItem, Character.Controlled);
1055 CancelFabricating(
Character.Controlled);
1064 activateButton.
Enabled =
false;
1067 ingredientHighlightTimer -= deltaTime;
1071 if (selectedItem !=
null && displayingForCharacter != character)
1074 SelectItem(character, selectedItem);
1078 if (refreshIngredientsTimer <= 0.0f)
1080 RefreshAvailableIngredients();
1081 refreshIngredientsTimer = RefreshIngredientsInterval;
1083 refreshIngredientsTimer -= deltaTime;
1086 if (character !=
null)
1092 if (recipe != selectedItem &&
1093 (child.
Rect.Y > itemList.Rect.Bottom || child.
Rect.Bottom < itemList.Rect.Y))
1098 bool canBeFabricated = CanBeFabricated(recipe, availableIngredients, character);
1099 if (recipe == selectedItem)
1101 activateButton.
Enabled = canBeFabricated;
1105 childContainer.
GetChild<
GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
1106 childContainer.
GetChild<
GUIImage>().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
1109 limitReachedText.
Visible = !canBeFabricated && fabricationLimits.TryGetValue(recipe.RecipeHash, out
int amount) && amount <= 0;
1119 if (!LastSelectedRecipe.TryUnwrap(out var lastSelected)) {
return; }
1120 CreateSelectedItemUI(lastSelected);
1123 partial
void UpdateRequiredTimeProjSpecific()
1125 if (requiredTimeBlock ==
null) {
return; }
1126 requiredTimeBlock.
Text = ToolBox.SecondsToReadableTime(timeUntilReady > 0.0f ? timeUntilReady : requiredTime);
1131 uint recipeHash = pendingFabricatedItem?.
RecipeHash ?? 0;
1138 FabricatorState newState = (FabricatorState)msg.
ReadByte();
1147 for (
int i = 0; i < reachedLimitCount; i++)
1156 this.amountToFabricate = amountToFabricate;
1158 this.amountRemaining = amountRemaining;
1159 if (newState == FabricatorState.Stopped || recipeHash == 0)
1161 CancelFabricating();
1163 else if (newState == FabricatorState.Active || newState == FabricatorState.Paused)
1166 if (fabricatedItem !=
null && fabricatedItem.
RecipeHash == recipeHash) {
return; }
1167 if (recipeHash == 0) {
return; }
1169 SelectItem(user, fabricationRecipes[recipeHash]);
1170 StartFabricating(fabricationRecipes[recipeHash], user);
1172 timeUntilReady = newTimeUntilReady;
float GetSkillLevel(string skillIdentifier)
static Character? Controlled
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
readonly float OutCondition
readonly int RequiredMoney
readonly ImmutableArray< Skill > RequiredSkills
readonly float RequiredTime
GUIComponent GetChild(int index)
virtual void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectInflate=null)
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIComponent that can be used to render custom content on the UI
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
bool DrawWhenEquipped
Normally false - we don't draw the UI because it's drawn when the player hovers the cursor over the i...
RectTransform RectTransform
If set, the inventory is automatically positioned inside the rect
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
bool CanBePut(Item item)
Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be p...
int GetMaxStackSize(Inventory inventory)
override LocalizedString Name
override MapEntityCategory Category
Identifier SelectedItemIdentifier
FabricationRecipe SelectedItem
void RefreshSelectedItem()
void HighlightRecipe(string identifier, Color color)
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
void ClientEventRead(IReadMessage msg, float sendingTime)
string FabricationLimitReachedText
float SkillRequirementMultiplier
override bool RecreateGUIOnResolutionChange
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
override void OnPlayerSkillsChanged()
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
override void OnResolutionChanged()
float FabricationDegreeOfSuccess(Character character, ImmutableArray< Skill > skills)
readonly record struct QualityResult(int Quality, bool HasRandomQuality, float PlusOnePercentage, float PlusTwoPercentage)
readonly ItemInventory Inventory
LocalizedString Description
readonly Identifier Identifier
void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount=0.5f)
Interface for entities that the clients can send events to the server
int ReadRangedInteger(int min, int max)
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)
void WriteUInt32(UInt32 val)