Client LuaCsForBarotrauma
SubmarineSelection.cs
1 using System;
2 using System.Collections.Generic;
3 using Microsoft.Xna.Framework;
4 using System.Linq;
5 using Microsoft.Xna.Framework.Graphics;
6 using Microsoft.Xna.Framework.Input;
7 using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
8 
9 namespace Barotrauma
10 {
12  {
13  private const int submarinesPerPage = 4;
14  private int currentPage = 1;
15  private int pageCount;
16  private readonly bool transferService, purchaseService;
17  private bool initialized;
18 
20  private GUIFrame pageIndicatorHolder;
21  private GUICustomComponent selectedSubmarineIndicator;
22  private GUILayoutGroup submarineHorizontalGroup, submarineControlsGroup;
23  private GUIButton browseLeftButton, browseRightButton, confirmButton, confirmButtonAlt;
24  private GUIListBox specsFrame;
25  private GUIImage[] pageIndicators;
26  private GUITextBlock descriptionTextBlock;
27  private int selectionIndicatorThickness;
28  private GUIImage listBackground;
29  private GUITickBox transferItemsTickBox;
30  private GUITextBlock itemTransferInfoBlock;
31 
32  private readonly List<SubmarineInfo> subsToShow;
33  private readonly SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
34  private SubmarineInfo selectedSubmarine = null;
35  private LocalizedString purchaseAndSwitchText, purchaseOnlyText, selectedSubText, switchText, missingPreviewText, currencyName;
36  private readonly RectTransform parent;
37  private readonly Action closeAction;
38  private Sprite pageIndicator;
39 
40  private readonly LocalizedString[] messageBoxOptions;
41 
42  public static bool ContentRefreshRequired = false;
43 
44  private static readonly Color indicatorColor = new Color(112, 149, 129);
45  private Point createdForResolution;
46 
47  private PlayerBalanceElement? playerBalanceElement;
48 
49  private struct SubmarineDisplayContent
50  {
51  public GUIFrame background;
52  public GUIImage submarineImage;
53  public SubmarineInfo displayedSubmarine;
54  public GUITextBlock submarineName;
55  public GUITextBlock submarineClass;
56  public GUITextBlock submarineTier;
57  public GUITextBlock submarineFee;
58  public GUIButton selectSubmarineButton;
59  public GUITextBlock middleTextBlock;
60  public GUIButton previewButton;
61  }
62 
63  private bool TransferItemsOnSwitch
64  {
65  get
66  {
67  return transferItemsOnSwitch;
68  }
69  set
70  {
71  transferItemsOnSwitch = value;
72  if (transferItemsTickBox != null)
73  {
74  transferItemsTickBox.Selected = value;
75  }
76  }
77  }
78  private bool transferItemsOnSwitch = true;
79 
80  public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
81  {
82  if (GameMain.GameSession.Campaign == null) { return; }
83 
84  transferService = transfer;
85  purchaseService = !transfer;
86  this.parent = parent;
87  this.closeAction = closeAction;
88 
89  subsToShow = new List<SubmarineInfo>();
90 
91  if (GameMain.Client == null)
92  {
93  messageBoxOptions = new LocalizedString[2] { TextManager.Get("Yes"), TextManager.Get("Cancel") };
94  }
95  else
96  {
97  messageBoxOptions = new LocalizedString[2] { TextManager.Get("Yes") + " " + TextManager.Get("initiatevoting"), TextManager.Get("Cancel") };
98  }
99 
100  if (Submarine.MainSub?.Info == null) { return; }
101  Initialize();
102  }
103 
104  private void Initialize()
105  {
106  initialized = true;
107  selectedSubText = TextManager.Get("selectedsub");
108  switchText = TextManager.Get("switchtosubmarinebutton");
109  purchaseAndSwitchText = TextManager.Get("purchaseandswitch");
110  purchaseOnlyText = TextManager.Get("purchase");
111 
112  currencyName = TextManager.Get("credit").Value.ToLowerInvariant();
113 
114  UpdateSubmarines();
115  missingPreviewText = TextManager.Get("SubPreviewImageNotFound");
116  CreateGUI();
117  }
118 
119  private void CreateGUI()
120  {
121  createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
122 
123  GUILayoutGroup content;
124  GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.7f), parent, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) });
125  selectionIndicatorThickness = HUDLayoutSettings.Padding / 2;
126 
127  GUIFrame background = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), color: Color.Black * 0.9f)
128  {
129  CanBeFocused = false
130  };
131 
132  content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) };
133  GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.DisplayName), font: GUIStyle.LargeFont);
134  header.CalculateHeightFromText(0, true);
135  playerBalanceElement = CampaignUI.AddBalanceElement(header, new Vector2(1.0f, 1.5f));
136 
137  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), content.RectTransform), style: "HorizontalLine");
138 
139  GUILayoutGroup submarineContentGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true };
140  submarineHorizontalGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), submarineContentGroup.RectTransform)) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true };
141 
142  submarineControlsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), submarineContentGroup.RectTransform), true, Anchor.TopCenter);
143 
144  GUILayoutGroup infoFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { IsHorizontal = true, Stretch = true, AbsoluteSpacing = HUDLayoutSettings.Padding };
145  new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform), style: null, new Color(8, 13, 19)) { IgnoreLayoutGroups = true };
146  listBackground = new GUIImage(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform, Anchor.CenterRight), style: null, true)
147  {
148  IgnoreLayoutGroups = true
149  };
150  new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false };
151  specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null)
152  {
153  CurrentSelectMode = GUIListBox.SelectMode.None,
154  Spacing = GUI.IntScale(5),
155  Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0)
156  };
157  new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine");
158  GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) };
159  descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false };
160 
161  GUILayoutGroup bottomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.075f), content.RectTransform, Anchor.CenterRight), childAnchor: Anchor.CenterRight) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding };
162  float transferInfoFrameWidth = 1.0f;
163 
164  if (closeAction != null)
165  {
166  GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), TextManager.Get("Close"), style: "GUIButtonFreeScale")
167  {
168  OnClicked = (button, userData) =>
169  {
170  closeAction();
171  return true;
172  }
173  };
174  transferInfoFrameWidth -= closeButton.RectTransform.RelativeSize.X;
175  }
176 
177  if (purchaseService)
178  {
179  confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale");
180  transferInfoFrameWidth -= confirmButtonAlt.RectTransform.RelativeSize.X;
181  }
182  confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), bottomContainer.RectTransform), purchaseService ? purchaseAndSwitchText : switchText, style: "GUIButtonFreeScale");
183  SetConfirmButtonState(false);
184  transferInfoFrameWidth -= confirmButton.RectTransform.RelativeSize.X;
185  GUIFrame transferInfoFrame = new GUIFrame(new RectTransform(new Vector2(transferInfoFrameWidth, 1.0f), bottomContainer.RectTransform), style: null)
186  {
187  CanBeFocused = false
188  };
189  transferItemsTickBox = new GUITickBox(new RectTransform(new Vector2(0.2f, 1.0f), transferInfoFrame.RectTransform, Anchor.CenterRight), TextManager.Get("transferitems"), font: GUIStyle.SubHeadingFont)
190  {
191  Selected = TransferItemsOnSwitch,
192  Visible = false,
193  OnSelected = (tb) => transferItemsOnSwitch = tb.Selected
194  };
195  transferItemsTickBox.RectTransform.Resize(new Point(Math.Min((int)transferItemsTickBox.ContentWidth, transferInfoFrame.Rect.Width), transferItemsTickBox.Rect.Height));
196  itemTransferInfoBlock = new GUITextBlock(new RectTransform(Vector2.One, transferInfoFrame.RectTransform, Anchor.CenterRight), null, wrap: true)
197  {
198  TextAlignment = Alignment.CenterRight,
199  Visible = false
200  };
201 
202  pageIndicatorHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.5f), submarineControlsGroup.RectTransform), style: null);
203  pageIndicator = GUIStyle.GetComponentStyle("GUIPageIndicator").GetDefaultSprite();
204  UpdatePaging();
205 
206  for (int i = 0; i < submarineDisplays.Length; i++)
207  {
208  SubmarineDisplayContent submarineDisplayElement = new SubmarineDisplayContent
209  {
210  background = new GUIFrame(new RectTransform(new Vector2(1f / submarinesPerPage, 1f), submarineHorizontalGroup.RectTransform), style: null, new Color(8, 13, 19))
211  };
212  submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true);
213  submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center);
214  submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
215  submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
216  submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
217  submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
218  {
219  Color = Color.White,
220  HoverColor = Color.White,
221  PressedColor = Color.White
222  };
223  submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left);
224  submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right);
225 
226  submarineDisplays[i] = submarineDisplayElement;
227  }
228 
229  selectedSubmarineIndicator = new GUICustomComponent(new RectTransform(Point.Zero, submarineHorizontalGroup.RectTransform), onDraw: (sb, component) => DrawSubmarineIndicator(sb, component.Rect)) { IgnoreLayoutGroups = true, CanBeFocused = false };
230  }
231 
232  private void UpdatePaging()
233  {
234  if (pageIndicatorHolder == null) return;
235  pageIndicatorHolder.ClearChildren();
236  if (currentPage > pageCount) currentPage = pageCount;
237  if (pageCount < 2) return;
238 
239  browseLeftButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterLeft, Pivot.CenterRight) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleLeft")
240  {
241  IgnoreLayoutGroups = true,
242  OnClicked = (button, userData) =>
243  {
244  ChangePage(-1);
245  return true;
246  }
247  };
248 
249  Point indicatorSize = new Point(GUI.IntScale(pageIndicator.SourceRect.Width * 1.5f), GUI.IntScale(pageIndicator.SourceRect.Height * 1.5f));
250  pageIndicatorHolder.RectTransform.NonScaledSize = new Point(pageCount * indicatorSize.X + HUDLayoutSettings.Padding * (pageCount - 1), pageIndicatorHolder.RectTransform.NonScaledSize.Y);
251 
252  int xPos = 0;
253  int yPos = pageIndicatorHolder.Rect.Height / 2 - indicatorSize.Y / 2;
254 
255  pageIndicators = new GUIImage[pageCount];
256  for (int i = 0; i < pageCount; i++)
257  {
258  pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true);
259  xPos += indicatorSize.X + HUDLayoutSettings.Padding;
260  }
261 
262  for (int i = 0; i < pageIndicators.Length; i++)
263  {
264  pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray;
265  }
266 
267  browseRightButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterRight, Pivot.CenterLeft) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleRight")
268  {
269  IgnoreLayoutGroups = true,
270  OnClicked = (button, userData) =>
271  {
272  ChangePage(1);
273  return true;
274  }
275  };
276 
277  browseLeftButton.Enabled = currentPage > 1;
278  browseRightButton.Enabled = currentPage < pageCount;
279  }
280 
281  private void DrawSubmarineIndicator(SpriteBatch spriteBatch, Rectangle area)
282  {
283  if (area == Rectangle.Empty) return;
284  GUI.DrawRectangle(spriteBatch, area, indicatorColor, thickness: selectionIndicatorThickness);
285  }
286 
287  public void Update()
288  {
290  {
292  }
293  else
294  {
295  playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
296  }
297 
298  // Input
299  if (PlayerInput.KeyHit(Keys.Left))
300  {
301  SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), -1);
302  }
303  else if (PlayerInput.KeyHit(Keys.Right))
304  {
305  SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), 1);
306  }
307  }
308 
309  public void RefreshSubmarineDisplay(bool updateSubs, bool setTransferOptionToTrue = false)
310  {
311  if (!initialized)
312  {
313  Initialize();
314  }
315  if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y)
316  {
317  CreateGUI();
318  }
319  else
320  {
321  playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
322  }
323  if (setTransferOptionToTrue)
324  {
325  TransferItemsOnSwitch = true;
326  }
327  if (updateSubs)
328  {
329  UpdateSubmarines();
330  }
331 
332  if (pageIndicators != null)
333  {
334  for (int i = 0; i < pageIndicators.Length; i++)
335  {
336  pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray;
337  }
338  }
339 
340  int submarineIndex = (currentPage - 1) * submarinesPerPage;
341 
342  for (int i = 0; i < submarineDisplays.Length; i++)
343  {
344  SubmarineInfo subToDisplay = GetSubToDisplay(submarineIndex);
345  if (subToDisplay == null)
346  {
347  submarineDisplays[i].submarineImage.Sprite = null;
348  submarineDisplays[i].submarineName.Text = string.Empty;
349  submarineDisplays[i].submarineFee.Text = string.Empty;
350  submarineDisplays[i].submarineClass.Text = string.Empty;
351  submarineDisplays[i].submarineTier.Text = string.Empty;
352  submarineDisplays[i].selectSubmarineButton.Enabled = false;
353  submarineDisplays[i].selectSubmarineButton.OnClicked = null;
354  submarineDisplays[i].displayedSubmarine = null;
355  submarineDisplays[i].middleTextBlock.AutoDraw = false;
356  submarineDisplays[i].previewButton.Visible = false;
357  }
358  else
359  {
360  submarineDisplays[i].displayedSubmarine = subToDisplay;
361  Sprite previewImage = GetPreviewImage(subToDisplay);
362 
363  if (previewImage != null)
364  {
365  submarineDisplays[i].submarineImage.Sprite = previewImage;
366  submarineDisplays[i].middleTextBlock.AutoDraw = false;
367  }
368  else
369  {
370  submarineDisplays[i].submarineImage.Sprite = null;
371  submarineDisplays[i].middleTextBlock.Text = missingPreviewText;
372  submarineDisplays[i].middleTextBlock.AutoDraw = true;
373  }
374 
375  submarineDisplays[i].selectSubmarineButton.Enabled = true;
376 
377  int index = i;
378  submarineDisplays[i].selectSubmarineButton.OnClicked = (button, userData) =>
379  {
380  SelectSubmarine(subToDisplay, submarineDisplays[index].background.Rect);
381  return true;
382  };
383 
384  submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
385 
386  submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"));
387  submarineDisplays[i].submarineClass.ToolTip = TextManager.Get("submarineclass.description") + "\n\n" + TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}.description");
388 
389  submarineDisplays[i].submarineTier.Text = TextManager.Get($"submarinetier.{subToDisplay.Tier}");
390  submarineDisplays[i].submarineTier.ToolTip = TextManager.Get("submarinetier.description");
391 
392  if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
393  {
394  LocalizedString amountString = TextManager.FormatCurrency(subToDisplay.GetPrice());
395  submarineDisplays[i].submarineFee.Text = TextManager.GetWithVariable("price", "[amount]", amountString);
396  }
397  else
398  {
399  if (subToDisplay.Name != CurrentOrPendingSubmarine().Name)
400  {
401  submarineDisplays[i].submarineFee.Text = string.Empty;
402  }
403  else
404  {
405  submarineDisplays[i].submarineFee.Text = selectedSubText;
406  }
407  }
408 
409  if (transferService && subToDisplay.Name == CurrentOrPendingSubmarine().Name && updateSubs)
410  {
411  if (selectedSubmarine == null)
412  {
413  CoroutineManager.StartCoroutine(SelectOwnSubmarineWithDelay(subToDisplay, submarineDisplays[i]));
414  }
415  else
416  {
417  SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect);
418  }
419  }
420  else if (!transferService && selectedSubmarine == null || !transferService && GameMain.GameSession.IsSubmarineOwned(selectedSubmarine) || subToDisplay == selectedSubmarine)
421  {
422  SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect);
423  }
424 
425  submarineDisplays[i].previewButton.Visible = true;
426  submarineDisplays[i].previewButton.OnClicked = (btn, obj) =>
427  {
428  SubmarinePreview.Create(subToDisplay);
429  return false;
430  };
431  }
432 
433  submarineIndex++;
434  }
435 
436  if (subsToShow.Count == 0)
437  {
438  SelectSubmarine(null, Rectangle.Empty);
439  }
440  else
441  {
442  UpdateItemTransferInfoFrame();
443  }
444  }
445 
446  private void UpdateSubmarines()
447  {
448  subsToShow.Clear();
449  if (transferService)
450  {
451  subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
452  subsToShow.Sort(ComparePrice);
453  string currentSubName = CurrentOrPendingSubmarine().Name;
454  int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
455  if (currentIndex != -1)
456  {
457  currentPage = (int)Math.Ceiling((currentIndex + 1) / (float)submarinesPerPage);
458  }
459  }
460  else
461  {
462  subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
463  .Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
464  if (GameMain.GameSession.Campaign?.Map?.CurrentLocation is Location currentLocation)
465  {
466  subsToShow.RemoveAll(sub => !currentLocation.IsSubmarineAvailable(sub));
467  }
468  subsToShow.Sort(ComparePrice);
469  }
470 
471  if (transferService)
472  {
473  SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
474  }
475 
476  pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
477  UpdatePaging();
478  ContentRefreshRequired = false;
479 
480  static int ComparePrice(SubmarineInfo x, SubmarineInfo y)
481  {
482  return x.Price.CompareTo(y.Price) * 100 + x.Name.CompareTo(y.Name);
483  }
484  }
485 
486  private SubmarineInfo GetSubToDisplay(int index)
487  {
488  if (subsToShow.Count <= index || index < 0) { return null; }
489  return subsToShow[index];
490  }
491 
492  private Sprite GetPreviewImage(SubmarineInfo info)
493  {
494  Sprite preview = info.PreviewImage;
495 
496  if (preview == null)
497  {
498  SubmarineInfo potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
499 
500  preview = potentialMatch?.PreviewImage;
501 
502  // Try name comparison as a backup
503  if (preview == null)
504  {
505  potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == info.Name);
506  preview = potentialMatch?.PreviewImage;
507  }
508  }
509 
510  return preview;
511  }
512 
513  // Initial submarine selection needs a slight wait to allow the layoutgroups to place content properly
514  private IEnumerable<CoroutineStatus> SelectOwnSubmarineWithDelay(SubmarineInfo info, SubmarineDisplayContent display)
515  {
516  yield return new WaitForSeconds(0.05f);
517  SelectSubmarine(info, display.background.Rect);
518  }
519 
520  // Selection based on key input
521  private void SelectSubmarine(int index, int direction)
522  {
523  SubmarineInfo nextSub = GetSubToDisplay(index + direction);
524  if (nextSub == null) return;
525 
526  for (int i = 0; i < submarineDisplays.Length; i++)
527  {
528  if (submarineDisplays[i].displayedSubmarine == nextSub)
529  {
530  SelectSubmarine(nextSub, submarineDisplays[i].background.Rect);
531  return;
532  }
533  }
534 
535  ChangePage(direction);
536 
537  for (int i = 0; i < submarineDisplays.Length; i++)
538  {
539  if (submarineDisplays[i].displayedSubmarine == nextSub)
540  {
541  SelectSubmarine(nextSub, submarineDisplays[i].background.Rect);
542  return;
543  }
544  }
545  }
546 
547  private void SelectSubmarine(SubmarineInfo info, Rectangle backgroundRect)
548  {
549 #if !DEBUG
550  if (selectedSubmarine == info) return;
551 #endif
552  specsFrame.Content.ClearChildren();
553  selectedSubmarine = info;
554 
555  if (info != null)
556  {
557  bool owned = GameMain.GameSession.IsSubmarineOwned(info);
558 
559  if (owned)
560  {
561  confirmButton.Text = switchText;
562  confirmButton.OnClicked = (button, userData) =>
563  {
564  ShowTransferPrompt();
565  return true;
566  };
567  }
568  else
569  {
570  confirmButton.Text = purchaseAndSwitchText;
571  confirmButton.OnClicked = (button, userData) =>
572  {
573  ShowBuyPrompt(false);
574  return true;
575  };
576 
577  confirmButtonAlt.Text = purchaseOnlyText;
578  confirmButtonAlt.OnClicked = (button, userData) =>
579  {
580  ShowBuyPrompt(true);
581  return true;
582  };
583  }
584 
585  SetConfirmButtonState(selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
586 
587  selectedSubmarineIndicator.RectTransform.NonScaledSize = backgroundRect.Size;
588  selectedSubmarineIndicator.RectTransform.AbsoluteOffset = new Point(backgroundRect.Left - submarineHorizontalGroup.Rect.Left, 0);
589 
590  Sprite previewImage = GetPreviewImage(info);
591  listBackground.Sprite = previewImage;
592  listBackground.SetCrop(true);
593 
594  GUIFont font = GUIStyle.Font;
595  info.CreateSpecsWindow(specsFrame, font, includeCrushDepth: true);
596  descriptionTextBlock.Text = info.Description;
597  descriptionTextBlock.CalculateHeightFromText();
598  }
599  else
600  {
601  listBackground.Sprite = null;
602  listBackground.SetCrop(false);
603  descriptionTextBlock.Text = string.Empty;
604  selectedSubmarineIndicator.RectTransform.NonScaledSize = Point.Zero;
605  SetConfirmButtonState(false);
606  }
607 
608  UpdateItemTransferInfoFrame();
609  }
610 
611  private bool IsSelectedSubCurrentSub => Submarine.MainSub?.Info?.Name == selectedSubmarine?.Name;
612 
613  private void UpdateItemTransferInfoFrame()
614  {
615  if (selectedSubmarine != null)
616  {
617  if (IsSelectedSubCurrentSub)
618  {
619  TransferItemsOnSwitch = false;
620  transferItemsTickBox.Visible = false;
621  itemTransferInfoBlock.Visible = confirmButton.Enabled;
622  itemTransferInfoBlock.Text = TextManager.Get("switchingbacktocurrentsub");
623  }
624  else if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch?.Name == selectedSubmarine.Name)
625  {
626  transferItemsTickBox.Visible = false;
627  itemTransferInfoBlock.Visible = true;
628  itemTransferInfoBlock.Text = GameMain.GameSession.Campaign.TransferItemsOnSubSwitch ? TextManager.Get("itemtransferenabledreminder") : TextManager.Get("itemtransferdisabledreminder");
629  }
630  else
631  {
632  transferItemsTickBox.Selected = TransferItemsOnSwitch;
633  transferItemsTickBox.Visible = true;
634  itemTransferInfoBlock.Visible = false;
635  }
636  }
637  else
638  {
639  transferItemsTickBox.Visible = false;
640  itemTransferInfoBlock.Visible = false;
641  }
642  }
643 
644  private void SetConfirmButtonState(bool state)
645  {
646  if (confirmButtonAlt != null)
647  {
648  confirmButtonAlt.Enabled = state;
649  }
650 
651  if (confirmButton != null)
652  {
653  confirmButton.Enabled = state;
654  }
655  }
656 
658  {
660  {
661  return Submarine.MainSub?.Info;
662  }
663  else
664  {
666  }
667  }
668 
669  private void ChangePage(int pageChangeDirection)
670  {
671  SelectSubmarine(null, Rectangle.Empty);
672  if (pageChangeDirection < 0 && currentPage > 1) currentPage--;
673  if (pageChangeDirection > 0 && currentPage < pageCount) currentPage++;
674  browseLeftButton.Enabled = currentPage > 1;
675  browseRightButton.Enabled = currentPage < pageCount;
676 
678  }
679 
680  private void ShowTransferPrompt()
681  {
682  var text = TextManager.GetWithVariables("switchsubmarinetext",
683  ("[submarinename1]", CurrentOrPendingSubmarine().DisplayName),
684  ("[submarinename2]", selectedSubmarine.DisplayName));
685  text += GetItemTransferText();
686  GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), text, messageBoxOptions);
687 
688 
689  msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
690  {
691  if (!TransferItemsOnSwitch && !IsSelectedSubCurrentSub)
692  {
693  if (selectedSubmarine.NoItems)
694  {
695  return ShowConfirmationPopup(TextManager.Get("noitemsheader"), TextManager.Get("noitemswarning"));
696  }
697  if (!GameMain.GameSession.IsSubmarineOwned(selectedSubmarine) && !selectedSubmarine.IsManuallyOutfitted)
698  {
699  var (header, body) = GetItemTransferWarningText();
700  return ShowConfirmationPopup(header, body);
701  }
702  if (selectedSubmarine.LowFuel)
703  {
704  return ShowConfirmationPopup(TextManager.Get("lowfuelheader"), TextManager.Get("lowfuelwarning"));
705  }
706  }
707  return Confirm();
708  };
709  msgBox.Buttons[0].OnClicked += msgBox.Close;
710  msgBox.Buttons[1].OnClicked = msgBox.Close;
711 
712  bool ShowConfirmationPopup(LocalizedString header, LocalizedString textBody)
713  {
714  msgBox.Close();
715  var extraConfirmationBox = new GUIMessageBox(header, textBody, new LocalizedString[2] { TextManager.Get("ok"), TextManager.Get("cancel") });
716  extraConfirmationBox.Buttons[0].OnClicked = (b, o) => Confirm();
717  extraConfirmationBox.Buttons[1].OnClicked = (b, o) =>
718  {
719  selectedSubmarine = CurrentOrPendingSubmarine();
721  return true;
722  };
723  extraConfirmationBox.Buttons[0].OnClicked += extraConfirmationBox.Close;
724  extraConfirmationBox.Buttons[1].OnClicked += extraConfirmationBox.Close;
725  return true;
726  }
727 
728  bool Confirm()
729  {
730  if (GameMain.Client == null)
731  {
732  GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
734  }
735  else
736  {
737  GameMain.Client.InitiateSubmarineChange(selectedSubmarine, TransferItemsOnSwitch, Networking.VoteType.SwitchSub);
738  }
739  return true;
740  }
741  }
742 
743  private (LocalizedString header, LocalizedString body) GetItemTransferWarningText()
744  {
745  var header = TextManager.Get("itemtransferheader").Fallback(TextManager.Get("lowfuelheader"), useDefaultLanguageIfFound: false);
746  var body = TextManager.Get("itemtransferwarning").Fallback(TextManager.Get("lowfuelwarning"), useDefaultLanguageIfFound: false);
747  return (header, body);
748  }
749 
750  private void ShowBuyPrompt(bool purchaseOnly)
751  {
752  int price = selectedSubmarine.GetPrice();
753 
754  if (!GameMain.GameSession.Campaign.CanAfford(price))
755  {
756  new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext",
757  ("[currencyname]", currencyName),
758  ("[submarinename]", selectedSubmarine.DisplayName)));
759  return;
760  }
761 
762  GUIMessageBox msgBox;
763  if (!purchaseOnly)
764  {
765  var text = TextManager.GetWithVariables("purchaseandswitchsubmarinetext",
766  ("[submarinename1]", selectedSubmarine.DisplayName),
767  ("[amount]", price.ToString()),
768  ("[currencyname]", currencyName),
769  ("[submarinename2]", CurrentOrPendingSubmarine().DisplayName));
770  text += GetItemTransferText();
771  msgBox = new GUIMessageBox(TextManager.Get("purchaseandswitchsubmarineheader"), text, messageBoxOptions);
772 
773  msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
774  {
775  if (!TransferItemsOnSwitch && !IsSelectedSubCurrentSub)
776  {
777  if (selectedSubmarine.NoItems)
778  {
779  ShowConfirmationPopup(TextManager.Get("noitemsheader"), TextManager.Get("noitemswarning"));
780  return false;
781  }
782  if (!GameMain.GameSession.IsSubmarineOwned(selectedSubmarine) && !selectedSubmarine.IsManuallyOutfitted)
783  {
784  var (header, body) = GetItemTransferWarningText();
785  ShowConfirmationPopup(header, body);
786  return false;
787  }
788  if (selectedSubmarine.LowFuel)
789  {
790  ShowConfirmationPopup(TextManager.Get("lowfuelheader"), TextManager.Get("lowfuelwarning"));
791  return false;
792  }
793  }
794  return Confirm();
795  };
796 
797  void ShowConfirmationPopup(LocalizedString header, LocalizedString textBody)
798  {
799  msgBox.Close();
800  var extraConfirmationBox = new GUIMessageBox(header, textBody, new LocalizedString[2] { TextManager.Get("ok"), TextManager.Get("cancel") });
801  extraConfirmationBox.Buttons[0].OnClicked = (b, o) => Confirm();
802  extraConfirmationBox.Buttons[0].OnClicked += extraConfirmationBox.Close;
803  extraConfirmationBox.Buttons[1].OnClicked += extraConfirmationBox.Close;
804  }
805 
806  bool Confirm()
807  {
808  if (GameMain.Client == null)
809  {
810  if (GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine))
811  {
812  GameMain.GameSession.SwitchSubmarine(selectedSubmarine, TransferItemsOnSwitch);
813  }
815  }
816  else
817  {
818  GameMain.Client.InitiateSubmarineChange(selectedSubmarine, TransferItemsOnSwitch, Networking.VoteType.PurchaseAndSwitchSub);
819  }
820  return true;
821  }
822  }
823  else
824  {
825  msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext",
826  ("[submarinename]", selectedSubmarine.DisplayName),
827  ("[amount]", price.ToString()),
828  ("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions);
829 
830  msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
831  {
832  if (GameMain.Client == null)
833  {
834  GameMain.GameSession.TryPurchaseSubmarine(selectedSubmarine);
836  }
837  else
838  {
839  GameMain.Client.InitiateSubmarineChange(selectedSubmarine, false, Networking.VoteType.PurchaseSub);
840  }
841  return true;
842  };
843  }
844 
845  msgBox.Buttons[0].ClickSound = GUISoundType.ConfirmTransaction;
846  msgBox.Buttons[0].OnClicked += msgBox.Close;
847  msgBox.Buttons[1].OnClicked = msgBox.Close;
848  }
849 
850  private LocalizedString GetItemTransferText()
851  {
852  if (Submarine.MainSub?.Info?.Name == selectedSubmarine.Name)
853  {
854  return $"\n\n{TextManager.Get("switchingbacktocurrentsub")}";
855  }
856  var s = "\n\n" + TextManager.Get(TransferItemsOnSwitch ? "itemswillbetransferred" : "itemswontbetransferred");
857  s += $" {TextManager.Get("toggleitemtransferprompt")}";
858  return s;
859  }
860  }
861 }
static ? PlayerBalanceElement UpdateBalanceElement(PlayerBalanceElement? playerBalanceElement)
Definition: CampaignUI.cs:717
OnClickedHandler OnClicked
Definition: GUIButton.cs:16
override bool Enabled
Definition: GUIButton.cs:27
LocalizedString Text
Definition: GUIButton.cs:138
virtual void ClearChildren()
virtual Rectangle Rect
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
void SetCrop(bool state, bool center=true)
Definition: GUIImage.cs:47
Sprite?? Sprite
Definition: GUIImage.cs:84
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
void CalculateHeightFromText(int padding=0, bool removeExtraSpacing=false)
override bool Selected
Definition: GUITickBox.cs:18
static int GraphicsWidth
Definition: GameMain.cs:162
static GameSession?? GameSession
Definition: GameMain.cs:88
static int GraphicsHeight
Definition: GameMain.cs:168
static GameClient Client
Definition: GameMain.cs:188
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
Vector2 RelativeSize
Relative to the parent rect.
void Resize(Point absoluteSize, bool resizeChildren=true)
Point NonScaledSize
Size before scale multiplications.
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
static Submarine MainSub
Note that this can be null in some situations, e.g. editors and missions that don't load a submarine.
int GetPrice(Location location=null, ImmutableHashSet< Character > characterList=null)
bool LowFuel
Note: Refreshed for loaded submarines when they are saved, when they are loaded, and on round end....
static void Create(SubmarineInfo submarineInfo)
SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
void RefreshSubmarineDisplay(bool updateSubs, bool setTransferOptionToTrue=false)
static SubmarineInfo CurrentOrPendingSubmarine()
GUISoundType
Definition: GUI.cs:21