Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 
11 {
12  partial class Fabricator : Powered, IServerSerializable, IClientSerializable
13  {
14  private GUIListBox itemList;
15 
16  private GUIFrame selectedItemFrame;
17  private GUIFrame selectedItemReqsFrame;
18 
19  private GUITextBlock amountTextMax;
20  private GUIScrollBar amountInput;
21 
23  {
24  get { return activateButton; }
25  }
26  private GUIButton activateButton;
27 
28  private GUITextBox itemFilterBox;
29 
30  private GUIComponent outputSlot;
31  private GUIComponent inputInventoryHolder, outputInventoryHolder;
32 
33  private readonly List<GUIButton> itemCategoryButtons = new List<GUIButton>();
34  private MapEntityCategory? selectedItemCategory;
35 
37  {
38  get { return selectedItem; }
39  }
40  private FabricationRecipe selectedItem;
41 
45  private Character displayingForCharacter;
46 
47  public Identifier SelectedItemIdentifier => SelectedItem?.TargetItem.Identifier ?? Identifier.Empty;
48 
49  private GUIComponent inSufficientPowerWarning;
50 
51  private FabricationRecipe pendingFabricatedItem;
52 
53  private class ToolTip
54  {
55  public Rectangle TargetElement;
56  public LocalizedString Tooltip;
57  }
58  private ToolTip tooltip;
59 
60  private GUITextBlock requiredTimeBlock;
61 
62  [Serialize("FabricatorCreate", IsPropertySaveable.Yes)]
63  public string CreateButtonText { get; set; }
64 
65  [Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
66  public string FabricationLimitReachedText { get; set; }
67 
68  public override bool RecreateGUIOnResolutionChange => true;
69 
70  protected override void OnResolutionChanged()
71  {
72  if (GuiFrame != null)
73  {
74  InitInventoryUIs();
75  }
76  }
77 
78  protected override void CreateGUI()
79  {
80  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
81 
82  // === LABEL === //
83  new GUITextBlock(new RectTransform(new Vector2(1f, 0.05f), paddedFrame.RectTransform), item.Prefab.Name, font: GUIStyle.SubHeadingFont)
84  {
85  TextAlignment = Alignment.Center,
86  AutoScaleVertical = true
87  };
88 
89  var innerArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.95f), paddedFrame.RectTransform, Anchor.Center), isHorizontal: true)
90  {
91  RelativeSpacing = 0.01f,
92  Stretch = true,
93  CanBeFocused = true
94  };
95 
96  List<MapEntityCategory> itemCategories = Enum.GetValues<MapEntityCategory>().ToList();
97  itemCategories.Remove(MapEntityCategory.None);
98  itemCategories.RemoveAll(c => fabricationRecipes.None(f => f.Value?.TargetItem is ItemPrefab ti && ti.Category.HasFlag(c)));
99  itemCategoryButtons.Clear();
100 
101  //only create category buttons if there's more than one category in addition to "All"
102  if (itemCategories.Count > 2)
103  {
104  // === Item category buttons ===
105  var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.05f, 1.0f), innerArea.RectTransform))
106  {
107  RelativeSpacing = 0.01f
108  };
109 
110  int buttonSize = Math.Min(categoryButtonContainer.Rect.Width, categoryButtonContainer.Rect.Height / itemCategories.Count);
111 
112  var categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform), style: "CategoryButton.All")
113  {
114  ToolTip = TextManager.Get("MapEntityCategory.All"),
115  OnClicked = OnClickedCategoryButton
116  };
117  itemCategoryButtons.Add(categoryButton);
118  foreach (MapEntityCategory category in itemCategories)
119  {
120  categoryButton = new GUIButton(new RectTransform(new Point(buttonSize), categoryButtonContainer.RectTransform),
121  style: "CategoryButton." + category)
122  {
123  ToolTip = TextManager.Get("MapEntityCategory." + category),
124  UserData = category,
125  OnClicked = OnClickedCategoryButton
126  };
127  itemCategoryButtons.Add(categoryButton);
128  }
129  bool OnClickedCategoryButton(GUIButton button, object userData)
130  {
131  MapEntityCategory? newCategory = !button.Selected ? (MapEntityCategory?)userData : null;
132  if (newCategory.HasValue) { itemFilterBox.Text = ""; }
133  selectedItemCategory = newCategory;
134  FilterEntities(newCategory, itemFilterBox.Text);
135  return true;
136  }
137  foreach (var btn in itemCategoryButtons)
138  {
139  btn.RectTransform.SizeChanged += () =>
140  {
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)));
145  };
146  }
147  }
148 
149  var mainFrame = new GUILayoutGroup(new RectTransform(Vector2.One, innerArea.RectTransform), childAnchor: Anchor.TopCenter)
150  {
151  RelativeSpacing = 0.02f,
152  Stretch = true,
153  CanBeFocused = true
154  };
155 
156  // === TOP AREA ===
157  var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.65f), mainFrame.RectTransform), style: "InnerFrameDark");
158 
159  // === ITEM LIST ===
160  var itemListFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), topFrame.RectTransform), childAnchor: Anchor.Center);
161  var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), itemListFrame.RectTransform))
162  {
163  Stretch = true,
164  RelativeSpacing = 0.03f
165  };
166  var filterArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
167  {
168  Stretch = true,
169  RelativeSpacing = 0.03f,
170  UserData = "filterarea"
171  };
172  new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"), font: GUIStyle.SubHeadingFont)
173  {
174  Padding = Vector4.Zero,
175  AutoScaleVertical = true
176  };
177  itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true)
178  {
179  OverflowClip = true
180  };
181  itemFilterBox.OnTextChanged += (textBox, text) =>
182  {
183  FilterEntities(selectedItemCategory, text);
184  return true;
185  };
186  filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
187 
188  itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null)
189  {
190  PlaySoundOnSelect = true,
191  OnSelected = (component, userdata) =>
192  {
193  selectedItem = userdata as FabricationRecipe;
194  if (selectedItem != null) { SelectItem(Character.Controlled, selectedItem); }
195  return true;
196  }
197  };
198 
199  // === SEPARATOR === //
200  new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
201 
202  // === OUTPUT AREA === //
203  var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), topFrame.RectTransform, Anchor.TopRight), childAnchor: Anchor.Center);
204  var paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), outputArea.RectTransform));
205  var outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5F), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
206  // === OUTPUT SLOT === //
207  outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), outputTopArea.RectTransform), style: null);
208  outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
209  new GUICustomComponent(new RectTransform(Vector2.One, outputInventoryHolder.RectTransform), DrawOutputOverLay) { CanBeFocused = false };
210  // === DESCRIPTION === //
211  selectedItemFrame = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), outputTopArea.RectTransform), style: null);
212  // === REQUIREMENTS === //
213  selectedItemReqsFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform), style: null);
214 
215  // === BOTTOM AREA === //
216  var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), mainFrame.RectTransform), style: null);
217 
218  if (inputContainer.Capacity > 0)
219  {
220  // === SEPARATOR === //
221  var separatorArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
222  {
223  Stretch = true,
224  RelativeSpacing = 0.03f
225  };
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));
228  new GUIFrame(new RectTransform(Vector2.One, separatorArea.RectTransform), style: "HorizontalLine");
229 
230  // === INPUT AREA === //
231  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1f), bottomFrame.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft);
232 
233  // === INPUT SLOTS === //
234  inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
235  new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
236 
237  // === ACTIVATE BUTTON === //
238  var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.9f), inputArea.RectTransform))
239  {
240  Stretch = true,
241  RelativeSpacing = 0.05f
242  };
243 
244  var amountInputHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), buttonFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
245  {
246  Stretch = true
247  };
248 
249  new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
250 
251  amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider")
252  {
253  OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
254  {
255  scrollBar.Step = 1.0f / Math.Max(scrollBar.Range.Y - 1, 1);
256  AmountToFabricate = (int)MathF.Round(scrollBar.BarScrollValue);
257  RefreshActivateButtonText();
258  if (GameMain.Client != null)
259  {
260  pendingFabricatedItem = null;
261  item.CreateClientEvent(this);
262  }
263  return true;
264  }
265  };
266 
267  amountTextMax = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
268 
269  activateButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.6f), buttonFrame.RectTransform),
270  TextManager.Get(CreateButtonText), style: "DeviceButton")
271  {
272  OnClicked = StartButtonClicked,
273  UserData = selectedItem,
274  Enabled = false
275  };
276 
277  //spacing
278  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), buttonFrame.RectTransform), style: null);
279  }
280  else
281  {
282  bottomFrame.RectTransform.RelativeSize = new Vector2(1.0f, 0.1f);
283  activateButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), bottomFrame.RectTransform, Anchor.CenterRight),
284  TextManager.Get(CreateButtonText), style: "DeviceButtonFixedSize")
285  {
286  OnClicked = StartButtonClicked,
287  UserData = selectedItem,
288  Enabled = false
289  };
290  }
291  // === POWER WARNING === //
292  inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
293  TextManager.Get("FabricatorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
294  {
295  HoverColor = Color.Black,
296  IgnoreLayoutGroups = true,
297  Visible = false,
298  CanBeFocused = false
299  };
300  CreateRecipes();
301  }
302 
303  private void RefreshActivateButtonText()
304  {
305  if (amountInput == null)
306  {
307  activateButton.Text = TextManager.Get(IsActive ? "FabricatorCancel" : CreateButtonText);
308  }
309  else
310  {
311  activateButton.Text =
312  IsActive ?
313  $"{TextManager.Get("FabricatorCancel")} ({amountRemaining})" :
314  $"{TextManager.Get(CreateButtonText)} ({AmountToFabricate})";
315  }
316  }
317 
318  partial void CreateRecipes()
319  {
320  itemList.Content.RectTransform.ClearChildren();
321 
322  foreach (FabricationRecipe fi in fabricationRecipes.Values)
323  {
324  var frame = new GUIFrame(new RectTransform(new Point(itemList.Content.Rect.Width, (int)(40 * GUI.yScale)), itemList.Content.RectTransform), style: null)
325  {
326  UserData = fi,
327  HoverColor = Color.Gold * 0.2f,
328  SelectedColor = Color.Gold * 0.5f,
329  ToolTip = RichString.Rich(fi.TargetItem.Description)
330  };
331 
332  var container = new GUILayoutGroup(new RectTransform(Vector2.One, frame.RectTransform),
333  childAnchor: Anchor.CenterLeft, isHorizontal: true) { RelativeSpacing = 0.02f };
334 
335  var itemIcon = fi.TargetItem.InventoryIcon ?? fi.TargetItem.Sprite;
336  if (itemIcon != null)
337  {
338  new GUIImage(new RectTransform(new Point(frame.Rect.Height,frame.Rect.Height), container.RectTransform),
339  itemIcon, scaleToFit: true)
340  {
341  Color = fi.TargetItem.InventoryIconColor,
342  ToolTip = RichString.Rich(fi.TargetItem.Description)
343  };
344  }
345 
346  new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
347  {
348  Padding = Vector4.Zero,
349  AutoScaleVertical = true,
350  ToolTip = RichString.Rich(fi.TargetItem.Description)
351  };
352 
353  new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), frame.RectTransform, Anchor.BottomRight),
354  TextManager.Get(FabricationLimitReachedText), font: GUIStyle.SmallFont, textAlignment: Alignment.BottomRight)
355  {
356  UserData = nameof(FabricationLimitReachedText),
357  Visible = false
358  };
359  }
360  }
361 
362  private void InitInventoryUIs()
363  {
364  if (inputInventoryHolder != null)
365  {
366  inputContainer.AllowUIOverlap = true;
367  inputContainer.Inventory.DrawWhenEquipped = true;
368  inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
369  }
370  outputContainer.AllowUIOverlap = true;
371  outputContainer.Inventory.DrawWhenEquipped = true;
372  outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
373  }
374 
375  private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
376  {
377  if (fabricationRecipe == null) { return ""; }
378  if (fabricationRecipe.Amount > 1)
379  {
380  return TextManager.GetWithVariables("fabricationrecipenamewithamount",
381  ("[name]", fabricationRecipe.DisplayName), ("[amount]", fabricationRecipe.Amount.ToString()));
382  }
383  else
384  {
385  return fabricationRecipe.DisplayName;
386  }
387  }
388 
389  partial void OnItemLoadedProjSpecific()
390  {
391  CreateGUI();
392  InitInventoryUIs();
393  }
394 
395  partial void SelectProjSpecific(Character character)
396  {
397  if (character != Character.Controlled) { return; }
398 
399  var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
400  nonItems.ForEach(i => itemList.Content.RemoveChild(i));
401 
402  itemList.Content.RectTransform.SortChildren((c1, c2) =>
403  {
404  var item1 = c1.GUIComponent.UserData as FabricationRecipe;
405  var item2 = c2.GUIComponent.UserData as FabricationRecipe;
406 
407  int itemPlacement1 = calculatePlacement(item1);
408  int itemPlacement2 = calculatePlacement(item2);
409  if (itemPlacement1 != itemPlacement2)
410  {
411  return itemPlacement1 > itemPlacement2 ? -1 : 1;
412  }
413 
414  int calculatePlacement(FabricationRecipe recipe)
415  {
416  if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
417  {
418  return -2;
419  }
420  int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
421  return placement;
422  }
423 
424  return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
425  });
426 
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)
429  {
430  AutoScaleHorizontal = true,
431  CanBeFocused = false
432  };
433  sufficientSkillsText.RectTransform.SetAsFirstChild();
434 
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)
437  {
438  AutoScaleHorizontal = true,
439  CanBeFocused = false
440  };
441  var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
442  if (firstinSufficient != null)
443  {
444  insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
445  }
446  else
447  {
448  sufficientSkillsText.Visible = insufficientSkillsText.Visible = false;
449  sufficientSkillsText.Enabled = insufficientSkillsText.Enabled = false;
450  }
451 
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)
454  {
455  AutoScaleHorizontal = true,
456  CanBeFocused = false
457  };
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)
462  {
463  requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
464  }
465 
466  FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
467  HideEmptyItemListCategories();
468  }
469 
470  private readonly Dictionary<FabricationRecipe.RequiredItem, int> missingIngredientCounts = new Dictionary<FabricationRecipe.RequiredItem, int>();
471  private float ingredientHighlightTimer;
472 
473  private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
474  {
475  overlayComponent.RectTransform.SetAsLastChild();
476 
477  missingIngredientCounts.Clear();
478 
479  FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
480  if (targetItem != null)
481  {
482  foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
483  {
484  if (missingIngredientCounts.ContainsKey(requiredItem))
485  {
486  missingIngredientCounts[requiredItem] += requiredItem.Amount;
487  }
488  else
489  {
490  missingIngredientCounts[requiredItem] = requiredItem.Amount;
491  }
492  }
493  foreach (Item item in inputContainer.Inventory.AllItems)
494  {
495  var missingIngredient = missingIngredientCounts.Keys.FirstOrDefault(mi => mi.MatchesItem(item));
496  if (missingIngredient == null) { continue; }
497 
498  if (missingIngredientCounts[missingIngredient] == 1)
499  {
500  missingIngredientCounts.Remove(missingIngredient);
501  }
502  else
503  {
504  missingIngredientCounts[missingIngredient]--;
505  }
506  }
507 
508  if (ingredientHighlightTimer <= 0.0f)
509  {
510  //highlight inventory slots that contain suitable ingredients in linked inventories
511  foreach (var inventory in linkedInventories)
512  {
513  if (inventory.visualSlots == null) { continue; }
514  for (int i = 0; i < inventory.Capacity; i++)
515  {
516  if (inventory.visualSlots[i].HighlightTimer > 0.0f) { continue; }
517  var availableItem = inventory.GetItemAt(i);
518  if (availableItem == null) { continue; }
519 
520  if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableItem)))
521  {
522  inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
523  continue;
524  }
525  if (availableItem.OwnInventory != null)
526  {
527  for (int j = 0; j < availableItem.OwnInventory.Capacity; j++)
528  {
529  var availableContainedItem = availableItem.OwnInventory.GetItemAt(i);
530  if (availableContainedItem == null) { continue; }
531  if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableContainedItem)))
532  {
533  inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
534  break;
535  }
536  }
537  }
538  }
539  }
540  ingredientHighlightTimer = 1.0f;
541  }
542 
543  int slotIndex = 0;
544  foreach (var kvp in missingIngredientCounts)
545  {
546  if (inputContainer.Inventory?.visualSlots == null) { break; }
547 
548  var requiredItem = kvp.Key;
549  int missingCount = kvp.Value;
550 
551  while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null)
552  {
553  slotIndex++;
554  }
555 
556  if (slotIndex >= inputContainer.Capacity) { break; }
557 
558  if (slotIndex < inputContainer.Capacity &&
559  inputContainer.Inventory.visualSlots[slotIndex].HighlightTimer <= 0.0f &&
560  availableIngredients.Any(i => i.Value.Any() && requiredItem.MatchesItem(i.Value.First())))
561  {
562  inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
563  }
564 
565  Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
566 
567  var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
568 
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())
572  {
573  float iconCycleSpeed = 0.75f;
574  float iconCycleT = (float)Timing.TotalTime * iconCycleSpeed;
575  int iconIndex = (int)(iconCycleT % requiredItem.ItemPrefabs.Count());
576 
577  requiredItemToDisplay = requiredItem.ItemPrefabs.Skip(iconIndex).FirstOrDefault();
578  iconAlpha = Math.Min(Math.Abs(MathF.Sin(iconCycleT * MathHelper.Pi)) * 2.0f, 1.0f);
579  }
580  else
581  {
582  requiredItemToDisplay ??= requiredItem.ItemPrefabs.FirstOrDefault();
583  iconAlpha = 1.0f;
584  }
585  if (iconAlpha > 0.0f)
586  {
587  var itemIcon = requiredItemToDisplay.InventoryIcon ?? requiredItemToDisplay.Sprite;
588  itemIcon.Draw(
589  spriteBatch,
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));
593  }
594 
595  if (missingCount > 1)
596  {
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);
602  }
603 
604  if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
605  {
606  DrawConditionBar(spriteBatch, requiredItem.MinCondition);
607  }
608  else if (requiredItem.MaxCondition < 1.0f)
609  {
610  DrawConditionBar(spriteBatch, requiredItem.MaxCondition);
611  }
612 
613  void DrawConditionBar(SpriteBatch sb, float condition)
614  {
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);
621  }
622 
623  if (slotRect.Contains(PlayerInput.MousePosition))
624  {
625  LocalizedString toolTipText = requiredItem.OverrideHeader;
626  if (requiredItem.OverrideHeader.IsNullOrEmpty())
627  {
628  var suitableIngredients = requiredItem.ItemPrefabs.Where(ip => !ip.HideInMenus).OrderBy(ip => ip.DefaultPrice?.Price ?? 0).Select(ip => ip.Name).Distinct();
629  toolTipText = GetSuitableIngredientText(suitableIngredients);
630  }
631  if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
632  {
633  toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
634  }
635  else if (requiredItem.MaxCondition < 1.0f)
636  {
637  if (requiredItem.MaxCondition <= 0.0f)
638  {
639  toolTipText += " " + (int)Math.Round(requiredItem.MaxCondition * 100) + "%";
640  }
641  else
642  {
643  toolTipText += " 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) + "%";
644  }
645  }
646  else if (requiredItem.MaxCondition <= 0.0f)
647  {
648  toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
649  }
650 
651  toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
652  if (!requiredItem.OverrideDescription.IsNullOrEmpty())
653  {
654  toolTipText += '\n' + requiredItem.OverrideDescription;
655  }
656  else if (!requiredItemPrefab.Description.IsNullOrEmpty())
657  {
658  toolTipText += '\n' + requiredItemPrefab.Description;
659  }
660  tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
661  }
662 
663  slotIndex++;
664  }
665  }
666  }
667 
668  private LocalizedString GetSuitableIngredientText(IEnumerable<LocalizedString> itemNameList)
669  {
670  int count = itemNameList.Count();
671  if (count == 0)
672  {
673  return string.Empty;
674  }
675  else if (count == 1)
676  {
677  return itemNameList.First();
678  }
679  else if (count == 2)
680  {
681  //[item1] or [item2]
682  return TextManager.GetWithVariables(
683  "DialogRequiredTreatmentOptionsLast",
684  ("[treatment1]", itemNameList.ElementAt(0)),
685  ("[treatment2]", itemNameList.ElementAt(1)));
686  }
687  else
688  {
689  // [item1], [item2], [item3] ... or [lastitem]
690  LocalizedString itemListStr = TextManager.GetWithVariables(
691  "DialogRequiredTreatmentOptionsFirst",
692  ("[treatment1]", itemNameList.ElementAt(0)),
693  ("[treatment2]", itemNameList.ElementAt(1)));
694 
695  int i;
696  bool isTruncated = false;
697  for (i = 2; i < count - 1; i++)
698  {
699  if (itemListStr.Length > 50)
700  {
701  isTruncated = true;
702  break;
703  }
704  itemListStr = TextManager.GetWithVariables(
705  "DialogRequiredTreatmentOptionsFirst",
706  ("[treatment1]", itemListStr),
707  ("[treatment2]", itemNameList.ElementAt(i)));
708  }
709  itemListStr = TextManager.GetWithVariables(
710  "DialogRequiredTreatmentOptionsLast",
711  ("[treatment1]", itemListStr),
712  ("[treatment2]", itemNameList.ElementAt(i)));
713 
714  if (isTruncated)
715  {
716  itemListStr += TextManager.Get("ellipsis");
717  }
718  return itemListStr;
719  }
720  }
721 
722  private void DrawOutputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
723  {
724  overlayComponent.RectTransform.SetAsLastChild();
725 
726  FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
727  if (targetItem != null && outputContainer.Inventory?.visualSlots != null)
728  {
729  Rectangle slotRect = outputContainer.Inventory.visualSlots[0].Rect;
730  if (fabricatedItem != null)
731  {
732  float clampedProgressState = Math.Clamp(progressState, 0f, 1f);
733  GUI.DrawRectangle(spriteBatch,
734  new Rectangle(
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);
738  }
739 
740  if (outputContainer.Inventory.IsEmpty())
741  {
742  var itemIcon = targetItem.TargetItem.InventoryIcon ?? targetItem.TargetItem.Sprite;
743  itemIcon.Draw(
744  spriteBatch,
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);
748  }
749  }
750 
751  if (tooltip != null)
752  {
753  GUIComponent.DrawToolTip(spriteBatch, RichString.Rich(tooltip.Tooltip), tooltip.TargetElement);
754  tooltip = null;
755  }
756  }
757 
758  private bool FilterEntities(MapEntityCategory? category, string filter)
759  {
760  foreach (GUIComponent child in itemList.Content.Children)
761  {
762  FabricationRecipe recipe = child.UserData as FabricationRecipe;
763  if (recipe?.DisplayName == null) { continue; }
764 
765  if (recipe.HideForNonTraitors)
766  {
767  if (Character.Controlled is not { IsTraitor: true })
768  {
769  child.Visible = false;
770  continue;
771  }
772  }
773 
774  child.Visible =
775  (string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
776  (!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
777  }
778 
779  foreach (GUIButton btn in itemCategoryButtons)
780  {
781  btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory;
782  }
783  HideEmptyItemListCategories();
784 
785  return true;
786  }
787 
788  private void HideEmptyItemListCategories()
789  {
790  bool visibleElementsChanged = false;
791  //go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them
792  bool recipeVisible = false;
793  foreach (GUIComponent child in itemList.Content.Children.Reverse())
794  {
795  if (child.UserData is not FabricationRecipe recipe)
796  {
797  if (child.Enabled)
798  {
799  if (child.Visible != recipeVisible)
800  {
801  child.Visible = recipeVisible;
802  visibleElementsChanged = true;
803  }
804  }
805  recipeVisible = false;
806  }
807  else
808  {
809  recipeVisible |= child.Visible;
810  }
811  }
812 
813  if (visibleElementsChanged)
814  {
815  itemList.UpdateScrollBarSize();
816  itemList.BarScroll = 0.0f;
817  }
818  }
819 
820  public bool ClearFilter()
821  {
822  FilterEntities(selectedItemCategory, "");
823  itemList.UpdateScrollBarSize();
824  itemList.BarScroll = 0.0f;
825  itemFilterBox.Text = "";
826  return true;
827  }
828 
829  private readonly record struct SelectedRecipe(Character User, FabricationRecipe SelectedItem, Option<float> OverrideRequiredTime);
830  private Option<SelectedRecipe> LastSelectedRecipe = Option.None;
831 
832  private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null)
833  {
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);
839  return true;
840  }
841 
842  private void CreateSelectedItemUI(SelectedRecipe recipe)
843  {
844  var (user, selectedItem, overrideRequiredTime) = recipe;
845  int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1);
846 
847  if (amountInput != null)
848  {
849  float prevBarScroll = amountInput.BarScroll;
850  amountInput.Range = new Vector2(1, max);
851  amountInput.BarScroll = prevBarScroll;
852 
853  amountTextMax.Text = max.ToString();
854  amountInput.Enabled = amountTextMax.Enabled = max > 1;
855  AmountToFabricate = Math.Min((int)amountInput.BarScrollValue, max);
856  }
857  RefreshActivateButtonText();
858 
859  selectedItemFrame.ClearChildren();
860  selectedItemReqsFrame.ClearChildren();
861 
862  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
863  var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
864 
865  LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
866  LocalizedString name = itemName;
867 
868  QualityResult result = GetFabricatedItemQuality(selectedItem, user);
869 
870  float quality = selectedItem.Quality ?? result.Quality;
871  if (quality > 0 || result.HasRandomQualityRollChance)
872  {
873  name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n')
874  .Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n'));
875  }
876 
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)
879  {
880  AutoScaleHorizontal = true
881  };
882 
883  if (result.HasRandomQualityRollChance)
884  {
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);
887 
888  float percentage1 = result.TotalPlusOnePercentage;
889  float percentage2 = result.TotalPlusTwoPercentage;
890 
891  string chance1text = percentage1.ToString("F1", CultureInfo.InvariantCulture);
892  string chance2text = percentage2.ToString("F1", CultureInfo.InvariantCulture);
893 
894  int quality1 = Math.Clamp(result.Quality + 1, min: 0, max: 3);
895  int quality2 = Math.Clamp(result.Quality + 2, min: 0, max: 3);
896 
897  LocalizedString quality1Text = TextManager.Get($"quality{quality1}");
898  LocalizedString quality2Text = TextManager.Get($"quality{quality2}");
899 
900  string localizationTag = percentage2 > 0f && percentage1 > 0 && quality1 != quality2 ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
901 
902  var variables = new (string Key, LocalizedString Value)[]
903  {
904  ("[chance]", chance1text), ("[quality]", quality1Text),
905  ("[chance2]", chance2text), ("[quality2]", quality2Text)
906  };
907 
908  if (MathUtils.NearlyEqual(percentage1, 0))
909  {
910  variables = new[] { ("[chance]", chance2text), ("[quality]", quality2Text) };
911  }
912 
913  if (quality1 == quality2)
914  {
915  LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
916  variables = new[] { ("[chance]", rawPercentage), ("[quality]", quality1Text) };
917  }
918 
919  LocalizedString qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
920 
921  icon.ToolTip = RichString.Rich(qualityTooltip);
922  icon.Visible = icon.CanBeFocused = true;
923  }
924 
925  nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
926  if (nameBlock.TextScale < 0.7f)
927  {
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));
933  }
934 
935  if (!selectedItem.TargetItem.Description.IsNullOrEmpty())
936  {
937  var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
938  RichString.Rich(selectedItem.TargetItem.Description),
939  font: GUIStyle.SmallFont, wrap: true);
940  description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
941 
942  while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
943  {
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();
949  description.ToolTip = selectedItem.TargetItem.Description;
950  }
951  }
952 
953  IEnumerable<Skill> inadequateSkills = Enumerable.Empty<Skill>();
954  if (user != null)
955  {
956  inadequateSkills = selectedItem.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
957  }
958 
959  if (selectedItem.RequiredSkills.Any())
960  {
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)
964  {
965  AutoScaleHorizontal = true,
966  ToolTip = TextManager.Get("fabricatorrequiredskills.tooltip")
967  };
968  foreach (Skill skill in selectedItem.RequiredSkills)
969  {
970  text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + Math.Round(skill.Level * SkillRequirementMultiplier);
971  if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; }
972  }
973  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUIStyle.SmallFont);
974  }
975 
976  float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
977  if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
978 
979  float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
980  ? time
981  : (user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
982 
983  if ((int)requiredTime > 0)
984  {
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)
987  {
988  AutoScaleHorizontal = true,
989  };
990  requiredTimeBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), ToolBox.SecondsToReadableTime(requiredTime),
991  font: GUIStyle.SmallFont);
992  }
993 
994  if (SelectedItem.RequiredMoney > 0)
995  {
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)
998  {
999  AutoScaleHorizontal = true,
1000  };
1001  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), TextManager.FormatCurrency(SelectedItem.RequiredMoney),
1002  font: GUIStyle.SmallFont);
1003 
1004  }
1005  }
1006 
1007  public void HighlightRecipe(string identifier, Color color)
1008  {
1009  foreach (GUIComponent child in itemList.Content.Children)
1010  {
1011  FabricationRecipe recipe = child.UserData as FabricationRecipe;
1012  if (recipe?.DisplayName == null) { continue; }
1013  if (recipe.TargetItem.Identifier == identifier)
1014  {
1015  if (child.FlashTimer > 0.0f) return;
1016  child.Flash(color, 1.5f, false);
1017 
1018  for (int i = 0; i < child.CountChildren; i++)
1019  {
1020  var grandChild = child.GetChild(i);
1021  if (grandChild is GUITextBlock) continue;
1022  grandChild.Flash(color, 1.5f, false);
1023  }
1024 
1025  return;
1026  }
1027  }
1028  }
1029 
1030  private bool StartButtonClicked(GUIButton button, object obj)
1031  {
1032  if (selectedItem == null) { return false; }
1033  if (fabricatedItem == null &&
1034  !outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health))
1035  {
1036  outputSlot.Flash(GUIStyle.Red);
1037  return false;
1038  }
1039 
1040  amountRemaining = AmountToFabricate;
1041 
1042  if (GameMain.Client != null)
1043  {
1044  pendingFabricatedItem = fabricatedItem != null ? null : selectedItem;
1045  item.CreateClientEvent(this);
1046  }
1047  else
1048  {
1049  if (fabricatedItem == null)
1050  {
1051  StartFabricating(selectedItem, Character.Controlled);
1052  }
1053  else
1054  {
1055  CancelFabricating(Character.Controlled);
1056  }
1057  }
1058 
1059  return true;
1060  }
1061 
1062  public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
1063  {
1064  activateButton.Enabled = false;
1065  inSufficientPowerWarning.Visible = IsActive && !hasPower;
1066 
1067  ingredientHighlightTimer -= deltaTime;
1068 
1069  if (!IsActive)
1070  {
1071  if (selectedItem != null && displayingForCharacter != character)
1072  {
1073  //reselect to recreate the info based on the new user's skills
1074  SelectItem(character, selectedItem);
1075  }
1076 
1077  //only check ingredients if the fabricator isn't active (if it is, this is done in Update)
1078  if (refreshIngredientsTimer <= 0.0f)
1079  {
1080  RefreshAvailableIngredients();
1081  refreshIngredientsTimer = RefreshIngredientsInterval;
1082  }
1083  refreshIngredientsTimer -= deltaTime;
1084  }
1085 
1086  if (character != null)
1087  {
1088  foreach (GUIComponent child in itemList.Content.Children)
1089  {
1090  if (child.UserData is not FabricationRecipe recipe) { continue; }
1091 
1092  if (recipe != selectedItem &&
1093  (child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
1094  {
1095  continue;
1096  }
1097 
1098  bool canBeFabricated = CanBeFabricated(recipe, availableIngredients, character);
1099  if (recipe == selectedItem)
1100  {
1101  activateButton.Enabled = canBeFabricated;
1102  }
1103 
1104  var childContainer = child.GetChild<GUILayoutGroup>();
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);
1107 
1108  var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText));
1109  limitReachedText.Visible = !canBeFabricated && fabricationLimits.TryGetValue(recipe.RecipeHash, out int amount) && amount <= 0;
1110  }
1111  }
1112  }
1113 
1114  public override void OnPlayerSkillsChanged()
1115  => RefreshSelectedItem();
1116 
1117  public void RefreshSelectedItem()
1118  {
1119  if (!LastSelectedRecipe.TryUnwrap(out var lastSelected)) { return; }
1120  CreateSelectedItemUI(lastSelected);
1121  }
1122 
1123  partial void UpdateRequiredTimeProjSpecific()
1124  {
1125  if (requiredTimeBlock == null) { return; }
1126  requiredTimeBlock.Text = ToolBox.SecondsToReadableTime(timeUntilReady > 0.0f ? timeUntilReady : requiredTime);
1127  }
1128 
1129  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
1130  {
1131  uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
1132  msg.WriteUInt32(recipeHash);
1133  msg.WriteRangedInteger(AmountToFabricate, 1, MaxAmountToFabricate);
1134  }
1135 
1136  public void ClientEventRead(IReadMessage msg, float sendingTime)
1137  {
1138  FabricatorState newState = (FabricatorState)msg.ReadByte();
1139  int amountToFabricate = msg.ReadRangedInteger(0, MaxAmountToFabricate);
1140  int amountRemaining = msg.ReadRangedInteger(0, MaxAmountToFabricate);
1141  float newTimeUntilReady = msg.ReadSingle();
1142  uint recipeHash = msg.ReadUInt32();
1143  UInt16 userID = msg.ReadUInt16();
1144  Character user = Entity.FindEntityByID(userID) as Character;
1145 
1146  ushort reachedLimitCount = msg.ReadUInt16();
1147  for (int i = 0; i < reachedLimitCount; i++)
1148  {
1149  fabricationLimits[msg.ReadUInt32()] = 0;
1150  }
1151  State = newState;
1152  //don't touch the amount unless another character changed it or the fabricator is running
1153  //otherwise we may end up reverting the changes the client just did to the amount
1154  if ((user != null && user != Character.Controlled) || State != FabricatorState.Stopped)
1155  {
1156  this.amountToFabricate = amountToFabricate;
1157  }
1158  this.amountRemaining = amountRemaining;
1159  if (newState == FabricatorState.Stopped || recipeHash == 0)
1160  {
1161  CancelFabricating();
1162  }
1163  else if (newState == FabricatorState.Active || newState == FabricatorState.Paused)
1164  {
1165  //if already fabricating the selected item, return
1166  if (fabricatedItem != null && fabricatedItem.RecipeHash == recipeHash) { return; }
1167  if (recipeHash == 0) { return; }
1168 
1169  SelectItem(user, fabricationRecipes[recipeHash]);
1170  StartFabricating(fabricationRecipes[recipeHash], user);
1171  }
1172  timeUntilReady = newTimeUntilReady;
1173  }
1174  }
1175 }
float GetSkillLevel(string skillIdentifier)
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
override bool Enabled
Definition: GUIButton.cs:27
LocalizedString Text
Definition: GUIButton.cs:138
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
virtual void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectInflate=null)
virtual float FlashTimer
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
Definition: GUIComponent.cs:95
virtual Rectangle Rect
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
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...
Definition: GUITextBox.cs:38
static GameClient Client
Definition: GameMain.cs:188
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...
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
float FabricationDegreeOfSuccess(Character character, ImmutableArray< Skill > skills)
readonly record struct QualityResult(int Quality, bool HasRandomQuality, float PlusOnePercentage, float PlusTwoPercentage)
readonly Identifier Identifier
Definition: Prefab.cs:34
void Resize(Point absoluteSize, bool resizeChildren=true)
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)