Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
1 using System.Collections.Generic;
2 using System.Collections.Immutable;
4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
6 using System.Linq;
7 using System.Xml.Linq;
9 
11 {
12  partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
13  {
15  {
16  get { return activateButton; }
17  }
18  private GUIButton activateButton;
19  private GUIComponent inputInventoryHolder, outputInventoryHolder;
20  private GUIListBox outputDisplayListBox;
21 
22  private GUIComponent inSufficientPowerWarning;
23 
24  private bool pendingState;
25 
26  private GUITextBlock infoArea;
27 
28  [Serialize("DeconstructorDeconstruct", IsPropertySaveable.Yes)]
29  public string ActivateButtonText { get; set; }
30 
31  [Serialize("", IsPropertySaveable.Yes)]
32  public string InfoText { get; set; }
33 
34  [Serialize(0.0f, IsPropertySaveable.Yes)]
35  public float InfoAreaWidth { get; set; }
36 
37  [Serialize(true, IsPropertySaveable.Yes)]
38  public bool ShowOutput { get; set; }
39 
40  partial void InitProjSpecific(XElement _)
41  {
42  CreateGUI();
43  }
44 
45  public override bool RecreateGUIOnResolutionChange => true;
46 
47  protected override void OnResolutionChanged()
48  {
49  OnItemLoadedProjSpecific();
50  }
51 
52  protected override void CreateGUI()
53  {
54  var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.88f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
55  {
56  Stretch = true,
57  RelativeSpacing = 0.08f
58  };
59 
60  new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Prefab.Name, font: GUIStyle.SubHeadingFont)
61  {
62  TextAlignment = Alignment.Center,
63  AutoScaleHorizontal = true
64  };
65 
66  var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);
67 
68  // === INPUT LABEL === //
69  var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true);
70 
71  var queueLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.43f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
72  {
73  Stretch = true,
74  RelativeSpacing = 0.05f
75  };
76  var queueLabel = new GUITextBlock(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), TextManager.Get("deconstructor.inputqueue"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
77  queueLabel.RectTransform.Resize(new Point((int) queueLabel.Font.MeasureString(queueLabel.Text).X, queueLabel.RectTransform.Rect.Height));
78  new GUIFrame(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), style: "HorizontalLine");
79 
80  var inputLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.57f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
81  {
82  Stretch = true,
83  RelativeSpacing = 0.05f
84  };
85  var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
86  inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
87  new GUIFrame(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), style: "HorizontalLine");
88 
89  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
90 
91  // === INPUT SLOTS === //
92  inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
93  new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawOverLay, null) { CanBeFocused = false };
94 
95  // === ACTIVATE BUTTON === //
96  var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
97  activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
98  {
99  UserData = UIHighlightAction.ElementId.DeconstructButton,
100  TextBlock = { AutoScaleHorizontal = true },
101  OnClicked = OnActivateButtonClicked
102  };
103  inSufficientPowerWarning = new GUITextBlock(new RectTransform(Vector2.One, activateButton.RectTransform),
104  TextManager.Get("DeconstructorNoPower"), textColor: GUIStyle.Orange, textAlignment: Alignment.Center, color: Color.Black, style: "OuterGlow", wrap: true)
105  {
106  HoverColor = Color.Black,
107  IgnoreLayoutGroups = true,
108  Visible = false,
109  CanBeFocused = false,
110  AutoScaleHorizontal = true
111  };
112 
113  // === OUTPUT AREA === //
114  var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);
115 
116  // === OUTPUT LABEL === //
117  var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
118  {
119  Stretch = true,
120  RelativeSpacing = 0.05f
121  };
122  var outputLabel = new GUITextBlock(new RectTransform(new Vector2(0f, 1.0f), outputLabelArea.RectTransform), TextManager.Get("uilabel.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
123  outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
124  new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");
125 
126  var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
127 
128  // === OUTPUT SLOTS === //
129  outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);
130 
131  if (ShowOutput)
132  {
133  GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter);
134  GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true);
135  GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
136  GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true);
137  outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null);
138  }
139 
140  if (InfoAreaWidth >= 0.0f)
141  {
142  var infoAreaContainer = new GUILayoutGroup(new RectTransform(new Vector2(InfoAreaWidth, 0.8f), outputArea.RectTransform), childAnchor: Anchor.CenterLeft);
143  infoArea = new GUITextBlock(new RectTransform(new Vector2(0.95f, 0.95f), infoAreaContainer.RectTransform), string.Empty, wrap: true);
144  }
145 
147  {
148  activateButton.Enabled = true;
149  if (string.IsNullOrEmpty(InfoText))
150  {
151  infoArea.Text = string.Empty;
152  }
153  else
154  {
155  infoArea.Text = TextManager.Get(InfoText).Fallback(InfoText);
156  }
157  if (IsActive)
158  {
159  activateButton.Text = TextManager.Get("DeconstructorCancel");
160  infoArea.Text = string.Empty;
161  return;
162  }
163  bool outputsFound = false;
164  foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: true))
165  {
166  outputsFound = true;
167  if (!string.IsNullOrEmpty(deconstructItem.ActivateButtonText))
168  {
169  LocalizedString buttonText = TextManager.Get(deconstructItem.ActivateButtonText).Fallback(deconstructItem.ActivateButtonText);
170  LocalizedString infoText = string.Empty;
171  if (!string.IsNullOrEmpty(deconstructItem.InfoText))
172  {
173  infoText = TextManager.Get(deconstructItem.InfoText).Fallback(deconstructItem.InfoText);
174  }
175  inputItem.GetComponent<GeneticMaterial>()?.ModifyDeconstructInfo(this, ref buttonText, ref infoText);
176  activateButton.Text = buttonText;
177  if (infoArea != null)
178  {
179  infoArea.Text = infoText;
180  }
181  return;
182  }
183  }
184  //no valid outputs found: check if we're missing some required items from the input slots and display a message about it if possible
185  if (!outputsFound && infoArea != null)
186  {
187  foreach (var (inputItem, deconstructItem) in GetAvailableOutputs(checkRequiredOtherItems: false))
188  {
189  if (deconstructItem.RequiredOtherItem.Any() && !string.IsNullOrEmpty(deconstructItem.InfoTextOnOtherItemMissing))
190  {
191  LocalizedString missingItemName = TextManager.Get("entityname." + deconstructItem.RequiredOtherItem.First());
192  infoArea.Text = TextManager.GetWithVariable(deconstructItem.InfoTextOnOtherItemMissing, "[itemname]", missingItemName);
193  }
194  }
195  }
196  activateButton.Enabled = outputsFound || !InputContainer.Inventory.IsEmpty();
197  activateButton.Text = TextManager.Get(ActivateButtonText);
198  };
199  }
200 
201  public override bool Select(Character character)
202  {
203  // TODO, This works fine as of now but if GUI.PreventElementOverlap ever gets fixed this block of code may become obsolete or detrimental.
204  // Only do this if there's only one linked component. If you link more containers then may
205  // GUI.PreventElementOverlap have mercy on your HUD layout
206  if (GuiFrame != null && item.linkedTo.Count(entity => entity is Item { DisplaySideBySideWhenLinked: true }) == 1)
207  {
208  foreach (MapEntity linkedTo in item.linkedTo)
209  {
210  if (!(linkedTo is Item { DisplaySideBySideWhenLinked: true } linkedItem)) { continue; }
211  if (!linkedItem.Components.Any()) { continue; }
212 
213  var itemContainer = linkedItem.GetComponent<ItemContainer>();
214  if (itemContainer?.GuiFrame == null || itemContainer.AllowUIOverlap) { continue; }
215 
216  // how much spacing do we want between the components
217  var padding = (int) (8 * GUI.Scale);
218  // Move the linked container to the right and move the deconstructor to the left
219  itemContainer.GuiFrame.RectTransform.AbsoluteOffset = new Point(GuiFrame.Rect.Width / -2 - padding, 0);
220  GuiFrame.RectTransform.AbsoluteOffset = new Point(itemContainer.GuiFrame.Rect.Width / 2 + padding, 0);
221  }
222  }
223  return base.Select(character);
224  }
225 
226  partial void OnItemSlotsChanged(ItemContainer container)
227  {
228  if (container.Inventory is null) { return; }
229  RefreshOutputDisplay(container.Inventory.AllItems.ToImmutableArray());
230  }
231 
232  private void RefreshOutputDisplay(ImmutableArray<Item> items)
233  {
234  const string outputItemCountUserData = "OutputItemCount";
235  const string questionMarkUserData = "UnknownItemOutput";
236 
237  if (outputDisplayListBox is null || inputContainer.Inventory is null) { return; }
238 
239  Dictionary<Identifier, int> itemCounts = new Dictionary<Identifier, int>();
240  Dictionary<Identifier, GUIComponent> children = new Dictionary<Identifier, GUIComponent>();
241 
242  bool addQuestionMark = false;
243 
244  foreach (GUIComponent child in outputDisplayListBox.Content.Children)
245  {
246  if (child.UserData is Identifier it)
247  {
248  children.Add(it, child);
249  }
250  }
251 
252  if (outputDisplayListBox.Content.FindChild(questionMarkUserData) is { } foundChild)
253  {
254  outputDisplayListBox.RemoveChild(foundChild);
255  }
256 
257  foreach (Item it in items)
258  {
259  if (it.Prefab.RandomDeconstructionOutput)
260  {
261  addQuestionMark = true;
262  continue;
263  }
264 
265  foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems)
266  {
267  if (!deconstructItem.IsValidDeconstructor(item)) { continue; }
268  float percentageHealth = it.Condition / it.MaxCondition;
269  if (percentageHealth < deconstructItem.MinCondition || percentageHealth > deconstructItem.MaxCondition) { continue; }
270  RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount);
271  }
272 
273  /*if (it.OwnInventory is { } inventory)
274  {
275  foreach (Item inventoryItems in inventory.AllItems)
276  {
277  RegisterItem(inventoryItems.Prefab.Identifier);
278  }
279  }*/
280 
281  void RegisterItem(Identifier identifier, int amount = 1)
282  {
283  if (itemCounts.ContainsKey(identifier))
284  {
285  itemCounts[identifier] += amount;
286  return;
287  }
288  itemCounts.Add(identifier, amount);
289  }
290  }
291 
292  foreach (var (it, child) in children)
293  {
294  if (!itemCounts.ContainsKey(it))
295  {
296  outputDisplayListBox.RemoveChild(child);
297  }
298  }
299 
300  foreach (var (it, amount) in itemCounts)
301  {
302  if (!children.TryGetValue(it, out GUIComponent child))
303  {
304  child = CreateOutputDisplayItem(it, outputDisplayListBox.Content);
305  }
306 
307  if (child is null) { continue; }
308  UpdateOutputDisplayItemCount(child, amount);
309  }
310 
311  if (addQuestionMark)
312  {
313  CreateQuestionMark(outputDisplayListBox.Content);
314  }
315 
316  static void CreateQuestionMark(GUIComponent parent)
317  {
318  GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
319  {
320  UserData = questionMarkUserData,
321  ToolTip = TextManager.Get("deconstructor.unknownitemsoutput")
322  };
323 
324  GUIFrame questionMarkFrame = new GUIFrame(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), style: "GUIFrameListBox")
325  {
326  CanBeFocused = false,
327  };
328 
329  // question mark text
330  new GUITextBlock(new RectTransform(Vector2.One, questionMarkFrame.RectTransform, anchor: Anchor.Center), text: "?", textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
331  {
332  CanBeFocused = false
333  };
334  }
335 
336  static GUIComponent CreateOutputDisplayItem(Identifier identifier, GUIComponent parent)
337  {
338  ItemPrefab prefab = ItemPrefab.Find(null, identifier);
339  if (prefab is null) { return null; }
340 
341  GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
342  {
343  UserData = identifier,
344  ToolTip = prefab.CreateTooltipText()
345  };
346 
347  Sprite icon = prefab.InventoryIcon ?? prefab.Sprite;
348  Color iconColor = prefab.InventoryIcon is null ? prefab.SpriteColor : prefab.InventoryIconColor;
349 
350  GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
351  {
352  Color = iconColor,
353  CanBeFocused = false
354  };
355 
356  // item count text
357  new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), itemIcon.RectTransform, anchor: Anchor.BottomRight), "", font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
358  {
359  UserData = outputItemCountUserData,
360  Shadow = true,
361  CanBeFocused = false,
362  Padding = Vector4.Zero,
363  TextColor = Color.White,
364  };
365 
366  return itemFrame;
367  }
368 
369  static void UpdateOutputDisplayItemCount(GUIComponent component, int count)
370  {
371  if (!(component.FindChild(outputItemCountUserData, recursive: true) is GUITextBlock textBlock)) { return; }
372 
373  textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString());
374  }
375  }
376 
377  partial void OnItemLoadedProjSpecific()
378  {
379  inputContainer.AllowUIOverlap = true;
380  inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
381  outputContainer.AllowUIOverlap = true;
382  outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
383 
384  inputContainer.Inventory.Locked = IsActive;
385  }
386 
387  private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
388  {
389  overlayComponent.RectTransform.SetAsLastChild();
390 
391  if (!(inputContainer?.Inventory?.visualSlots is { } visualSlots)) { return; }
392 
394  {
395  for (int i = 0; i < InputContainer.Inventory.Capacity; i++)
396  {
397  if (InputContainer.Inventory.GetItemAt(i) == null) { continue; }
398  DrawProgressBar(InputContainer.Inventory.visualSlots[i]);
399  }
400  }
401  else
402  {
403  DrawProgressBar(inputContainer.Inventory.visualSlots.Last());
404  }
405 
406  void DrawProgressBar(VisualSlot slot)
407  {
408  GUI.DrawRectangle(spriteBatch,
409  new Rectangle(
410  slot.Rect.X, slot.Rect.Y + (int)(slot.Rect.Height * (1.0f - progressState)),
411  slot.Rect.Width, (int)(slot.Rect.Height * progressState)),
412  GUIStyle.Green * 0.5f, isFilled: true);
413  }
414  }
415 
416  public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
417  {
418  inSufficientPowerWarning.Visible = IsActive && !hasPower;
419  }
420 
421  private bool OnActivateButtonClicked(GUIButton button, object obj)
422  {
423  if (!IsActive)
424  {
425  //don't allow turning on if there's non-deconstructable items in the queue
426  var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false);
427  if (disallowedItem != null && !DeconstructItemsSimultaneously)
428  {
429  int index = inputContainer.Inventory.FindIndex(disallowedItem);
430  if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length)
431  {
432  var slot = inputContainer.Inventory.visualSlots[index];
433  slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
434  }
435  return true;
436  }
437  }
438 
439  if (GameMain.Client != null)
440  {
441  pendingState = !IsActive;
442  item.CreateClientEvent(this);
443  }
444  else
445  {
446  SetActive(!IsActive, Character.Controlled);
447  }
448  return true;
449  }
450 
451  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
452  {
453  msg.WriteBoolean(pendingState);
454  }
455 
456  public void ClientEventRead(IReadMessage msg, float sendingTime)
457  {
458  ushort userID = msg.ReadUInt16();
459  Character user = userID == Entity.NullEntityID ? null : Entity.FindEntityByID(userID) as Character;
460  SetActive(msg.ReadBoolean(), user);
461  progressTimer = msg.ReadSingle();
462  }
463  }
464 }
const ushort NullEntityID
Definition: Entity.cs:14
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
Action< GUIComponent > OnAddedToGUIUpdateList
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
override void RemoveChild(GUIComponent child)
Definition: GUIListBox.cs:1249
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
RectTransform RectTransform
If set, the inventory is automatically positioned inside the rect
Item FindItem(Func< Item, bool > predicate, bool recursive)
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
int FindIndex(Item item)
Find the index of the first slot the item is contained in.
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
void SetActive(bool active, Character user=null, bool createNetworkEvent=false)
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
void Resize(Point absoluteSize, bool resizeChildren=true)
void ShowBorderHighlight(Color color, float fadeInDuration, float fadeOutDuration, float scaleUpAmount=0.5f)
Highlights an UI element of some kind. Generally used in tutorials.
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients