Client LuaCsForBarotrauma
MainMenuScreen.cs
1 //#define TEST_REMOTE_CONTENT
2 
6 using Lidgren.Network;
7 using Microsoft.Xna.Framework;
8 using Microsoft.Xna.Framework.Graphics;
9 using RestSharp;
10 using System;
11 using System.Collections.Generic;
12 using System.Collections.Immutable;
13 using System.Diagnostics;
14 using Barotrauma.IO;
15 using System.Linq;
16 using System.Net;
17 using System.Threading;
18 using System.Threading.Tasks;
19 using System.Xml.Linq;
20 using Barotrauma.Steam;
21 
22 namespace Barotrauma
23 {
24  sealed class MainMenuScreen : Screen
25  {
26  private enum Tab
27  {
28  NewGame = 0,
29  LoadGame = 1,
30  HostServer = 2,
31  Settings = 3,
32  Tutorials = 4,
33  JoinServer = 5,
34  CharacterEditor = 6,
35  SubmarineEditor = 7,
36  Mods = 8,
37  Credits = 9,
38  Empty = 10
39  }
40 
41  private readonly GUIComponent buttonsParent;
42 
43  private readonly Dictionary<Tab, GUIFrame> menuTabs;
44 
45  private SinglePlayerCampaignSetupUI campaignSetupUI;
46 
47  private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
48  private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
49  private GUIDropDown languageDropdown, serverExecutableDropdown;
50  private readonly GUIButton joinServerButton, hostServerButton;
51 
52  private readonly GUIFrame modsButtonContainer;
53  private readonly GUIButton modsButton, modUpdatesButton;
54  private (DateTime WhenToRefresh, int Count) modUpdateStatus = (DateTime.Now, 0);
55  private static readonly TimeSpan ModUpdateInterval = TimeSpan.FromSeconds(60.0f);
56 
57  private readonly GameMain game;
58 
59  private GUIImage playstyleBanner;
60  private GUITextBlock playstyleDescription;
61 
62  private static string RemoteContentUrl => GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
63 
64  private readonly GUIComponent remoteContentContainer;
65  private XDocument remoteContentDoc;
66 
67  private Tab selectedTab = Tab.Empty;
68 
69  private Sprite backgroundSprite;
70 
71  private readonly GUIComponent titleText;
72 
73  private readonly CreditsPlayer creditsPlayer;
74 
75  public static readonly Queue<ulong> WorkshopItemsToUpdate = new Queue<ulong>();
76 
77  private GUIImage tutorialBanner;
78  private GUITextBlock tutorialHeader, tutorialDescription;
79  private GUIListBox tutorialList;
80 
81  private readonly GUITextBlock gameAnalyticsStatusText;
82 
83  private readonly GUILayoutGroup leftTextFooterLayout;
84  private readonly GUILayoutGroup rightTextFooterLayout;
85 
86  private GUIComponent versionMismatchWarning;
87 
88  #region Creation
89  public MainMenuScreen(GameMain game) : base()
90  {
91  leftTextFooterLayout = createTextFooter();
92  rightTextFooterLayout = createTextFooter();
93 
94  gameAnalyticsStatusText = createLeftText(TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.Consent.Unknown}"));
95  createLeftText("Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
96 
97  var privacyPolicyText = createRightText(TextManager.Get("privacypolicy").Fallback("Privacy policy"));
98  (Rectangle Rect, bool MouseOn) getPrivacyPolicyHoverRect()
99  {
100  var textSize = privacyPolicyText.Font.MeasureString(privacyPolicyText.Text);
101  var bottomRight = privacyPolicyText.Rect.Location.ToVector2()
102  + privacyPolicyText.TextPos
103  + privacyPolicyText.TextOffset;
104  var rect = new Rectangle((bottomRight - textSize).ToPoint(), textSize.ToPoint());
105  bool mouseOn = rect.Contains(PlayerInput.LatestMousePosition) && GUI.IsMouseOn(privacyPolicyText);
106  return (rect, mouseOn);
107  }
108  new GUICustomComponent(new RectTransform(Vector2.One, privacyPolicyText.RectTransform),
109  onUpdate: (dt, component) =>
110  {
111  var (_, mouseOn) = getPrivacyPolicyHoverRect();
112  if (mouseOn && PlayerInput.PrimaryMouseButtonClicked())
113  {
114  GameMain.ShowOpenUriPrompt("https://privacypolicy.daedalic.com");
115  }
116  },
117  onDraw: (sb, component) =>
118  {
119  var (rect, mouseOn) = getPrivacyPolicyHoverRect();
120  Color color = mouseOn ? Color.White : Color.White * 0.7f;
121  privacyPolicyText.TextColor = color;
122  GUI.DrawLine(sb, new Vector2(rect.Left, rect.Bottom), new Vector2(rect.Right, rect.Bottom), color);
123  });
124 
125  createRightText("© " + DateTime.Now.Year + " Undertow Games & FakeFish. All rights reserved.");
126  createRightText("© " + DateTime.Now.Year + " Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved.");
127 
128  GUILayoutGroup createTextFooter()
129  => new GUILayoutGroup(new RectTransform((1.0f, 0.06f), Frame.RectTransform, Anchor.BottomCenter))
130  {
131  ChildAnchor = Anchor.BottomLeft
132  };
133 
134  GUITextBlock createTextInFooter(GUILayoutGroup footer, LocalizedString str, Alignment textAlignment)
135  {
136  var textBlock = new GUITextBlock(
137  rectT: new RectTransform((1.0f, 0.3f), footer.RectTransform),
138  text: str,
139  textAlignment: textAlignment,
140  font: GUIStyle.SmallFont,
141  textColor: Color.White * 0.7f);
142  textBlock.RectTransform.SetAsFirstChild();
143  return textBlock;
144  }
145 
146  GUITextBlock createLeftText(LocalizedString str)
147  => createTextInFooter(leftTextFooterLayout, str, Alignment.BottomLeft);
148  GUITextBlock createRightText(LocalizedString str)
149  => createTextInFooter(rightTextFooterLayout, str, Alignment.BottomRight);
150 
151  GameMain.Instance.ResolutionChanged += () =>
152  {
153  SetMenuTabPositioning();
154  CreateHostServerFields();
155  bool prevMenuOpen = GUI.SettingsMenuOpen;
156  SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
157  GUI.SettingsMenuOpen = prevMenuOpen;
158  if (remoteContentDoc?.Root != null)
159  {
160  remoteContentContainer.ClearChildren();
161  try
162  {
163  foreach (var subElement in remoteContentDoc.Root.Elements())
164  {
165  GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
166  }
167  }
168  catch (Exception e)
169  {
170 #if DEBUG
171  DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
172 #endif
173  GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentParse:Exception", GameAnalyticsManager.ErrorSeverity.Error,
174  "Reading received remote main menu content failed. " + e.Message);
175  }
176  }
177  };
178 
179  versionMismatchWarning = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.065f), Frame.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(15)) }, style: "InnerFrame", color: GUIStyle.Red)
180  {
181  IgnoreLayoutGroups = true,
182  Visible = false
183  };
184  var versionMismatchContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), versionMismatchWarning.RectTransform, Anchor.Center), isHorizontal: true)
185  {
186  RelativeSpacing = 0.05f,
187  };
188  new GUIImage(new RectTransform(new Vector2(1.0f), versionMismatchContent.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUINotificationButton")
189  {
190  Color = GUIStyle.Orange
191  };
192  new GUITextBlock(new RectTransform(new Vector2(0.85f, 1.0f), versionMismatchContent.RectTransform),
193  TextManager.GetWithVariables("versionmismatchwarning",
194  ("[gameversion]", GameMain.Version.ToString()),
195  ("[contentversion]", ContentPackageManager.VanillaCorePackage.GameVersion.ToString())),
196  wrap: true)
197  {
198  TextColor = GUIStyle.Orange
199  };
200 
201  new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
202  { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
203  style: "TitleText")
204  {
205  Color = Color.Black * 0.5f,
206  CanBeFocused = false
207  };
208  titleText = new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
209  { RelativeOffset = new Vector2(0.08f, 0.05f) },
210  style: "TitleText");
211 
212  buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
213  {
214  AbsoluteOffset = new Point(50, 0)
215  })
216  {
217  Stretch = true,
218  RelativeSpacing = 0.02f
219  };
220 
221  remoteContentContainer = new GUIFrame(new RectTransform(Vector2.One, parent: Frame.RectTransform), style: null)
222  {
223  CanBeFocused = false
224  };
225 
226 #if TEST_REMOTE_CONTENT
227 
228  var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml");
229  if (doc?.Root != null)
230  {
231  foreach (var subElement in doc?.Root.Elements())
232  {
233  GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
234  }
235  }
236 #else
237  SpamServerFilters.RequestGlobalSpamFilter();
238  FetchRemoteContent();
239 #endif
240 
241  float labelHeight = 0.18f;
242 
243 
244  // === CAMPAIGN
245  var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true);
246 
247  new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon")
248  {
249  CanBeFocused = false
250  };
251 
252  //spacing
253  new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), campaignHolder.RectTransform), style: null);
254 
255  var campaignNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: campaignHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
256 
257  new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), campaignNavigation.RectTransform),
258  TextManager.Get("CampaignLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
259 
260  var campaignButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: campaignNavigation.RectTransform), style: "MainMenuGUIFrame");
261 
262  var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: campaignButtons.RectTransform))
263  {
264  Stretch = false,
265  RelativeSpacing = 0.035f
266  };
267 
268  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("TutorialButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
269  {
271  UserData = Tab.Tutorials,
272  OnClicked = (tb, userdata) =>
273  {
274  SelectTab(tb, userdata);
275  return true;
276  }
277  };
278 
279  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
280  {
282  UserData = Tab.LoadGame,
283  OnClicked = (tb, userdata) =>
284  {
285  SelectTab(tb, userdata);
286  return true;
287  }
288  };
289 
290  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("NewGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
291  {
293  UserData = Tab.NewGame,
294  OnClicked = (tb, userdata) =>
295  {
296  SelectTab(tb, userdata);
297  return true;
298  }
299  };
300 
301  // === MULTIPLAYER
302  var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true);
303 
304  new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), multiplayerHolder.RectTransform), "MainMenuMultiplayerIcon")
305  {
306  CanBeFocused = false
307  };
308 
309  //spacing
310  new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), multiplayerHolder.RectTransform), style: null);
311 
312  var multiplayerNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: multiplayerHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
313 
314  new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), multiplayerNavigation.RectTransform),
315  TextManager.Get("MultiplayerLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
316 
317  var multiplayerButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: multiplayerNavigation.RectTransform), style: "MainMenuGUIFrame");
318 
319  var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: multiplayerButtons.RectTransform))
320  {
321  Stretch = false,
322  RelativeSpacing = 0.035f
323  };
324 
325  joinServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("JoinServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
326  {
328  UserData = Tab.JoinServer,
329  OnClicked = (tb, userdata) =>
330  {
331  SelectTab(tb, userdata);
332  return true;
333  }
334  };
335  hostServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
336  {
338  UserData = Tab.HostServer,
339  OnClicked = (tb, userdata) =>
340  {
341  SelectTab(tb, userdata);
342  return true;
343  }
344  };
345 
346  // === CUSTOMIZE
347  var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true);
348 
349  new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), customizeHolder.RectTransform), "MainMenuCustomizeIcon")
350  {
351  CanBeFocused = false
352  };
353 
354  //spacing
355  new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), customizeHolder.RectTransform), style: null);
356 
357  var customizeNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: customizeHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
358 
359  new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), customizeNavigation.RectTransform),
360  TextManager.Get("CustomizeLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
361 
362  var customizeButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: customizeNavigation.RectTransform), style: "MainMenuGUIFrame");
363 
364  var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: customizeButtons.RectTransform))
365  {
366  Stretch = false,
367  RelativeSpacing = 0.035f
368  };
369 
370  modsButtonContainer = new GUIFrame(new RectTransform(Vector2.One, customizeList.RectTransform),
371  style: null);
372 
373  modsButton = new GUIButton(new RectTransform(Vector2.One, modsButtonContainer.RectTransform),
374  TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
375  {
377  Enabled = true,
378  UserData = Tab.Mods,
379  OnClicked = SelectTab
380  };
381 
382  modUpdatesButton = new GUIButton(new RectTransform(Vector2.One * 0.95f, modsButtonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
383  style: "GUIUpdateButton")
384  {
385  ToolTip = TextManager.Get("ModUpdatesAvailable"),
386  OnClicked = (_, _) =>
387  {
388  BulkDownloader.PrepareUpdates();
389  return false;
390  },
391  Visible = false
392  };
393 
394  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
395  {
397  UserData = Tab.SubmarineEditor,
398  OnClicked = (tb, userdata) =>
399  {
400  SelectTab(tb, userdata);
401  return true;
402  }
403  };
404 
405  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("CharacterEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
406  {
408  UserData = Tab.CharacterEditor,
409  OnClicked = (tb, userdata) =>
410  {
411  SelectTab(tb, userdata);
412  return true;
413  }
414  };
415 
416  // === OPTION
417  var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), parent: buttonsParent.RectTransform), isHorizontal: true);
418 
419  new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon")
420  {
421  CanBeFocused = false
422  };
423 
424  //spacing
425  new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null);
426 
427  var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) });
428 
429  var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.25f), parent: optionButtons.RectTransform))
430  {
431  Stretch = false,
432  RelativeSpacing = 0.035f
433  };
434 
435  var settingsButtonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), style: null);
436 
437  new GUIButton(new RectTransform(Vector2.One, settingsButtonContainer.RectTransform), TextManager.Get("SettingsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
438  {
440  UserData = Tab.Settings,
441  OnClicked = SelectTab
442  };
443 
444  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("EditorDisclaimerWikiLink"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
445  {
447  OnClicked = (button, userData) =>
448  {
449  string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value;
450  GameMain.ShowOpenUriPrompt(url, promptExtensionTag: "wikinotice");
451  return true;
452  }
453  };
454  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
455  {
457  UserData = Tab.Credits,
458  OnClicked = SelectTab
459  };
460  new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
461  {
463  OnClicked = QuitClicked
464  };
465 
466  //debug button for quickly starting a new round
467 #if DEBUG
468  new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 80) },
469  "Quickstart (dev)", style: "GUIButtonLarge", color: GUIStyle.Red)
470  {
471  IgnoreLayoutGroups = true,
472  UserData = Tab.Empty,
473  OnClicked = (tb, userdata) =>
474  {
475  SelectTab(tb, userdata);
476 
477  QuickStart();
478 
479  return true;
480  }
481  };
482  new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 130) },
483  "Profiling", style: "GUIButtonLarge", color: GUIStyle.Red)
484  {
485  IgnoreLayoutGroups = true,
486  UserData = Tab.Empty,
487  ToolTip = "Enables performance indicators and starts the game with a fixed sub, crew and level to make it easier to compare the performance between sessions.",
488  OnClicked = (tb, userdata) =>
489  {
490  SelectTab(tb, userdata);
491 
492  QuickStart(fixedSeed: true);
493  GameMain.ShowPerf = true;
494  GameMain.ShowFPS = true;
495 
496  return true;
497  }
498  };
499  new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 180) },
500  "Join Localhost", style: "GUIButtonLarge", color: GUIStyle.Red)
501  {
502  IgnoreLayoutGroups = true,
503  UserData = Tab.Empty,
504  ToolTip = "Connects to a locally hosted dedicated server, assuming default port.",
505  OnClicked = (tb, userdata) =>
506  {
507  SelectTab(tb, userdata);
508 
509  GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
510  new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
511 
512  return true;
513  }
514  };
515 #endif
516  new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) },
517  $"Open LuaCs Settings", style: "MainMenuGUIButton", color: GUIStyle.Red)
518  {
519  IgnoreLayoutGroups = true,
520  OnClicked = (tb, userdata) =>
521  {
522  LuaCsSettingsMenu.Open(Frame.RectTransform);
523  return true;
524  }
525  };
526 
527  string version = File.Exists(LuaCsSetup.VersionFile) ? File.ReadAllText(LuaCsSetup.VersionFile) : "Github";
528 
529  new GUITextBlock(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(10, 10) }, $"Using LuaCsForBarotrauma revision {AssemblyInfo.GitRevision} version {version}", Color.Red)
530  {
531  IgnoreLayoutGroups = false
532  };
533 
534  var minButtonSize = new Point(120, 20);
535  var maxButtonSize = new Point(480, 80);
536 
537  var relativeSize = new Vector2(0.6f, 0.65f);
538  var minSize = new Point(600, 400);
539  var maxSize = new Point(2000, 1500);
540  var anchor = Anchor.Center;
541  var pivot = Pivot.Center;
542  Vector2 relativeOffset = new Vector2(0.05f, 0.0f);
543 
544  menuTabs = new Dictionary<Tab, GUIFrame>
545  {
546  [Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
547  style: null)
548  {
549  CanBeFocused = false
550  },
551  [Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
552  [Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
553  };
554 
555  CreateCampaignSetupUI();
556 
557  var hostServerScale = new Vector2(0.7f, 1.2f);
558  menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform(
559  Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
560  { RelativeOffset = relativeOffset });
561 
562  CreateHostServerFields();
563 
564  //----------------------------------------------------------------------
565 
566  menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
567  CreateTutorialTab();
568 
569  this.game = game;
570 
571  menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, Frame.RectTransform, Anchor.Center), style: null)
572  {
573  CanBeFocused = false
574  };
575  var blockerFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
576  {
577  CanBeFocused = false
578  };
579  blockerFrame.RectTransform.RelativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
580 
581  var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
582  creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
583  creditsPlayer.CloseButton.OnClicked = (btn, userdata) =>
584  {
585  SelectTab(Tab.Empty);
586  return true;
587  };
588 
589  SetMenuTabPositioning();
590  SelectTab(Tab.Empty);
591  }
592 
593  private void SetMenuTabPositioning()
594  {
595  foreach (GUIFrame menuTab in menuTabs.Values)
596  {
597  var anchor = GUI.IsUltrawide ? Anchor.Center : Anchor.CenterRight;
598  var pivot = GUI.IsUltrawide ? Pivot.Center : Pivot.CenterRight;
599  Vector2 relativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
600  menuTab.RectTransform.SetPosition(anchor, pivot);
601  menuTab.RectTransform.RelativeOffset = relativeOffset;
602  }
603  }
604 
605  private void CreateTutorialTab()
606  {
607  var tutorialInnerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.Tutorials].RectTransform, Anchor.Center), style: "InnerFrame");
608  var tutorialContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tutorialInnerFrame.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.02f, Stretch = true };
609 
610  tutorialList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), tutorialContent.RectTransform))
611  {
612  PlaySoundOnSelect = true,
613  OnSelected = (component, obj) =>
614  {
615  SelectTutorial(obj as Tutorial);
616  return true;
617  }
618  };
619  var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
620  var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
621  tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true);
622 
623  var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
624  var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
625  {
626  AbsoluteSpacing = GUI.IntScale(10)
627  };
628 
629  tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont);
630  tutorialDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, wrap: true);
631 
632  var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton"))
633  {
634  IgnoreLayoutGroups = true,
635  OnClicked = (component, obj) =>
636  {
637  (tutorialList.SelectedData as Tutorial)?.Start();
638  return true;
639  }
640  };
641 
642  Tutorial firstTutorial = null;
643  foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
644  {
645  var tutorial = new Tutorial(tutorialPrefab);
646  firstTutorial ??= tutorial;
647  var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), tutorialList.Content.RectTransform), tutorial.DisplayName)
648  {
649  Padding = new Vector4(30.0f * GUI.Scale, 0,0,0),
650  UserData = tutorial
651  };
652  tutorialText.RectTransform.MinSize = new Point(0, (int)(tutorialText.TextSize.Y * 2));
653  }
654  GUITextBlock.AutoScaleAndNormalize(tutorialList.Content.Children.Select(c => c as GUITextBlock));
655  tutorialList.Select(firstTutorial);
656  }
657 
658  private void SelectTutorial(Tutorial tutorial)
659  {
660  tutorialHeader.Text = tutorial.DisplayName;
661  tutorialHeader.CalculateHeightFromText();
662  tutorialDescription.Text = tutorial.Description;
663  tutorialDescription.CalculateHeightFromText();
664  (tutorialDescription.Parent as GUILayoutGroup)?.Recalculate();
666  tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner;
667  tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White;
668  }
669 
670  public static void UpdateInstanceTutorialButtons()
671  {
672  if (GameMain.MainMenuScreen is not MainMenuScreen menuScreen) { return; }
673  menuScreen.tutorialList.ClearChildren();
674  menuScreen.CreateTutorialTab();
675  }
676 
677  #endregion
678 
679  #region Selection
680  public override void Select()
681  {
682  GameMain.LuaCs.Stop();
683 
684  ResetModUpdateButton();
685 
686  if (WorkshopItemsToUpdate.Any())
687  {
688  while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
689  {
690  SteamManager.Workshop.OnItemDownloadComplete(workshopId, forceInstall: true);
691  }
692  }
693 
694  GUI.PreventPauseMenuToggle = false;
695 
696  base.Select();
697 
698  if (GameMain.Client != null)
699  {
700  GameMain.Client.Quit();
701  GameMain.Client = null;
702  }
703 
705  Submarine.Unload();
706 
707  versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;
708 
709  ResetButtonStates(null);
710 
711  Eos.EosAccount.ExecuteAfterLogin(AchievementManager.SyncBetweenPlatforms);
712  }
713 
714  public override void Deselect()
715  {
716  base.Deselect();
717  SelectTab(null, 0);
718  }
719 
720  private bool SelectTab(GUIButton button, object obj)
721  {
722  titleText.Visible = true;
723  if (obj is Tab tab)
724  {
725  SelectTab(tab);
726  }
727  else
728  {
729  SelectTab(Tab.Empty);
730  }
731  return true;
732  }
733 
734  private bool SelectTab(Tab tab)
735  {
736  titleText.Visible = true;
737  SettingsMenu.Instance?.Close();
738  #warning TODO: reimplement settings confirmation dialog
739 
740  switch (tab)
741  {
742  case Tab.NewGame:
743  if (GameSettings.CurrentConfig.TutorialSkipWarning)
744  {
745  selectedTab = Tab.Empty;
746  ShowTutorialSkipWarning(Tab.NewGame);
747  return true;
748  }
749  campaignSetupUI.RandomizeCrew();
750  campaignSetupUI.SetPage(0);
751  campaignSetupUI.CreateDefaultSaveName();
752  campaignSetupUI.RandomizeSeed();
753  campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines);
754  break;
755  case Tab.LoadGame:
756  campaignSetupUI.CreateLoadMenu();
757  break;
758  case Tab.Settings:
759  SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
760  break;
761  case Tab.JoinServer:
762  if (GameSettings.CurrentConfig.TutorialSkipWarning)
763  {
764  selectedTab = Tab.Empty;
765  ShowTutorialSkipWarning(Tab.JoinServer);
766  return true;
767  }
768  GameMain.ServerListScreen.Select();
769  break;
770  case Tab.HostServer:
771  if (GameSettings.CurrentConfig.TutorialSkipWarning)
772  {
773  selectedTab = Tab.Empty;
774  ShowTutorialSkipWarning(tab);
775  return true;
776  }
777  serverExecutableDropdown.ListBox.Content.Children.ToArray()
778  .Where(c => c.UserData is ServerExecutableFile f && !ContentPackageManager.EnabledPackages.All.Contains(f.ContentPackage))
779  .ForEach(serverExecutableDropdown.ListBox.RemoveChild);
780  var newServerExes
781  = ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<ServerExecutableFile>())
782  .Where(f => serverExecutableDropdown.ListBox.Content.Children.None(c => c.UserData == f))
783  .ToArray();
784  foreach (var newServerExe in newServerExes)
785  {
786  var serverExeEntry = serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe);
787  if (newServerExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
788  {
789  serverExeEntry.ToolTip =
790  TextManager.GetWithVariables("versionmismatchwarning",
791  ("[gameversion]", newServerExe.ContentPackage.GameVersion.ToString()),
792  ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString()));
793  if (serverExeEntry is GUITextBlock serverExeText)
794  {
795  serverExeText.TextColor = GUIStyle.Red;
796  }
797  }
798  }
799  serverExecutableDropdown.ListBox.Content.Children.ForEach(c =>
800  {
801  c.RectTransform.RelativeSize = (1.0f, c.RectTransform.RelativeSize.Y);
802  c.ForceLayoutRecalculation();
803  });
804  bool serverExePickable = serverExecutableDropdown.ListBox.Content.CountChildren > 1;
805  bool wasPickable = serverExecutableDropdown.Parent.Visible;
806  if (wasPickable != serverExePickable)
807  {
808  serverExecutableDropdown.Parent.Visible = serverExePickable;
809  serverExecutableDropdown.Parent.IgnoreLayoutGroups = !serverExePickable;
810  (serverExecutableDropdown.Parent.Parent as GUILayoutGroup)?.Recalculate();
811  if (serverExecutableDropdown.SelectedComponent is null)
812  {
813  serverExecutableDropdown.Select(0);
814  }
815  }
816  break;
817  case Tab.Tutorials:
818  UpdateTutorialList();
819  break;
820  case Tab.CharacterEditor:
821  Submarine.MainSub = null;
822  CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.CharacterEditorScreen));
823  break;
824  case Tab.SubmarineEditor:
825  CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen));
826  break;
827  case Tab.Mods:
828  var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
829  settings.SelectTab(SettingsMenu.Tab.Mods);
830  tab = Tab.Settings;
831  break;
832  case Tab.Credits:
833  titleText.Visible = false;
834  creditsPlayer.Restart();
835  break;
836  case Tab.Empty:
837  titleText.Visible = true;
838  selectedTab = Tab.Empty;
839  break;
840  }
841 
842  selectedTab = tab;
843  leftTextFooterLayout.Visible = tab != Tab.Credits;
844  rightTextFooterLayout.Visible = tab != Tab.Credits;
845 
846  foreach (var tabFrame in menuTabs.Values)
847  {
848  tabFrame.Visible = false;
849  }
850  if (menuTabs.TryGetValue(selectedTab, out var visibleTab)) { visibleTab.Visible = true; }
851 
852  return true;
853  }
854 
855  private IEnumerable<CoroutineStatus> SelectScreenWithWaitCursor(Screen screen)
856  {
857  GUI.SetCursorWaiting();
858  //tiny delay to get the cursor to render
859  yield return new WaitForSeconds(0.02f);
860  GUI.ClearCursorWait();
861  screen.Select();
862  yield return CoroutineStatus.Success;
863  }
864 
865  public bool ReturnToMainMenu(GUIButton button, object obj)
866  {
867  GUI.PreventPauseMenuToggle = false;
868 
869  if (Selected != this)
870  {
871  Select();
872  }
873  else
874  {
875  ResetButtonStates(button);
876  }
877 
878  SelectTab(null, 0);
879 
880  return true;
881  }
882 
883  private void ResetButtonStates(GUIButton button)
884  {
885  foreach (GUIComponent child in buttonsParent.Children)
886  {
887  GUIButton otherButton = child as GUIButton;
888  if (otherButton == null || otherButton == button) continue;
889 
890  otherButton.Selected = false;
891  }
892  }
893 #endregion
894 
895  public void ResetModUpdateButton()
896  {
897  modUpdateStatus = (DateTime.Now, 0);
898  modUpdatesButton.Visible = false;
899  }
900 
901  public void QuickStart(bool fixedSeed = false, Identifier sub = default, float difficulty = 50, LevelGenerationParams levelGenerationParams = null)
902  {
903  if (fixedSeed)
904  {
905  Rand.SetSyncedSeed(1);
906  Rand.SetLocalRandom(1);
907  }
908 
909  SubmarineInfo selectedSub = null;
910  Identifier subName = sub.IfEmpty(GameSettings.CurrentConfig.QuickStartSub);
911  if (!subName.IsEmpty)
912  {
913  DebugConsole.NewMessage($"Loading the predefined quick start sub \"{subName}\"", Color.White);
914 
915  selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
916  if (selectedSub == null)
917  {
918  DebugConsole.NewMessage($"Cannot find a sub that matches the name \"{subName}\".", Color.Red);
919  }
920  }
921  if (selectedSub == null)
922  {
923  DebugConsole.NewMessage("Loading a random sub.", Color.White);
924  var subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus));
925  selectedSub = subs.ElementAt(Rand.Int(subs.Count()));
926  }
927  var gamesession = new GameSession(
928  selectedSub,
929  Option.None,
931  missionPrefabs: null);
932  //(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8));
933  gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty, levelGenerationParams);
935  // TODO: modding support
936  Identifier[] jobIdentifiers = new Identifier[] {
937  "captain".ToIdentifier(),
938  "engineer".ToIdentifier(),
939  "mechanic".ToIdentifier(),
940  "securityofficer".ToIdentifier(),
941  "medicaldoctor".ToIdentifier() };
942  foreach (Identifier job in jobIdentifiers)
943  {
944  var jobPrefab = JobPrefab.Get(job);
945  var variant = Rand.Range(0, jobPrefab.Variants);
946  var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant);
947  if (characterInfo.Job == null)
948  {
949  DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!");
950  }
951  gamesession.CrewManager.AddCharacterInfo(characterInfo);
952  }
953  gamesession.CrewManager.InitSinglePlayerRound();
954  }
955 
956  private void ShowTutorialSkipWarning(Tab tabToContinueTo)
957  {
958  var tutorialSkipWarning = new GUIMessageBox("", TextManager.Get("tutorialskipwarning"), new LocalizedString[] { TextManager.Get("tutorialwarningskiptutorials"), TextManager.Get("tutorialwarningplaytutorials") });
959 
960  GUIButton.OnClickedHandler proceedToTab(Tab tab)
961  => (btn, userdata) =>
962  {
963  var config = GameSettings.CurrentConfig;
964  config.TutorialSkipWarning = false;
965  GameSettings.SetCurrentConfig(config);
966  GameSettings.SaveCurrentConfig();
967  tutorialSkipWarning.Close();
968  SelectTab(tab);
969  return true;
970  };
971 
972  tutorialSkipWarning.Buttons[0].OnClicked += proceedToTab(tabToContinueTo);
973  tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials);
974  }
975 
976  public override void AddToGUIUpdateList()
977  {
978  base.AddToGUIUpdateList();
979  switch (selectedTab)
980  {
981  case Tab.NewGame:
982  campaignSetupUI.CharacterMenus?.ForEach(static m => m.AddToGUIUpdateList());
983  break;
984  }
985  }
986 
987  private void UpdateTutorialList()
988  {
989  foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
990  {
991  var tutorial = (Tutorial)tutorialText.UserData;
992  if (CompletedTutorials.Instance.Contains(tutorial.Identifier) && tutorialText.GetChild<GUIImage>() == null)
993  {
994  new GUIImage(new RectTransform(new Point((int)(tutorialText.Padding.X * 0.8f)), tutorialText.RectTransform, Anchor.CenterLeft), style: "ObjectiveIndicatorCompleted");
995  }
996  }
997  }
998 
999  private bool ChangeMaxPlayers(GUIButton button, object obj)
1000  {
1001  int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
1002  currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers + (int)button.UserData, 1, NetConfig.MaxPlayers);
1003  maxPlayersBox.Text = currMaxPlayers.ToString();
1004  return true;
1005  }
1006 
1007  private void TryStartServer()
1008  {
1009  if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
1010  {
1011  var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
1012  var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
1013  waitBox.Buttons[0].OnClicked += (btn, userdata) =>
1014  {
1015  CoroutineManager.StopCoroutines(waitCoroutine);
1016  return true;
1017  };
1018  }
1019  else
1020  {
1021  StartServer();
1022  }
1023  }
1024 
1025  private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
1026  {
1027  LocalizedString originalText = messageBox.Text.Text;
1028  int doneCount = 0;
1029  do
1030  {
1031  doneCount = SubmarineInfo.SavedSubmarines.Count(s => !s.CalculatingHash);
1032  messageBox.Text.Text = originalText + $" ({doneCount}/{SubmarineInfo.SavedSubmarines.Count()})";
1033  yield return CoroutineStatus.Running;
1034  } while (doneCount < SubmarineInfo.SavedSubmarines.Count());
1035  messageBox.Close();
1036  StartServer();
1037  yield return CoroutineStatus.Success;
1038  }
1039 
1040  private void StartServer()
1041  {
1042  string name = serverNameBox.Text;
1043 
1044  GameMain.ResetNetLobbyScreen();
1045  try
1046  {
1047  string fileName;
1048  if (serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f &&
1049  f.ContentPackage != GameMain.VanillaContent)
1050  {
1051  fileName = Path.Combine(
1052  Path.GetDirectoryName(f.Path.Value),
1053  Path.GetFileNameWithoutExtension(f.Path.Value));
1054 #if WINDOWS
1055  fileName += ".exe";
1056 #endif
1057  }
1058  else
1059  {
1060 #if WINDOWS
1061  fileName = "DedicatedServer.exe";
1062 #else
1063  fileName = "./DedicatedServer";
1064 #endif
1065  }
1066 
1067  var arguments = new List<string>
1068  {
1069  "-name", name,
1070  "-public", isPublicBox.Selected.ToString(),
1071  "-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
1072  "-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
1073  "-karmaenabled", (!karmaBox.Selected).ToString(),
1074  "-maxplayers", maxPlayersBox.Text,
1075  "-language", languageDropdown.SelectedData.ToString()
1076  };
1077 
1078  if (!string.IsNullOrWhiteSpace(passwordBox.Text))
1079  {
1080  arguments.Add("-password");
1081  arguments.Add(passwordBox.Text);
1082  }
1083  else
1084  {
1085  arguments.Add("-nopassword");
1086  }
1087 
1088  var puids = EosInterface.IdQueries.GetLoggedInPuids();
1089 
1090  var endpoints = new List<Endpoint>();
1091  if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
1092  {
1093  endpoints.Add(new SteamP2PEndpoint(steamId));
1094  }
1095  if (puids.Length > 0)
1096  {
1097  endpoints.Add(new EosP2PEndpoint(puids[0]));
1098  }
1099  if (endpoints.Count == 0)
1100  {
1101  endpoints.Add(new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort));
1102  }
1103 
1104  if (endpoints.First() is P2PEndpoint firstEndpoint)
1105  {
1106  arguments.Add("-endpoint");
1107  arguments.Add(firstEndpoint.StringRepresentation);
1108  }
1109  int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
1110  arguments.Add("-ownerkey");
1111  arguments.Add(ownerKey.ToString());
1112 
1113  var processInfo = new ProcessStartInfo
1114  {
1115  FileName = fileName,
1116  WorkingDirectory = Directory.GetCurrentDirectory(),
1117 #if !DEBUG
1118  CreateNoWindow = true,
1119  UseShellExecute = false,
1120  WindowStyle = ProcessWindowStyle.Hidden
1121 #endif
1122  };
1123  arguments.ForEach(processInfo.ArgumentList.Add);
1124  ChildServerRelay.Start(processInfo);
1125  Thread.Sleep(1000); //wait until the server is ready before connecting
1126 
1127  GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
1128  SteamManager.GetUsername().FallbackNullOrEmpty(name)),
1129  endpoints.ToImmutableArray(),
1130  name,
1131  Option.Some(ownerKey));
1132  }
1133  catch (Exception e)
1134  {
1135  DebugConsole.ThrowError("Failed to start server", e);
1136  }
1137  }
1138 
1139  private bool QuitClicked(GUIButton button, object obj)
1140  {
1141  game.Exit();
1142  return true;
1143  }
1144 
1145  private void UpdateOutOfDateWorkshopItemCount()
1146  {
1147  if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
1148  if (!SteamManager.IsInitialized) { return; }
1149 
1150  var installedPackages = ContentPackageManager.WorkshopPackages;
1151 
1152  var ids = SteamManager.Workshop.GetSubscribedItemIds()
1153  .Select(id => id.Value)
1154  .Union(installedPackages
1155  .Select(pkg => pkg.UgcId)
1156  .NotNone()
1157  .OfType<SteamWorkshopId>()
1158  .Select(id => id.Value));
1159  var count = ids
1160  // Deliberately construct Steamworks.Ugc.Item directly
1161  // to not immediately generate a Workshop data request
1162  .Select(id => new Steamworks.Ugc.Item(id))
1163  .Count(item =>
1164  installedPackages.FirstOrDefault(p
1165  => p.UgcId.TryUnwrap(out SteamWorkshopId id) && id.Value == item.Id)
1166  is { } pkg
1167  // Checking that this item is downloading, waiting to be downloaded
1168  // or is newer than the currently installed copy should be good enough,
1169  // and should still not make a Workshop data request
1170  && (item.IsDownloading
1171  || item.IsDownloadPending
1172  || (item.InstallTime.TryGetValue(out var workshopInstallTime)
1173  && pkg.InstallTime.TryUnwrap(out var localInstallTime)
1174  && localInstallTime.ToUtcValue() < workshopInstallTime)));
1175 
1176  modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
1177  }
1178 
1179  private static bool CanHostServer()
1180  => EosInterface.IdQueries.IsLoggedIntoEosConnect
1181  || SteamManager.IsInitialized
1182  || AssemblyInfo.CurrentConfiguration == AssemblyInfo.Configuration.Debug;
1183 
1184  public override void Update(double deltaTime)
1185  {
1186  hostServerButton.Enabled = CanHostServer();
1187 
1188  gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
1189 
1190  UpdateOutOfDateWorkshopItemCount();
1191  modUpdatesButton.Visible = modUpdateStatus.Count > 0;
1192 
1193  if (modUpdatesButton.Visible)
1194  {
1195  var modButtonLabelSize =
1196  modsButton.Font.MeasureString(modsButton.Text).ToPoint()
1197  + new Point(GUI.IntScale(25));
1198  modUpdatesButton.RectTransform.AbsoluteOffset =
1199  (modButtonLabelSize.X, modsButton.Rect.Height / 2 - modUpdatesButton.Rect.Height / 2);
1200  }
1201 
1202  switch (selectedTab)
1203  {
1204  case Tab.NewGame:
1205  campaignSetupUI.Update();
1206  break;
1207  }
1208  }
1209 
1210  public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
1211  {
1212  graphics.Clear(Color.Black);
1213 
1214  if (backgroundSprite == null)
1215  {
1216 #if UNSTABLE
1217  backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
1218 #endif
1219  if (GUIStyle.GetComponentStyle("MainMenuBackground") is { } mainMenuStyle &&
1220  mainMenuStyle.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var sprites))
1221  {
1222  backgroundSprite = sprites.GetRandomUnsynced()?.Sprite;
1223  }
1224  backgroundSprite ??= LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(0);
1225  }
1226 
1227  var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
1228  float vignetteScale = Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y);
1229 
1230  Rectangle drawArea = new Rectangle(
1231  (int)(vignette.size.X * vignetteScale / 2), 0,
1232  (int)(GameMain.GraphicsWidth - vignette.size.X * vignetteScale / 2), GameMain.GraphicsHeight);
1233 
1234  if (backgroundSprite?.Texture != null)
1235  {
1236  GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White, drawArea);
1237  }
1238 
1239  if (vignette != null)
1240  {
1241  vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
1242  }
1243  }
1244 
1245  public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
1246  {
1247  spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
1248 
1249  DrawBackground(graphics, spriteBatch);
1250 
1251  GUI.Draw(Cam, spriteBatch);
1252 
1253  spriteBatch.End();
1254  }
1255 
1256  private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings)
1257  {
1258  if (string.IsNullOrEmpty(savePath)) { return; }
1259 
1260  var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
1261  if (existingSaveFiles.Any(s => s.FilePath == savePath))
1262  {
1263  new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText"));
1264  return;
1265  }
1266 
1267  if (selectedSub == null)
1268  {
1269  new GUIMessageBox(TextManager.Get("SubNotSelected"), TextManager.Get("SelectSubRequest"));
1270  return;
1271  }
1272 
1273  if (!Directory.Exists(SaveUtil.TempPath))
1274  {
1275  Directory.CreateDirectory(SaveUtil.TempPath, catchUnauthorizedAccessExceptions: true);
1276  }
1277 
1278  try
1279  {
1280  File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), overwrite: true, catchUnauthorizedAccessExceptions: false);
1281  }
1282  catch (System.IO.IOException e)
1283  {
1284  DebugConsole.ThrowError("Copying the file \"" + selectedSub.FilePath + "\" failed. The file may have been deleted or in use by another process. Try again or select another submarine.", e);
1285  GameAnalyticsManager.AddErrorEventOnce(
1286  "MainMenuScreen.StartGame:IOException" + selectedSub.Name,
1287  GameAnalyticsManager.ErrorSeverity.Error,
1288  "Copying a submarine file failed. " + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace());
1289  return;
1290  }
1291 
1292  GameMain.LuaCs.CheckInitialize();
1293 
1294  selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
1295 
1296  GameMain.GameSession = new GameSession(selectedSub, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.SinglePlayerCampaign, settings, mapSeed);
1297  GameMain.GameSession.CrewManager.ClearCharacterInfos();
1298  foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
1299  {
1300  GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo);
1301  }
1302  ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
1303  }
1304 
1305  private void LoadGame(string path, Option<uint> backupIndex)
1306  {
1307  if (string.IsNullOrWhiteSpace(path)) return;
1308 
1309  GameMain.LuaCs.CheckInitialize();
1310 
1311  try
1312  {
1313  CampaignDataPath dataPath =
1314  backupIndex.TryUnwrap(out uint index)
1315  ? new CampaignDataPath(loadPath: SaveUtil.GetBackupPath(path, index), path)
1316  : CampaignDataPath.CreateRegular(path);
1317 
1318  SaveUtil.LoadGame(dataPath);
1319  }
1320  catch (Exception e)
1321  {
1322  DebugConsole.ThrowError("Loading save \"" + path + "\" failed", e);
1323  return;
1324  }
1325 
1326  //TODO
1327  //GameMain.LobbyScreen.Select();
1328  }
1329 
1330 #region UI Methods
1331  private void CreateCampaignSetupUI()
1332  {
1333  menuTabs[Tab.NewGame].ClearChildren();
1334  menuTabs[Tab.LoadGame].ClearChildren();
1335 
1336  var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.NewGame].RectTransform, Anchor.Center))
1337  {
1338  Stretch = true,
1339  RelativeSpacing = 0.02f
1340  };
1341  var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center),
1342  style: "InnerFrame");
1343 
1344  var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) },
1345  style: null);
1346 
1347  campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame)
1348  {
1349  LoadGame = LoadGame,
1350  StartNewGame = StartGame
1351  };
1352  }
1353 
1354  private void CreateHostServerFields()
1355  {
1356  menuTabs[Tab.HostServer].ClearChildren();
1357 
1358  var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");
1359 
1360  var name = serverSettings.GetAttributeString("name", "");
1361  var password = serverSettings.GetAttributeString("password", "");
1362  var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
1363  var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);
1364 
1365  int maxPlayersElement = serverSettings.GetAttributeInt("maxplayers", 8);
1366  if (maxPlayersElement > NetConfig.MaxPlayers)
1367  {
1368  DebugConsole.AddWarning($"Setting the maximum amount of players to {maxPlayersElement} failed due to exceeding the limit of {NetConfig.MaxPlayers} players per server. Using the maximum of {NetConfig.MaxPlayers} instead.");
1369  }
1370  int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers);
1371 
1372  var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", true);
1373  var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual);
1374 
1375  Vector2 textLabelSize = new Vector2(1.0f, 0.05f);
1376  Alignment textAlignment = Alignment.CenterLeft;
1377  Vector2 textFieldSize = new Vector2(0.5f, 1.0f);
1378  Vector2 tickBoxSize = new Vector2(0.4f, 0.04f);
1379  var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.95f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
1380  {
1381  RelativeSpacing = 0.01f,
1382  Stretch = true
1383  };
1384  GUIComponent parent = content;
1385 
1386  var header = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) { ForceUpperCase = ForceUpperCase.Yes };
1387  header.RectTransform.IsFixedSize = true;
1388 
1389  //play style -----------------------------------------------------
1390 
1391  var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
1392 
1393  playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
1394  GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: true)
1395  {
1396  UserData = PlayStyle.Serious
1397  };
1398  float bannerAspectRatio = (float) playstyleBanner.Sprite.SourceRect.Width / playstyleBanner.Sprite.SourceRect.Height;
1399  playstyleBanner.RectTransform.NonScaledSize = new Point(playstyleBanner.Rect.Width, (int)(playstyleBanner.Rect.Width / bannerAspectRatio));
1400  playstyleBanner.RectTransform.IsFixedSize = true;
1401  new GUIFrame(new RectTransform(playstyleBanner.Rect.Size + new Point(1), playstyleBanner.RectTransform, Anchor.Center), "InnerGlow", color: Color.Black);
1402 
1403  new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.05f), playstyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.03f) },
1404  "playstyle name goes here", font: GUIStyle.SmallFont, textAlignment: Alignment.Center, textColor: Color.White, style: "GUISlopedHeader");
1405 
1406  new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterLeft)
1407  { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
1408  style: "UIToggleButton")
1409  {
1410  OnClicked = (btn, userdata) =>
1411  {
1412  int playStyleIndex = (int)playstyleBanner.UserData - 1;
1413  if (playStyleIndex < 0) { playStyleIndex = Enum.GetValues(typeof(PlayStyle)).Length - 1; }
1414  SetServerPlayStyle((PlayStyle)playStyleIndex);
1415  return true;
1416  }
1417  }.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
1418 
1419  new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterRight)
1420  { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
1421  style: "UIToggleButton")
1422  {
1423  OnClicked = (btn, userdata) =>
1424  {
1425  int playStyleIndex = (int)playstyleBanner.UserData + 1;
1426  if (playStyleIndex >= Enum.GetValues(typeof(PlayStyle)).Length) { playStyleIndex = 0; }
1427  SetServerPlayStyle((PlayStyle)playStyleIndex);
1428  return true;
1429  }
1430  };
1431 
1432  LocalizedString longestPlayStyleStr = "";
1433  foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
1434  {
1435  LocalizedString playStyleStr = TextManager.Get("servertagdescription." + playStyle);
1436  if (playStyleStr.Length > longestPlayStyleStr.Length) { longestPlayStyleStr = playStyleStr; }
1437  }
1438 
1439  playstyleDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), playstyleContainer.RectTransform, Anchor.BottomCenter),
1440  longestPlayStyleStr, style: null, wrap: true)
1441  {
1442  Color = Color.Black * 0.8f,
1443  TextColor = GUIStyle.GetComponentStyle("GUITextBlock").TextColor
1444  };
1445  playstyleDescription.Padding = Vector4.One * 10.0f * GUI.Scale;
1446  playstyleDescription.CalculateHeightFromText(padding: (int)(15 * GUI.Scale));
1447  playstyleDescription.RectTransform.NonScaledSize = new Point(playstyleDescription.Rect.Width, playstyleDescription.Rect.Height);
1448  playstyleDescription.RectTransform.IsFixedSize = true;
1449  playstyleContainer.RectTransform.NonScaledSize = new Point(playstyleContainer.Rect.Width, playstyleBanner.Rect.Height + playstyleDescription.Rect.Height);
1450  playstyleContainer.RectTransform.IsFixedSize = true;
1451 
1452  SetServerPlayStyle(selectedPlayStyle);
1453 
1454  //other settings -----------------------------------------------------
1455 
1456  //spacing
1457  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
1458 
1459  var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment);
1460  serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment)
1461  {
1462  MaxTextLength = NetConfig.ServerNameMaxLength,
1463  OverflowClip = true
1464  };
1465  label.RectTransform.IsFixedSize = true;
1466 
1467  var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment);
1468  var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
1469  {
1470  Stretch = true,
1471  RelativeSpacing = 0.1f
1472  };
1473  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
1474  {
1475  UserData = -1,
1476  OnClicked = ChangeMaxPlayers,
1477  ClickSound = GUISoundType.Decrease
1478  };
1479  maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
1480  {
1481  Text = maxPlayers.ToString()
1482  };
1483  maxPlayersBox.OnEnterPressed += (GUITextBox sender, string text) =>
1484  {
1485  maxPlayersBox.Deselect();
1486  return true;
1487  };
1488  maxPlayersBox.OnDeselected += (GUITextBox sender, Microsoft.Xna.Framework.Input.Keys key) =>
1489  {
1490  int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
1491  currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers, 1, NetConfig.MaxPlayers);
1492  maxPlayersBox.Text = currMaxPlayers.ToString();
1493  };
1494  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
1495  {
1496  UserData = 1,
1497  OnClicked = ChangeMaxPlayers,
1498  ClickSound = GUISoundType.Increase
1499  };
1500  maxPlayersLabel.RectTransform.IsFixedSize = true;
1501 
1502  label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("Password"), textAlignment: textAlignment);
1503  passwordBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: password, textAlignment: textAlignment)
1504  {
1505  Censor = true
1506  };
1507  label.RectTransform.IsFixedSize = true;
1508 
1509  var languageLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
1510  TextManager.Get("Language"), textAlignment: textAlignment);
1511  languageDropdown = new GUIDropDown(new RectTransform(textFieldSize, languageLabel.RectTransform, Anchor.CenterRight));
1512  foreach (var language in ServerLanguageOptions.Options)
1513  {
1514  languageDropdown.AddItem(language.Label, language.Identifier);
1515  }
1516  var defaultLanguage = ServerLanguageOptions.PickLanguage(GameSettings.CurrentConfig.Language);
1517  var settingsLanguage = serverSettings.GetAttributeIdentifier("language", defaultLanguage.Value).ToLanguageIdentifier();
1518  if (!ServerLanguageOptions.Options.Any(o => o.Identifier == settingsLanguage))
1519  {
1520  settingsLanguage = defaultLanguage;
1521  }
1522  languageDropdown.Select(ServerLanguageOptions.Options.FindIndex(o => o.Identifier == settingsLanguage));
1523 
1524  var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
1525  TextManager.Get("ServerExecutable"), textAlignment: textAlignment);
1526  const string vanillaServerOption = "Vanilla";
1527  serverExecutableDropdown
1528  = new GUIDropDown(new RectTransform(textFieldSize, serverExecutableLabel.RectTransform, Anchor.CenterRight),
1529  vanillaServerOption);
1530  var listBoxSize = serverExecutableDropdown.ListBox.RectTransform.RelativeSize;
1531  serverExecutableDropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.5f, listBoxSize.Y);
1532  serverExecutableDropdown.AddItem(vanillaServerOption, userData: null);
1533  serverExecutableDropdown.OnSelected = (selected, userData) =>
1534  {
1535  if (userData != null)
1536  {
1537  var warningBox = new GUIMessageBox(headerText: TextManager.Get("Warning"),
1538  text: TextManager.GetWithVariable("ModServerExesAtYourOwnRisk", "[exename]", serverExecutableDropdown.Text),
1539  new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
1540  warningBox.Buttons[0].OnClicked = (_, __) =>
1541  {
1542  warningBox.Close();
1543  return false;
1544  };
1545  warningBox.Buttons[1].OnClicked = (_, __) =>
1546  {
1547  serverExecutableDropdown.Select(0);
1548  warningBox.Close();
1549  return false;
1550  };
1551  }
1552 
1553  serverExecutableDropdown.Text = ToolBox.LimitString(serverExecutableDropdown.Text,
1554  serverExecutableDropdown.Font, serverExecutableDropdown.Rect.Width * 8 / 10);
1555 
1556  return true;
1557  };
1558  serverExecutableLabel.RectTransform.IsFixedSize = true;
1559 
1560  // tickbox upper ---------------
1561 
1562  var tickboxAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
1563 
1564  isPublicBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("PublicServer"))
1565  {
1566  Selected = isPublic,
1567  ToolTip = TextManager.Get("PublicServerToolTip")
1568  };
1569 
1570  wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"))
1571  {
1572  Selected = banAfterWrongPassword
1573  };
1574 
1575  tickboxAreaUpper.RectTransform.IsFixedSize = true;
1576 
1577  // tickbox lower ---------------
1578 
1579  var tickboxAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
1580 
1581  karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("HostServerKarmaSetting"))
1582  {
1583  Selected = !karmaEnabled,
1584  ToolTip = TextManager.Get("hostserverkarmasettingtooltip")
1585  };
1586 
1587  tickboxAreaLower.RectTransform.IsFixedSize = true;
1588 
1589  //spacing
1590  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), style: null);
1591 
1592  new GUIButton(new RectTransform(new Vector2(0.4f, 0.07f), content.RectTransform), TextManager.Get("StartServerButton"), style: "GUIButtonLarge")
1593  {
1594  OnClicked = (btn, userdata) =>
1595  {
1596  CheckServerName();
1597  return true;
1598  }
1599  };
1600 
1601  void CheckServerName()
1602  {
1603  string name = serverNameBox.Text;
1604  if (string.IsNullOrEmpty(name))
1605  {
1606  serverNameBox.Flash();
1607  return;
1608  }
1609  if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
1610  {
1611  var msgBox = new GUIMessageBox("",
1612  TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
1613  new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
1614  msgBox.Buttons[0].OnClicked += (_, __) =>
1615  {
1616  CheckServerExe();
1617  msgBox.Close();
1618  return true;
1619  };
1620  msgBox.Buttons[1].OnClicked += msgBox.Close;
1621  return;
1622  }
1623  CheckServerExe();
1624  }
1625 
1626  void CheckServerExe()
1627  {
1628  if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe &&
1629  serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
1630  {
1631  var msgBox = new GUIMessageBox(string.Empty,
1632  TextManager.GetWithVariables("versionmismatchwarning",
1633  ("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()),
1634  ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+
1635  TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name),
1636  new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
1637  msgBox.Buttons[0].OnClicked += (_, __) =>
1638  {
1639  TryStartServer();
1640  msgBox.Close();
1641  return true;
1642  };
1643  msgBox.Buttons[1].OnClicked += msgBox.Close;
1644  return;
1645  }
1646  TryStartServer();
1647  }
1648  }
1649 
1650  private void SetServerPlayStyle(PlayStyle playStyle)
1651  {
1652  playstyleBanner.Sprite = GUIStyle
1653  .GetComponentStyle($"PlayStyleBanner.{playStyle}")
1654  .GetSprite(GUIComponent.ComponentState.None);
1655  playstyleBanner.UserData = playStyle;
1656 
1657  var nameText = playstyleBanner.GetChild<GUITextBlock>();
1658  nameText.Text = TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag." + playStyle));
1659  nameText.Color = playstyleBanner.Sprite
1660  .SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
1661  nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();
1662 
1663  playstyleDescription.Text = TextManager.Get("servertagdescription." + playStyle);
1664  playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ?
1665  Alignment.CenterLeft : Alignment.Center;
1666  }
1667 #endregion
1668 
1669  private void FetchRemoteContent()
1670  {
1671  string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
1672  if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
1673  try
1674  {
1675  var client = new RestClient(remoteContentUrl);
1676  var request = new RestRequest("MenuContent.xml", Method.GET);
1677  TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
1678  RemoteContentReceived);
1679  }
1680 
1681  catch (Exception e)
1682  {
1683 #if DEBUG
1684  DebugConsole.ThrowError("Fetching remote content to the main menu failed.", e);
1685 #endif
1686  GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsManager.ErrorSeverity.Error,
1687  "Fetching remote content to the main menu failed. " + e.Message);
1688  return;
1689  }
1690  }
1691 
1692  private void RemoteContentReceived(Task t)
1693  {
1694  try
1695  {
1696  if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
1697  if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
1698  {
1699  DebugConsole.AddWarning(
1700  "Failed to receive remote main menu content. " +
1701  "There may be an issue with your internet connection, or the master server might be temporarily unavailable " +
1702  $"(error code: {remoteContentResponse.StatusCode})");
1703  return;
1704  }
1705  string xml = remoteContentResponse.Content;
1706  int index = xml.IndexOf('<');
1707  if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
1708  if (!string.IsNullOrWhiteSpace(xml))
1709  {
1710  remoteContentDoc = XDocument.Parse(xml);
1711  foreach (var subElement in remoteContentDoc?.Root.Elements())
1712  {
1713  GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
1714  }
1715  }
1716  }
1717 
1718  catch (Exception e)
1719  {
1720 #if DEBUG
1721  DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
1722 #endif
1723  GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error,
1724  "Reading received remote main menu content failed. " + e.Message);
1725  }
1726  }
1727  }
1728 }
Stores information about the Character that is needed between rounds in the menu etc....
static readonly Identifier HumanSpeciesName
static CompletedTutorials Instance
bool Contains(Identifier identifier)
Color GetAttributeColor(string key, in Color def)
delegate bool OnClickedHandler(GUIButton button, object obj)
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
virtual RichString ToolTip
virtual Rectangle Rect
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
GUIComponent that can be used to render custom content on the UI
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
Definition: GUIDropDown.cs:270
OnSelectedHandler OnSelected
Triggers when some item is cliecked from the dropdown. Note that SelectedData is not set yet when thi...
Definition: GUIDropDown.cs:20
override GUIFont??? Font
Definition: GUIDropDown.cs:113
void Select(int index)
Definition: GUIDropDown.cs:376
GUIComponent SelectedComponent
Definition: GUIDropDown.cs:68
LocalizedString Text
Definition: GUIDropDown.cs:159
Sprite?? Sprite
Definition: GUIImage.cs:84
override void RemoveChild(GUIComponent child)
Definition: GUIListBox.cs:1270
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
Definition: GUIListBox.cs:449
void CalculateHeightFromText(int padding=0, bool removeExtraSpacing=false)
LocalizedString WrappedText
TextBoxEvent OnDeselected
Definition: GUITextBox.cs:17
OnEnterHandler OnEnterPressed
Definition: GUITextBox.cs:29
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
Definition: GUITextBox.cs:411
override bool Selected
Definition: GUITickBox.cs:18
static int GraphicsWidth
Definition: GameMain.cs:162
static RasterizerState ScissorTestEnable
Definition: GameMain.cs:195
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static int GraphicsHeight
Definition: GameMain.cs:168
static readonly Version Version
Definition: GameMain.cs:46
static GameScreen GameScreen
Definition: GameMain.cs:52
static MainMenuScreen MainMenuScreen
Definition: GameMain.cs:53
static GameClient Client
Definition: GameMain.cs:188
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
static GameModePreset DevSandbox
bool Contains(string subStr, StringComparison comparison=StringComparison.Ordinal)
static readonly PrefabCollection< LocationType > Prefabs
Definition: LocationType.cs:15
void QuickStart(bool fixedSeed=false, Identifier sub=default, float difficulty=50, LevelGenerationParams levelGenerationParams=null)
override void Update(double deltaTime)
bool ReturnToMainMenu(GUIButton button, object obj)
void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
override void AddToGUIUpdateList()
By default, submits the screen's main GUIFrame and, if requested upon construction,...
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
static readonly Queue< ulong > WorkshopItemsToUpdate
static void UpdateInstanceTutorialButtons()
MainMenuScreen(GameMain game)
Vector2 RelativeSize
Relative to the parent rect.
Point NonScaledSize
Size before scale multiplications.
bool IsFixedSize
If false, the element will resize if the parent is resized (with the children). If true,...
override void CreateLoadMenu(IEnumerable< CampaignMode.SaveInfo > saveFiles=null)
CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus
void UpdateSubList(IEnumerable< SubmarineInfo > submarines)
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
ContentXElement SourceElement
Reference to the xml element from where the sprite was created. Can be null if the sprite was not def...
static IEnumerable< SubmarineInfo > SavedSubmarines
LocalizedString DisplayName
Definition: Tutorial.cs:26
readonly TutorialPrefab TutorialPrefab
Definition: Tutorial.cs:49
LocalizedString Description
Definition: Tutorial.cs:27
GUISoundType
Definition: GUI.cs:21