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,
930  missionPrefabs: null);
931  //(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8));
932  gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty, levelGenerationParams);
934  // TODO: modding support
935  Identifier[] jobIdentifiers = new Identifier[] {
936  "captain".ToIdentifier(),
937  "engineer".ToIdentifier(),
938  "mechanic".ToIdentifier(),
939  "securityofficer".ToIdentifier(),
940  "medicaldoctor".ToIdentifier() };
941  foreach (Identifier job in jobIdentifiers)
942  {
943  var jobPrefab = JobPrefab.Get(job);
944  var variant = Rand.Range(0, jobPrefab.Variants);
945  var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant);
946  if (characterInfo.Job == null)
947  {
948  DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!");
949  }
950  gamesession.CrewManager.AddCharacterInfo(characterInfo);
951  }
952  gamesession.CrewManager.InitSinglePlayerRound();
953  }
954 
955  private void ShowTutorialSkipWarning(Tab tabToContinueTo)
956  {
957  var tutorialSkipWarning = new GUIMessageBox("", TextManager.Get("tutorialskipwarning"), new LocalizedString[] { TextManager.Get("tutorialwarningskiptutorials"), TextManager.Get("tutorialwarningplaytutorials") });
958 
959  GUIButton.OnClickedHandler proceedToTab(Tab tab)
960  => (btn, userdata) =>
961  {
962  var config = GameSettings.CurrentConfig;
963  config.TutorialSkipWarning = false;
964  GameSettings.SetCurrentConfig(config);
965  GameSettings.SaveCurrentConfig();
966  tutorialSkipWarning.Close();
967  SelectTab(tab);
968  return true;
969  };
970 
971  tutorialSkipWarning.Buttons[0].OnClicked += proceedToTab(tabToContinueTo);
972  tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials);
973  }
974 
975  public override void AddToGUIUpdateList()
976  {
977  base.AddToGUIUpdateList();
978  switch (selectedTab)
979  {
980  case Tab.NewGame:
981  campaignSetupUI.CharacterMenus?.ForEach(static m => m.AddToGUIUpdateList());
982  break;
983  }
984  }
985 
986  private void UpdateTutorialList()
987  {
988  foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
989  {
990  var tutorial = (Tutorial)tutorialText.UserData;
991  if (CompletedTutorials.Instance.Contains(tutorial.Identifier) && tutorialText.GetChild<GUIImage>() == null)
992  {
993  new GUIImage(new RectTransform(new Point((int)(tutorialText.Padding.X * 0.8f)), tutorialText.RectTransform, Anchor.CenterLeft), style: "ObjectiveIndicatorCompleted");
994  }
995  }
996  }
997 
998  private bool ChangeMaxPlayers(GUIButton button, object obj)
999  {
1000  int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
1001  currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers + (int)button.UserData, 1, NetConfig.MaxPlayers);
1002  maxPlayersBox.Text = currMaxPlayers.ToString();
1003  return true;
1004  }
1005 
1006  private void TryStartServer()
1007  {
1008  if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
1009  {
1010  var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
1011  var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
1012  waitBox.Buttons[0].OnClicked += (btn, userdata) =>
1013  {
1014  CoroutineManager.StopCoroutines(waitCoroutine);
1015  return true;
1016  };
1017  }
1018  else
1019  {
1020  StartServer();
1021  }
1022  }
1023 
1024  private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
1025  {
1026  LocalizedString originalText = messageBox.Text.Text;
1027  int doneCount = 0;
1028  do
1029  {
1030  doneCount = SubmarineInfo.SavedSubmarines.Count(s => !s.CalculatingHash);
1031  messageBox.Text.Text = originalText + $" ({doneCount}/{SubmarineInfo.SavedSubmarines.Count()})";
1032  yield return CoroutineStatus.Running;
1033  } while (doneCount < SubmarineInfo.SavedSubmarines.Count());
1034  messageBox.Close();
1035  StartServer();
1036  yield return CoroutineStatus.Success;
1037  }
1038 
1039  private void StartServer()
1040  {
1041  string name = serverNameBox.Text;
1042 
1043  GameMain.ResetNetLobbyScreen();
1044  try
1045  {
1046  string fileName;
1047  if (serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f &&
1048  f.ContentPackage != GameMain.VanillaContent)
1049  {
1050  fileName = Path.Combine(
1051  Path.GetDirectoryName(f.Path.Value),
1052  Path.GetFileNameWithoutExtension(f.Path.Value));
1053 #if WINDOWS
1054  fileName += ".exe";
1055 #endif
1056  }
1057  else
1058  {
1059 #if WINDOWS
1060  fileName = "DedicatedServer.exe";
1061 #else
1062  fileName = "./DedicatedServer";
1063 #endif
1064  }
1065 
1066  var arguments = new List<string>
1067  {
1068  "-name", name,
1069  "-public", isPublicBox.Selected.ToString(),
1070  "-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
1071  "-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
1072  "-karmaenabled", (!karmaBox.Selected).ToString(),
1073  "-maxplayers", maxPlayersBox.Text,
1074  "-language", languageDropdown.SelectedData.ToString()
1075  };
1076 
1077  if (!string.IsNullOrWhiteSpace(passwordBox.Text))
1078  {
1079  arguments.Add("-password");
1080  arguments.Add(passwordBox.Text);
1081  }
1082  else
1083  {
1084  arguments.Add("-nopassword");
1085  }
1086 
1087  var puids = EosInterface.IdQueries.GetLoggedInPuids();
1088 
1089  var endpoints = new List<Endpoint>();
1090  if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
1091  {
1092  endpoints.Add(new SteamP2PEndpoint(steamId));
1093  }
1094  if (puids.Length > 0)
1095  {
1096  endpoints.Add(new EosP2PEndpoint(puids[0]));
1097  }
1098  if (endpoints.Count == 0)
1099  {
1100  endpoints.Add(new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort));
1101  }
1102 
1103  if (endpoints.First() is P2PEndpoint firstEndpoint)
1104  {
1105  arguments.Add("-endpoint");
1106  arguments.Add(firstEndpoint.StringRepresentation);
1107  }
1108  int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
1109  arguments.Add("-ownerkey");
1110  arguments.Add(ownerKey.ToString());
1111 
1112  var processInfo = new ProcessStartInfo
1113  {
1114  FileName = fileName,
1115  WorkingDirectory = Directory.GetCurrentDirectory(),
1116 #if !DEBUG
1117  CreateNoWindow = true,
1118  UseShellExecute = false,
1119  WindowStyle = ProcessWindowStyle.Hidden
1120 #endif
1121  };
1122  arguments.ForEach(processInfo.ArgumentList.Add);
1123  ChildServerRelay.Start(processInfo);
1124  Thread.Sleep(1000); //wait until the server is ready before connecting
1125 
1126  GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
1127  SteamManager.GetUsername().FallbackNullOrEmpty(name)),
1128  endpoints.ToImmutableArray(),
1129  name,
1130  Option.Some(ownerKey));
1131  }
1132  catch (Exception e)
1133  {
1134  DebugConsole.ThrowError("Failed to start server", e);
1135  }
1136  }
1137 
1138  private bool QuitClicked(GUIButton button, object obj)
1139  {
1140  game.Exit();
1141  return true;
1142  }
1143 
1144  private void UpdateOutOfDateWorkshopItemCount()
1145  {
1146  if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
1147  if (!SteamManager.IsInitialized) { return; }
1148 
1149  var installedPackages = ContentPackageManager.WorkshopPackages;
1150 
1151  var ids = SteamManager.Workshop.GetSubscribedItemIds()
1152  .Select(id => id.Value)
1153  .Union(installedPackages
1154  .Select(pkg => pkg.UgcId)
1155  .NotNone()
1156  .OfType<SteamWorkshopId>()
1157  .Select(id => id.Value));
1158  var count = ids
1159  // Deliberately construct Steamworks.Ugc.Item directly
1160  // to not immediately generate a Workshop data request
1161  .Select(id => new Steamworks.Ugc.Item(id))
1162  .Count(item =>
1163  installedPackages.FirstOrDefault(p
1164  => p.UgcId.TryUnwrap(out SteamWorkshopId id) && id.Value == item.Id)
1165  is { } pkg
1166  // Checking that this item is downloading, waiting to be downloaded
1167  // or is newer than the currently installed copy should be good enough,
1168  // and should still not make a Workshop data request
1169  && (item.IsDownloading
1170  || item.IsDownloadPending
1171  || (item.InstallTime.TryGetValue(out var workshopInstallTime)
1172  && pkg.InstallTime.TryUnwrap(out var localInstallTime)
1173  && localInstallTime.ToUtcValue() < workshopInstallTime)));
1174 
1175  modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
1176  }
1177 
1178  private static bool CanHostServer()
1179  => EosInterface.IdQueries.IsLoggedIntoEosConnect
1180  || SteamManager.IsInitialized
1181  || AssemblyInfo.CurrentConfiguration == AssemblyInfo.Configuration.Debug;
1182 
1183  public override void Update(double deltaTime)
1184  {
1185  hostServerButton.Enabled = CanHostServer();
1186 
1187  gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
1188 
1189  UpdateOutOfDateWorkshopItemCount();
1190  modUpdatesButton.Visible = modUpdateStatus.Count > 0;
1191 
1192  if (modUpdatesButton.Visible)
1193  {
1194  var modButtonLabelSize =
1195  modsButton.Font.MeasureString(modsButton.Text).ToPoint()
1196  + new Point(GUI.IntScale(25));
1197  modUpdatesButton.RectTransform.AbsoluteOffset =
1198  (modButtonLabelSize.X, modsButton.Rect.Height / 2 - modUpdatesButton.Rect.Height / 2);
1199  }
1200 
1201  switch (selectedTab)
1202  {
1203  case Tab.NewGame:
1204  campaignSetupUI.Update();
1205  break;
1206  }
1207  }
1208 
1209  public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
1210  {
1211  graphics.Clear(Color.Black);
1212 
1213  if (backgroundSprite == null)
1214  {
1215 #if UNSTABLE
1216  backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
1217 #endif
1218  if (GUIStyle.GetComponentStyle("MainMenuBackground") is { } mainMenuStyle &&
1219  mainMenuStyle.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var sprites))
1220  {
1221  backgroundSprite = sprites.GetRandomUnsynced()?.Sprite;
1222  }
1223  backgroundSprite ??= LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(0);
1224  }
1225 
1226  var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
1227  float vignetteScale = Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y);
1228 
1229  Rectangle drawArea = new Rectangle(
1230  (int)(vignette.size.X * vignetteScale / 2), 0,
1231  (int)(GameMain.GraphicsWidth - vignette.size.X * vignetteScale / 2), GameMain.GraphicsHeight);
1232 
1233  if (backgroundSprite?.Texture != null)
1234  {
1235  GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White, drawArea);
1236  }
1237 
1238  if (vignette != null)
1239  {
1240  vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
1241  }
1242  }
1243 
1244  public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
1245  {
1246  spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
1247 
1248  DrawBackground(graphics, spriteBatch);
1249 
1250  GUI.Draw(Cam, spriteBatch);
1251 
1252  spriteBatch.End();
1253  }
1254 
1255  private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings)
1256  {
1257  if (string.IsNullOrEmpty(savePath)) { return; }
1258 
1259  var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
1260  if (existingSaveFiles.Any(s => s.FilePath == savePath))
1261  {
1262  new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText"));
1263  return;
1264  }
1265 
1266  if (selectedSub == null)
1267  {
1268  new GUIMessageBox(TextManager.Get("SubNotSelected"), TextManager.Get("SelectSubRequest"));
1269  return;
1270  }
1271 
1272  if (!Directory.Exists(SaveUtil.TempPath))
1273  {
1274  Directory.CreateDirectory(SaveUtil.TempPath);
1275  }
1276 
1277  try
1278  {
1279  File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), true);
1280  }
1281  catch (System.IO.IOException e)
1282  {
1283  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);
1284  GameAnalyticsManager.AddErrorEventOnce(
1285  "MainMenuScreen.StartGame:IOException" + selectedSub.Name,
1286  GameAnalyticsManager.ErrorSeverity.Error,
1287  "Copying a submarine file failed. " + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace());
1288  return;
1289  }
1290 
1291  GameMain.LuaCs.CheckInitialize();
1292 
1293  selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
1294 
1295  GameMain.GameSession = new GameSession(selectedSub, savePath, GameModePreset.SinglePlayerCampaign, settings, mapSeed);
1296  GameMain.GameSession.CrewManager.ClearCharacterInfos();
1297  foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
1298  {
1299  GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo);
1300  }
1301  ((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
1302  }
1303 
1304  private void LoadGame(string saveFile)
1305  {
1306  if (string.IsNullOrWhiteSpace(saveFile)) return;
1307 
1308  GameMain.LuaCs.CheckInitialize();
1309 
1310  try
1311  {
1312  SaveUtil.LoadGame(saveFile);
1313  }
1314  catch (Exception e)
1315  {
1316  DebugConsole.ThrowError("Loading save \"" + saveFile + "\" failed", e);
1317  return;
1318  }
1319 
1320  //TODO
1321  //GameMain.LobbyScreen.Select();
1322  }
1323 
1324 #region UI Methods
1325  private void CreateCampaignSetupUI()
1326  {
1327  menuTabs[Tab.NewGame].ClearChildren();
1328  menuTabs[Tab.LoadGame].ClearChildren();
1329 
1330  var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.NewGame].RectTransform, Anchor.Center))
1331  {
1332  Stretch = true,
1333  RelativeSpacing = 0.02f
1334  };
1335  var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center),
1336  style: "InnerFrame");
1337 
1338  var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) },
1339  style: null);
1340 
1341  campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame)
1342  {
1343  LoadGame = LoadGame,
1344  StartNewGame = StartGame
1345  };
1346  }
1347 
1348  private void CreateHostServerFields()
1349  {
1350  menuTabs[Tab.HostServer].ClearChildren();
1351 
1352  var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");
1353 
1354  var name = serverSettings.GetAttributeString("name", "");
1355  var password = serverSettings.GetAttributeString("password", "");
1356  var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
1357  var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);
1358 
1359  int maxPlayersElement = serverSettings.GetAttributeInt("maxplayers", 8);
1360  if (maxPlayersElement > NetConfig.MaxPlayers)
1361  {
1362  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.");
1363  }
1364  int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers);
1365 
1366  var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", true);
1367  var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual);
1368 
1369  Vector2 textLabelSize = new Vector2(1.0f, 0.05f);
1370  Alignment textAlignment = Alignment.CenterLeft;
1371  Vector2 textFieldSize = new Vector2(0.5f, 1.0f);
1372  Vector2 tickBoxSize = new Vector2(0.4f, 0.04f);
1373  var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.95f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
1374  {
1375  RelativeSpacing = 0.01f,
1376  Stretch = true
1377  };
1378  GUIComponent parent = content;
1379 
1380  var header = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) { ForceUpperCase = ForceUpperCase.Yes };
1381  header.RectTransform.IsFixedSize = true;
1382 
1383  //play style -----------------------------------------------------
1384 
1385  var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
1386 
1387  playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
1388  GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: true)
1389  {
1390  UserData = PlayStyle.Serious
1391  };
1392  float bannerAspectRatio = (float) playstyleBanner.Sprite.SourceRect.Width / playstyleBanner.Sprite.SourceRect.Height;
1393  playstyleBanner.RectTransform.NonScaledSize = new Point(playstyleBanner.Rect.Width, (int)(playstyleBanner.Rect.Width / bannerAspectRatio));
1394  playstyleBanner.RectTransform.IsFixedSize = true;
1395  new GUIFrame(new RectTransform(playstyleBanner.Rect.Size + new Point(1), playstyleBanner.RectTransform, Anchor.Center), "InnerGlow", color: Color.Black);
1396 
1397  new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.05f), playstyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.03f) },
1398  "playstyle name goes here", font: GUIStyle.SmallFont, textAlignment: Alignment.Center, textColor: Color.White, style: "GUISlopedHeader");
1399 
1400  new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterLeft)
1401  { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
1402  style: "UIToggleButton")
1403  {
1404  OnClicked = (btn, userdata) =>
1405  {
1406  int playStyleIndex = (int)playstyleBanner.UserData - 1;
1407  if (playStyleIndex < 0) { playStyleIndex = Enum.GetValues(typeof(PlayStyle)).Length - 1; }
1408  SetServerPlayStyle((PlayStyle)playStyleIndex);
1409  return true;
1410  }
1411  }.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
1412 
1413  new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterRight)
1414  { RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
1415  style: "UIToggleButton")
1416  {
1417  OnClicked = (btn, userdata) =>
1418  {
1419  int playStyleIndex = (int)playstyleBanner.UserData + 1;
1420  if (playStyleIndex >= Enum.GetValues(typeof(PlayStyle)).Length) { playStyleIndex = 0; }
1421  SetServerPlayStyle((PlayStyle)playStyleIndex);
1422  return true;
1423  }
1424  };
1425 
1426  LocalizedString longestPlayStyleStr = "";
1427  foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
1428  {
1429  LocalizedString playStyleStr = TextManager.Get("servertagdescription." + playStyle);
1430  if (playStyleStr.Length > longestPlayStyleStr.Length) { longestPlayStyleStr = playStyleStr; }
1431  }
1432 
1433  playstyleDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), playstyleContainer.RectTransform, Anchor.BottomCenter),
1434  longestPlayStyleStr, style: null, wrap: true)
1435  {
1436  Color = Color.Black * 0.8f,
1437  TextColor = GUIStyle.GetComponentStyle("GUITextBlock").TextColor
1438  };
1439  playstyleDescription.Padding = Vector4.One * 10.0f * GUI.Scale;
1440  playstyleDescription.CalculateHeightFromText(padding: (int)(15 * GUI.Scale));
1441  playstyleDescription.RectTransform.NonScaledSize = new Point(playstyleDescription.Rect.Width, playstyleDescription.Rect.Height);
1442  playstyleDescription.RectTransform.IsFixedSize = true;
1443  playstyleContainer.RectTransform.NonScaledSize = new Point(playstyleContainer.Rect.Width, playstyleBanner.Rect.Height + playstyleDescription.Rect.Height);
1444  playstyleContainer.RectTransform.IsFixedSize = true;
1445 
1446  SetServerPlayStyle(selectedPlayStyle);
1447 
1448  //other settings -----------------------------------------------------
1449 
1450  //spacing
1451  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
1452 
1453  var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment);
1454  serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment)
1455  {
1456  MaxTextLength = NetConfig.ServerNameMaxLength,
1457  OverflowClip = true
1458  };
1459  label.RectTransform.IsFixedSize = true;
1460 
1461  var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment);
1462  var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
1463  {
1464  Stretch = true,
1465  RelativeSpacing = 0.1f
1466  };
1467  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
1468  {
1469  UserData = -1,
1470  OnClicked = ChangeMaxPlayers,
1471  ClickSound = GUISoundType.Decrease
1472  };
1473  maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
1474  {
1475  Text = maxPlayers.ToString()
1476  };
1477  maxPlayersBox.OnEnterPressed += (GUITextBox sender, string text) =>
1478  {
1479  maxPlayersBox.Deselect();
1480  return true;
1481  };
1482  maxPlayersBox.OnDeselected += (GUITextBox sender, Microsoft.Xna.Framework.Input.Keys key) =>
1483  {
1484  int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
1485  currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers, 1, NetConfig.MaxPlayers);
1486  maxPlayersBox.Text = currMaxPlayers.ToString();
1487  };
1488  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
1489  {
1490  UserData = 1,
1491  OnClicked = ChangeMaxPlayers,
1492  ClickSound = GUISoundType.Increase
1493  };
1494  maxPlayersLabel.RectTransform.IsFixedSize = true;
1495 
1496  label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("Password"), textAlignment: textAlignment);
1497  passwordBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: password, textAlignment: textAlignment)
1498  {
1499  Censor = true
1500  };
1501  label.RectTransform.IsFixedSize = true;
1502 
1503  var languageLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
1504  TextManager.Get("Language"), textAlignment: textAlignment);
1505  languageDropdown = new GUIDropDown(new RectTransform(textFieldSize, languageLabel.RectTransform, Anchor.CenterRight));
1506  foreach (var language in ServerLanguageOptions.Options)
1507  {
1508  languageDropdown.AddItem(language.Label, language.Identifier);
1509  }
1510  var defaultLanguage = ServerLanguageOptions.PickLanguage(GameSettings.CurrentConfig.Language);
1511  var settingsLanguage = serverSettings.GetAttributeIdentifier("language", defaultLanguage.Value).ToLanguageIdentifier();
1512  if (!ServerLanguageOptions.Options.Any(o => o.Identifier == settingsLanguage))
1513  {
1514  settingsLanguage = defaultLanguage;
1515  }
1516  languageDropdown.Select(ServerLanguageOptions.Options.FindIndex(o => o.Identifier == settingsLanguage));
1517 
1518  var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
1519  TextManager.Get("ServerExecutable"), textAlignment: textAlignment);
1520  const string vanillaServerOption = "Vanilla";
1521  serverExecutableDropdown
1522  = new GUIDropDown(new RectTransform(textFieldSize, serverExecutableLabel.RectTransform, Anchor.CenterRight),
1523  vanillaServerOption);
1524  var listBoxSize = serverExecutableDropdown.ListBox.RectTransform.RelativeSize;
1525  serverExecutableDropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.5f, listBoxSize.Y);
1526  serverExecutableDropdown.AddItem(vanillaServerOption, userData: null);
1527  serverExecutableDropdown.OnSelected = (selected, userData) =>
1528  {
1529  if (userData != null)
1530  {
1531  var warningBox = new GUIMessageBox(headerText: TextManager.Get("Warning"),
1532  text: TextManager.GetWithVariable("ModServerExesAtYourOwnRisk", "[exename]", serverExecutableDropdown.Text),
1533  new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
1534  warningBox.Buttons[0].OnClicked = (_, __) =>
1535  {
1536  warningBox.Close();
1537  return false;
1538  };
1539  warningBox.Buttons[1].OnClicked = (_, __) =>
1540  {
1541  serverExecutableDropdown.Select(0);
1542  warningBox.Close();
1543  return false;
1544  };
1545  }
1546 
1547  serverExecutableDropdown.Text = ToolBox.LimitString(serverExecutableDropdown.Text,
1548  serverExecutableDropdown.Font, serverExecutableDropdown.Rect.Width * 8 / 10);
1549 
1550  return true;
1551  };
1552  serverExecutableLabel.RectTransform.IsFixedSize = true;
1553 
1554  // tickbox upper ---------------
1555 
1556  var tickboxAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
1557 
1558  isPublicBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("PublicServer"))
1559  {
1560  Selected = isPublic,
1561  ToolTip = TextManager.Get("PublicServerToolTip")
1562  };
1563 
1564  wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"))
1565  {
1566  Selected = banAfterWrongPassword
1567  };
1568 
1569  tickboxAreaUpper.RectTransform.IsFixedSize = true;
1570 
1571  // tickbox lower ---------------
1572 
1573  var tickboxAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
1574 
1575  karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("HostServerKarmaSetting"))
1576  {
1577  Selected = !karmaEnabled,
1578  ToolTip = TextManager.Get("hostserverkarmasettingtooltip")
1579  };
1580 
1581  tickboxAreaLower.RectTransform.IsFixedSize = true;
1582 
1583  //spacing
1584  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), style: null);
1585 
1586  new GUIButton(new RectTransform(new Vector2(0.4f, 0.07f), content.RectTransform), TextManager.Get("StartServerButton"), style: "GUIButtonLarge")
1587  {
1588  OnClicked = (btn, userdata) =>
1589  {
1590  CheckServerName();
1591  return true;
1592  }
1593  };
1594 
1595  void CheckServerName()
1596  {
1597  string name = serverNameBox.Text;
1598  if (string.IsNullOrEmpty(name))
1599  {
1600  serverNameBox.Flash();
1601  return;
1602  }
1603  if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
1604  {
1605  var msgBox = new GUIMessageBox("",
1606  TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
1607  new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
1608  msgBox.Buttons[0].OnClicked += (_, __) =>
1609  {
1610  CheckServerExe();
1611  msgBox.Close();
1612  return true;
1613  };
1614  msgBox.Buttons[1].OnClicked += msgBox.Close;
1615  return;
1616  }
1617  CheckServerExe();
1618  }
1619 
1620  void CheckServerExe()
1621  {
1622  if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe &&
1623  serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
1624  {
1625  var msgBox = new GUIMessageBox(string.Empty,
1626  TextManager.GetWithVariables("versionmismatchwarning",
1627  ("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()),
1628  ("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+
1629  TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name),
1630  new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
1631  msgBox.Buttons[0].OnClicked += (_, __) =>
1632  {
1633  TryStartServer();
1634  msgBox.Close();
1635  return true;
1636  };
1637  msgBox.Buttons[1].OnClicked += msgBox.Close;
1638  return;
1639  }
1640  TryStartServer();
1641  }
1642  }
1643 
1644  private void SetServerPlayStyle(PlayStyle playStyle)
1645  {
1646  playstyleBanner.Sprite = GUIStyle
1647  .GetComponentStyle($"PlayStyleBanner.{playStyle}")
1648  .GetSprite(GUIComponent.ComponentState.None);
1649  playstyleBanner.UserData = playStyle;
1650 
1651  var nameText = playstyleBanner.GetChild<GUITextBlock>();
1652  nameText.Text = TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag." + playStyle));
1653  nameText.Color = playstyleBanner.Sprite
1654  .SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
1655  nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();
1656 
1657  playstyleDescription.Text = TextManager.Get("servertagdescription." + playStyle);
1658  playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ?
1659  Alignment.CenterLeft : Alignment.Center;
1660  }
1661 #endregion
1662 
1663  private void FetchRemoteContent()
1664  {
1665  string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
1666  if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
1667  try
1668  {
1669  var client = new RestClient(remoteContentUrl);
1670  var request = new RestRequest("MenuContent.xml", Method.GET);
1671  TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
1672  RemoteContentReceived);
1673  }
1674 
1675  catch (Exception e)
1676  {
1677 #if DEBUG
1678  DebugConsole.ThrowError("Fetching remote content to the main menu failed.", e);
1679 #endif
1680  GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsManager.ErrorSeverity.Error,
1681  "Fetching remote content to the main menu failed. " + e.Message);
1682  return;
1683  }
1684  }
1685 
1686  private void RemoteContentReceived(Task t)
1687  {
1688  try
1689  {
1690  if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
1691  if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
1692  {
1693  DebugConsole.AddWarning(
1694  "Failed to receive remote main menu content. " +
1695  "There may be an issue with your internet connection, or the master server might be temporarily unavailable " +
1696  $"(error code: {remoteContentResponse.StatusCode})");
1697  return;
1698  }
1699  string xml = remoteContentResponse.Content;
1700  int index = xml.IndexOf('<');
1701  if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
1702  if (!string.IsNullOrWhiteSpace(xml))
1703  {
1704  remoteContentDoc = XDocument.Parse(xml);
1705  foreach (var subElement in remoteContentDoc?.Root.Elements())
1706  {
1707  GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
1708  }
1709  }
1710  }
1711 
1712  catch (Exception e)
1713  {
1714 #if DEBUG
1715  DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
1716 #endif
1717  GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error,
1718  "Reading received remote main menu content failed. " + e.Message);
1719  }
1720  }
1721  }
1722 }
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:247
OnSelectedHandler OnSelected
Definition: GUIDropDown.cs:13
override GUIFont??? Font
Definition: GUIDropDown.cs:100
void Select(int index)
Definition: GUIDropDown.cs:349
GUIComponent SelectedComponent
Definition: GUIDropDown.cs:55
LocalizedString Text
Definition: GUIDropDown.cs:146
Sprite?? Sprite
Definition: GUIImage.cs:84
override void RemoveChild(GUIComponent child)
Definition: GUIListBox.cs:1249
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:33
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
Definition: GUIListBox.cs:440
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