Client LuaCsForBarotrauma
SinglePlayerCampaignSetupUI.cs
2 using Barotrauma.IO;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Linq;
8 using System.Xml.Linq;
9 
10 namespace Barotrauma
11 {
13  {
14  private GUIListBox subList;
15 
17 
19 
20  private GUIButton nextButton;
21  private GUIListBox characterInfoColumns;
22 
24  : base(newGameContainer, loadGameContainer)
25  {
26  CreateNewGameMenu();
27  }
28 
29  private int currentPage = 0;
30  private GUIListBox pageContainer;
31 
32  public void Update()
33  {
34  float targetScroll =
35  (float)currentPage / ((float)pageContainer.Content.CountChildren - 1);
36 
37  pageContainer.BarScroll = MathHelper.Lerp(pageContainer.BarScroll, targetScroll, 0.2f);
38  if (MathUtils.NearlyEqual(pageContainer.BarScroll, targetScroll, 0.001f))
39  {
40  pageContainer.BarScroll = targetScroll;
41  }
42 
43  for (int i = 0; i < CharacterMenus.Length; i++)
44  {
45  CharacterMenus[i]?.Update();
46  }
47 
48  pageContainer.HoverCursor = CursorState.Default;
49  pageContainer.Content.HoverCursor = CursorState.Default;
50  }
51 
52  public void SetPage(int pageIndex)
53  {
54  currentPage = pageIndex;
55  for (int i = 0; i < pageContainer.Content.CountChildren; i++)
56  {
57  var child = pageContainer.Content.GetChild(i);
58  child.CanBeFocused = (i == currentPage);
59  child.GetAllChildren().ForEach(c =>
60  {
61  if (c is GUIDropDown dd)
62  {
63  dd.Dropped = false;
64  }
65  c.CanBeFocused = (i == currentPage);
66  });
67  }
68  var previewListBox = subPreviewContainer.GetAllChildren<GUIListBox>().FirstOrDefault();
69  previewListBox?.GetAllChildren()?.ForEach(c =>
70  {
71  c.CanBeFocused = false;
72  });
73  }
74 
75  private void CreateNewGameMenu()
76  {
77  pageContainer =
78  new GUIListBox(new RectTransform(Vector2.One, newGameContainer.RectTransform), style: null, isHorizontal: true)
79  {
80  ScrollBarEnabled = false,
81  ScrollBarVisible = false,
82  AllowArrowKeyScroll = false,
83  HoverCursor = CursorState.Default
84  };
85 
86  GUILayoutGroup createPageLayout()
87  {
88  var containerItem =
89  new GUIFrame(new RectTransform(Vector2.One, pageContainer.Content.RectTransform), style: null);
90  return new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, containerItem.RectTransform,
91  Anchor.Center));
92  }
93 
94  CreateFirstPage(createPageLayout());
95  CreateSecondPage(createPageLayout());
96 
97  pageContainer.RecalculateChildren();
98  pageContainer.GetAllChildren().ForEach(c =>
99  {
100  c.ClampMouseRectToParent = true;
101  });
102  pageContainer.GetAllChildren<GUIDropDown>().ForEach(dd =>
103  {
104  dd.ListBox.ClampMouseRectToParent = false;
105  dd.ListBox.Content.ClampMouseRectToParent = false;
106  });
107  SetPage(0);
108  }
109 
110  private void CreateFirstPage(GUILayoutGroup firstPageLayout)
111  {
112  firstPageLayout.RelativeSpacing = 0.02f;
113 
114  var columnContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), firstPageLayout.RectTransform), isHorizontal: true)
115  {
116  Stretch = true,
117  RelativeSpacing = 0.02f
118  };
119 
120  var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform))
121  {
122  Stretch = true,
123  RelativeSpacing = 0.015f
124  };
125 
126  var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(1.5f, 1.0f), columnContainer.RectTransform))
127  {
128  Stretch = true,
129  RelativeSpacing = 0.015f
130  };
131 
132  columnContainer.Recalculate();
133 
134  // New game left side
135  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont);
136  saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
137  {
138  textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); }
139  };
140 
141  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont);
142  seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
143 
144  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUIStyle.SubHeadingFont);
145 
146  var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3);
147  moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All);
148  moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla);
149  moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom);
150  moddedDropdown.Select(0);
151 
152  var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true)
153  {
154  Stretch = true
155  };
156 
157  subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform))
158  {
159  PlaySoundOnSelect = true,
160  ScrollBarVisible = true
161  };
162 
163  var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUIStyle.Font);
164  var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUIStyle.Font, createClearButton: true);
165  filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize;
166  searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
167  searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
168  searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
169 
170  moddedDropdown.OnSelected = (component, data) =>
171  {
172  searchBox.Text = string.Empty;
173  subFilter = (CategoryFilter)data;
174  UpdateSubList(SubmarineInfo.SavedSubmarines);
175  return true;
176  };
177 
178  subList.OnSelected = OnSubSelected;
179 
180  // New game right side
181  subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform))
182  {
183  Stretch = true
184  };
185 
186  var firstPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
187  firstPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
188  {
189  RelativeSpacing = 0.025f
190  };
191 
192  InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1f), firstPageButtonContainer.RectTransform), "", font: GUIStyle.Font, textColor: GUIStyle.Green, textAlignment: Alignment.CenterLeft)
193  {
194  TextGetter = () =>
195  {
196  int initialMoney = CampaignSettings.CurrentSettings.InitialMoney;
197  if (subList.SelectedData is SubmarineInfo subInfo)
198  {
199  initialMoney -= subInfo.Price;
200  }
201  initialMoney = Math.Max(initialMoney, 0);
202  return TextManager.GetWithVariable("campaignstartingmoney", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", initialMoney));
203  }
204  };
205 
206  CampaignCustomizeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1f), firstPageButtonContainer.RectTransform, Anchor.CenterLeft), TextManager.Get("SettingsButton"))
207  {
208  OnClicked = (tb, userdata) =>
209  {
210  CreateCustomizeWindow(CampaignSettings.CurrentSettings, settings =>
211  {
212  CampaignSettings prevSettings = CampaignSettings.CurrentSettings;
213  CampaignSettings.CurrentSettings = settings;
214  if (prevSettings.InitialMoney != settings.InitialMoney)
215  {
216  object selectedData = subList.SelectedData;
217  UpdateSubList(SubmarineInfo.SavedSubmarines);
218  if (selectedData is SubmarineInfo selectedSub && selectedSub.Price <= CampaignSettings.CurrentSettings.InitialMoney)
219  {
220  subList.Select(selectedData);
221  }
222  }
223  });
224  return true;
225  }
226  };
227 
228  nextButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), firstPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Next"))
229  {
230  OnClicked = (GUIButton btn, object userData) =>
231  {
232  SetPage(1);
233  return false;
234  }
235  };
236 
237  columnContainer.Recalculate();
238  leftColumn.Recalculate();
239  rightColumn.Recalculate();
240  }
241 
242  private void CreateSecondPage(GUILayoutGroup secondPageLayout)
243  {
244  secondPageLayout.RelativeSpacing = 0.01f;
245 
246  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform),
247  TextManager.Get("Crew"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft);
248 
249  characterInfoColumns = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true);
250 
251  var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
252  secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
253  {
254  RelativeSpacing = 0.2f
255  };
256 
257  var backButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("Back"))
258  {
259  OnClicked = (GUIButton btn, object userData) =>
260  {
261  SetPage(0);
262  return false;
263  }
264  };
265 
266  StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), secondPageButtonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton"))
267  {
268  OnClicked = FinishSetup
269  };
270  }
271 
272  public void RandomizeCrew()
273  {
274  var characterInfos = new List<(CharacterInfo Info, JobPrefab Job)>();
275  foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
276  {
277  for (int i = 0; i < jobPrefab.InitialCount; i++)
278  {
279  var variant = Rand.Range(0, jobPrefab.Variants);
280  characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant), jobPrefab));
281  }
282  }
283  if (characterInfos.Count == 0)
284  {
285  DebugConsole.ThrowError($"No starting crew found! If you're using mods, it may be that the mods have overridden the vanilla jobs without specifying which types of characters the starting crew should consist of. If you're the developer of the mod, ensure that you've set the {nameof(JobPrefab.InitialCount)} properties for the custom jobs.");
286  DebugConsole.AddWarning("Choosing the first available jobs as the starting crew...");
287  foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
288  {
289  var variant = Rand.Range(0, jobPrefab.Variants);
290  characterInfos.Add((new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant), jobPrefab));
291  if (characterInfos.Count >= 3) { break; }
292  }
293  }
294  characterInfos.Sort((a, b) => Math.Sign(b.Job.MinKarma - a.Job.MinKarma));
295 
296  characterInfoColumns.ClearChildren();
297  CharacterMenus?.ForEach(m => m.Dispose());
298  CharacterMenus = new CharacterInfo.AppearanceCustomizationMenu[characterInfos.Count];
299 
300  for (int i = 0; i < characterInfos.Count; i++)
301  {
302  var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(Math.Max(1.0f / characterInfos.Count, 0.33f), 1.0f),
303  characterInfoColumns.Content.RectTransform));
304 
305  var (characterInfo, job) = characterInfos[i];
306 
307  characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.275f), subLayout.RectTransform));
308 
309  var jobTextContainer =
310  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), subLayout.RectTransform), style: null);
311  var jobText = new GUITextBlock(new RectTransform(Vector2.One, jobTextContainer.RectTransform), job.Name, job.UIColor);
312 
313  var characterName = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), subLayout.RectTransform))
314  {
315  Text = characterInfo.Name,
316  UserData = "random"
317  };
318  characterName.OnDeselected += (sender, key) =>
319  {
320  if (string.IsNullOrWhiteSpace(sender.Text))
321  {
322  characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced);
323  sender.Text = characterInfo.Name;
324  sender.UserData = "random";
325  }
326  else
327  {
328  characterInfo.Name = sender.Text;
329  sender.UserData = "user";
330  }
331  };
332  characterName.OnEnterPressed += (sender, text) =>
333  {
334  sender.Deselect();
335  return false;
336  };
337 
338  var customizationFrame =
339  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), subLayout.RectTransform), style: null);
340  CharacterMenus[i] =
341  new CharacterInfo.AppearanceCustomizationMenu(characterInfo, customizationFrame, hasIcon: false)
342  {
343  OnHeadSwitch = menu =>
344  {
345  if (characterName.UserData is string ud && ud == "random")
346  {
347  characterInfo.Name = characterInfo.GetRandomName(Rand.RandSync.Unsynced);
348  characterName.Text = characterInfo.Name;
349  characterName.UserData = "random";
350  }
351 
352  StealRandomizeButton(menu, jobTextContainer);
353  }
354  };
355  StealRandomizeButton(CharacterMenus[i], jobTextContainer);
356  }
357  }
358 
359  private void CreateCustomizeWindow(CampaignSettings prevSettings, Action<CampaignSettings> onClosed = null)
360  {
361  CampaignCustomizeSettings = new GUIMessageBox("", "", new[] { TextManager.Get("OK") }, new Vector2(0.25f, 0.5f), minSize: new Point(450, 350));
362 
363  GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
364 
365 
366  CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings, true);
367  CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) =>
368  {
369 
370  onClosed?.Invoke(elements.CreateSettings());
371  GameSettings.SaveCurrentConfig();
372  return CampaignCustomizeSettings.Close(button, o);
373  };
374  }
375 
376  private static void StealRandomizeButton(CharacterInfo.AppearanceCustomizationMenu menu, GUIComponent parent)
377  {
378  //This is just stupid
379  var randomizeButton = menu.RandomizeButton;
380  var oldButton = parent.GetChild<GUIButton>();
381  parent.RemoveChild(oldButton);
382  randomizeButton.RectTransform.Parent = parent.RectTransform;
383  randomizeButton.RectTransform.RelativeSize = Vector2.One * 1.3f;
384  }
385 
386  private bool FinishSetup(GUIButton btn, object userdata)
387  {
388  if (string.IsNullOrWhiteSpace(saveNameBox.Text))
389  {
390  saveNameBox.Flash(GUIStyle.Red);
391  return false;
392  }
393 
394  SubmarineInfo selectedSub = null;
395 
396  if (subList.SelectedData is not SubmarineInfo) { return false; }
397  selectedSub = subList.SelectedData as SubmarineInfo;
398 
399  if (selectedSub.SubmarineClass == SubmarineClass.Undefined)
400  {
401  new GUIMessageBox(TextManager.Get("error"), TextManager.Get("undefinedsubmarineselected"));
402  return false;
403  }
404 
405  if (string.IsNullOrEmpty(selectedSub.MD5Hash.StringRepresentation))
406  {
407  ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f;
408  subList.SelectedComponent.CanBeFocused = false;
409  subList.Deselect();
410  return false;
411  }
412 
413  string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer, saveNameBox.Text);
414  bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
415 
416  CampaignSettings settings = CampaignSettings.CurrentSettings;
417 
418  if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
419  {
420  if (!hasRequiredContentPackages)
421  {
422  var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"),
423  TextManager.GetWithVariable("ContentPackageMismatchWarning", "[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)),
424  new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
425 
426  msgBox.Buttons[0].OnClicked = msgBox.Close;
427  msgBox.Buttons[0].OnClicked += (button, obj) =>
428  {
429  if (GUIMessageBox.MessageBoxes.Count == 0)
430  {
431  StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
432  }
433  return true;
434  };
435 
436  msgBox.Buttons[1].OnClicked = msgBox.Close;
437  }
438 
439  if (selectedSub.HasTag(SubmarineTag.Shuttle))
440  {
441  var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"),
442  TextManager.Get("ShuttleWarning"),
443  new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
444 
445  msgBox.Buttons[0].OnClicked = (button, obj) =>
446  {
447  StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
448  return true;
449  };
450  msgBox.Buttons[0].OnClicked += msgBox.Close;
451 
452  msgBox.Buttons[1].OnClicked = msgBox.Close;
453  return false;
454  }
455  }
456  else
457  {
458  StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text, settings);
459  }
460 
461  return true;
462  }
463 
464  public void RandomizeSeed()
465  {
466  seedBox.Text = ToolBox.RandomSeed(8);
467  }
468 
469  private void FilterSubs(GUIListBox subList, string filter)
470  {
471  foreach (GUIComponent child in subList.Content.Children)
472  {
473  if (child.UserData is not SubmarineInfo sub) { return; }
474  child.Visible = string.IsNullOrEmpty(filter) || sub.DisplayName.Contains(filter.ToLower(), StringComparison.OrdinalIgnoreCase);
475  }
476  }
477 
478  private bool OnSubSelected(GUIComponent component, object obj)
479  {
480  if (subPreviewContainer == null) { return false; }
481  (subPreviewContainer.Parent as GUILayoutGroup)?.Recalculate();
482  subPreviewContainer.ClearChildren();
483 
484  if (obj is not SubmarineInfo sub) { return true; }
485 #if !DEBUG
486  if (sub.Price > CampaignSettings.CurrentSettings.InitialMoney && !GameMain.DebugDraw)
487  {
488  SetPage(0);
489  nextButton.Enabled = false;
490  return false;
491  }
492 #endif
493  nextButton.Enabled = true;
494  sub.CreatePreviewWindow(subPreviewContainer);
495  return true;
496  }
497 
498  public void CreateDefaultSaveName()
499  {
500  string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Singleplayer);
501  saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath);
502  }
503 
504  public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
505  {
506  List<SubmarineInfo> subsToShow;
507  if (subFilter != CategoryFilter.All)
508  {
509  subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList();
510  }
511  else
512  {
513  string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
514  subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList();
515  }
516 
517  subsToShow.Sort((s1, s2) =>
518  {
519  int p1 = s1.Price;
520  if (!s1.IsCampaignCompatible) { p1 += 100000; }
521  int p2 = s2.Price;
522  if (!s2.IsCampaignCompatible) { p2 += 100000; }
523  return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
524  });
525 
526  subList.ClearChildren();
527 
528  foreach (SubmarineInfo sub in subsToShow)
529  {
530  var textBlock = new GUITextBlock(
531  new RectTransform(new Vector2(1, 0.15f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
532  ToolBox.LimitString(sub.DisplayName.Value, GUIStyle.Font, subList.Rect.Width - 65), style: "ListBoxElement")
533  {
534  ToolTip = sub.Description,
535  UserData = sub
536  };
537 
539  {
540  textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f);
541  textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.ToolTip.SanitizedString;
542  }
543 
544  var infoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight), isHorizontal: false);
545  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
546  TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.BottomRight, font: GUIStyle.SmallFont)
547  {
548  TextColor = sub.Price > CampaignSettings.CurrentSettings.InitialMoney ? GUIStyle.Red : textBlock.TextColor * 0.8f,
549  ToolTip = textBlock.ToolTip
550  };
551  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), infoContainer.RectTransform),
552  TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.TopRight, font: GUIStyle.SmallFont)
553  {
554  TextColor = textBlock.TextColor * 0.8f,
555  ToolTip = textBlock.ToolTip
556  };
557 #if !DEBUG
558  if (!GameMain.DebugDraw)
559  {
560  if (sub.Price > CampaignSettings.CurrentSettings.InitialMoney || !sub.IsCampaignCompatible)
561  {
562  textBlock.CanBeFocused = false;
563  textBlock.TextColor *= 0.5f;
564  }
565  }
566 #endif
567  }
568  if (SubmarineInfo.SavedSubmarines.Any())
569  {
570  var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignSettings.CurrentSettings.InitialMoney).ToList();
571  if (validSubs.Count > 0)
572  {
573  subList.Select(validSubs[Rand.Int(validSubs.Count)]);
574  }
575  }
576  }
577 
578  public override void CreateLoadMenu(IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
579  {
580  prevSaveFiles?.Clear();
581  prevSaveFiles = null;
582  loadGameContainer.ClearChildren();
583 
584  if (saveFiles == null)
585  {
586  //we don't need to log errors at this point,
587  //if any file fails to load the error will get logged when we try to extract the root from the game session doc later in the method
588  saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer, logLoadErrors: false);
589  }
590 
591  var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform), childAnchor: Anchor.TopCenter)
592  {
593  Stretch = true,
594  RelativeSpacing = 0.03f
595  };
596 
597  saveList = new GUIListBox(new RectTransform(Vector2.One, leftColumn.RectTransform))
598  {
599  PlaySoundOnSelect = true,
600  OnSelected = SelectSaveFile
601  };
602 
603  new GUIButton(new RectTransform(new Vector2(0.6f, 0.08f), leftColumn.RectTransform), TextManager.Get("showinfolder"))
604  {
605  OnClicked = (btn, userdata) =>
606  {
607  var saveFolder = SaveUtil.GetSaveFolder(SaveUtil.SaveType.Singleplayer);
608  try
609  {
610  ToolBox.OpenFileWithShell(saveFolder);
611  }
612  catch (Exception e)
613  {
614  new GUIMessageBox(
615  TextManager.Get("error"),
616  TextManager.GetWithVariables("showinfoldererror", ("[folder]", saveFolder), ("[errormessage]", e.Message)));
617  }
618  return true;
619  }
620  };
621 
622  foreach (var saveInfo in saveFiles)
623  {
624  var saveFrame = CreateSaveElement(saveInfo);
625  if (saveFrame == null) { continue; }
626 
627  XElement docRoot = SaveUtil.ExtractGameSessionRootElementFromSaveFile(saveInfo.FilePath);
628 
629  if (docRoot == null)
630  {
631  DebugConsole.ThrowError("Error loading save file \"" + saveInfo.FilePath + "\". The file may be corrupted.");
632  saveFrame.GetChild<GUITextBlock>().TextColor = GUIStyle.Red;
633  continue;
634  }
635  if (docRoot.GetChildElement("multiplayercampaign") != null)
636  {
637  //multiplayer campaign save in the wrong folder -> don't show the save
638  saveList.Content.RemoveChild(saveFrame);
639  continue;
640  }
641  if (!SaveUtil.IsSaveFileCompatible(docRoot))
642  {
643  saveFrame.GetChild<GUITextBlock>().TextColor = GUIStyle.Red;
644  saveFrame.ToolTip = TextManager.Get("campaignmode.incompatiblesave");
645  }
646  }
647 
648  SortSaveList();
649 
650  loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"))
651  {
652  OnClicked = (btn, obj) =>
653  {
654  if (saveList.SelectedData is not CampaignMode.SaveInfo saveInfo) { return false; }
655  if (string.IsNullOrWhiteSpace(saveInfo.FilePath)) { return false; }
656  LoadGame?.Invoke(saveInfo.FilePath);
657 
658  return true;
659  },
660  Enabled = false
661  };
662  }
663 
664  private bool SelectSaveFile(GUIComponent component, object obj)
665  {
666  if (obj is not CampaignMode.SaveInfo saveInfo) { return true; }
667 
668  string fileName = saveInfo.FilePath;
669 
670  XElement docRoot = SaveUtil.ExtractGameSessionRootElementFromSaveFile(fileName);
671  if (docRoot == null)
672  {
673  DebugConsole.ThrowError("Error loading save file \"" + fileName + "\". The file may be corrupted.");
674  return false;
675  }
676 
677  loadGameButton.Enabled = SaveUtil.IsSaveFileCompatible(docRoot);
678 
679  RemoveSaveFrame();
680 
681  string subName = saveInfo.SubmarineName;
682  LocalizedString saveTime = saveInfo.SaveTime
683  .Select(t => (LocalizedString)t.ToLocalUserString())
684  .Fallback(TextManager.Get("Unknown"));
685 
686  string mapseed = docRoot.GetAttributeString("mapseed", "unknown");
687 
688  var saveFileFrame = new GUIFrame(
689  new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight)
690  {
691  RelativeOffset = new Vector2(0.0f, 0.1f)
692  }, style: "InnerFrame")
693  {
694  UserData = "savefileframe"
695  };
696 
697  var titleText = new GUITextBlock(
698  new RectTransform(new Vector2(0.9f, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter)
699  {
700  RelativeOffset = new Vector2(0, 0.05f)
701  },
702  Path.GetFileNameWithoutExtension(fileName), font: GUIStyle.LargeFont, textAlignment: Alignment.Center);
703  titleText.Text = ToolBox.LimitString(titleText.Text, titleText.Font, titleText.Rect.Width);
704 
705  var layoutGroup = new GUILayoutGroup(
706  new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center)
707  {
708  RelativeOffset = new Vector2(0, 0.1f)
709  });
710 
711  new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
712  $"{TextManager.Get("Submarine")} : {subName}", font: GUIStyle.SmallFont);
713  new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
714  $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUIStyle.SmallFont);
715  new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform),
716  $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUIStyle.SmallFont);
717 
718  new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter)
719  {
720  RelativeOffset = new Vector2(0, 0.1f)
721  }, TextManager.Get("Delete"), style: "GUIButtonSmall")
722  {
723  UserData = saveInfo,
724  OnClicked = DeleteSave
725  };
726 
727  return true;
728  }
729 
730  private void RemoveSaveFrame()
731  {
732  GUIComponent prevFrame = null;
733  foreach (GUIComponent child in loadGameContainer.Children)
734  {
735  if (child.UserData as string != "savefileframe") continue;
736 
737  prevFrame = child;
738  break;
739  }
740  loadGameContainer.RemoveChild(prevFrame);
741  }
742  }
743 }
readonly GUIComponent newGameContainer
Stores information about the Character that is needed between rounds in the menu etc....
static readonly Identifier HumanSpeciesName
override bool Enabled
Definition: GUIButton.cs:27
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
virtual void RemoveChild(GUIComponent child)
Definition: GUIComponent.cs:87
virtual void ClearChildren()
virtual RichString ToolTip
virtual Rectangle Rect
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
Definition: GUIComponent.cs:49
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
Definition: GUIListBox.cs:440
override void ClearChildren()
Definition: GUIListBox.cs:1243
TextBoxEvent OnDeselected
Definition: GUITextBox.cs:17
static bool DebugDraw
Definition: GameMain.cs:29
static readonly PrefabCollection< JobPrefab > Prefabs
override void CreateLoadMenu(IEnumerable< CampaignMode.SaveInfo > saveFiles=null)
CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus
void UpdateSubList(IEnumerable< SubmarineInfo > submarines)
SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer)
static IEnumerable< SubmarineInfo > SavedSubmarines
CursorState
Definition: GUI.cs:40