Files
NotAlwaysTrue 9b35f6b23f Sync with upstream
* Update bug-reports.yml

* Fix modifyChatMessage hook

* Add LuaCsSetup.Lua back for compatibility

* Fix Game.AssignOnExecute having command arguments be passed as varargs instead of a table

* Actually use the PackageId const everywhere we need to refer to our content package

* Load languages files even if the package is disabled

* Fix Hook.Remove not being implemented properly

* - Changed event aliases to be case insensitive.

* - Fixed assembly logging style.
- Fixed double logging during execution.

* Fix garbage network data being read by the game when reading LuaCs network messages

* PackageId -> PackageName

* Added caching toggle to PluginManagementService

* Fix LuaCs initializing too late for singleplayer campaigns and rework the C# prompt to only show when enabling mods/joining server

* Oops, fix NRE crash

* Fix hide username in logs config not doing anything

* Fix Cs prompt showing up more than one between rounds

* Fix server host being prompted twice with the C# popup

* Ignore our workshop packages from the game's dependency thing since it doesn't really make sense

* Load console commands after executing and possible fix for the not console command permitted

* Added fallback friendly name resolution for ModConfig assembly contents.

* Register Voronoi2 stuff

* Added configinfo null check to SettingBase.cs

* Add safety check so this stops crashing when we look at it the wrong way

* Fixed "Folder" attribute files not being found.

* Keep the LuaCsConfig class laying around for compatibility, not sure anywhere in our code base (and shouldn't be)

* Added fallback compilation for UseInternalsAwareAssembly if the publicized script compilation fails.

* Added legacy overload of AddCommand for mod compat.

* Added LoggerService to Lua env. Made ILoggerService compliant with LuaCsLogger API.

* Changed csharp script compilation algorithm to be best effort.

* Added "RunUnrestricted" mode for lua scripts that need to run outside of sandbox.

* - Fixed networking sync vars failing to sync initially.
- Fixed lua failing to differentiate overloads ISettingBase.

* Add alias for human.CPRSuccess and human.CPRFailed

* - Fixed up the settings menu.
- Made SettingEntry throw an error if "Value" attribute is not found in XML.
- Fixed saved values for settings sometimes not reloading after disabling and re-enabling a package.

* Fix LuaCs net messages received during connection initialization to be read incorrectly, happened because we would reset the BitPosition in our harmony patch which would cause the message to be read incorrectly later

* Allow reloadlua to force the state to running

* New icon for settings and make the top left text more user friendly

* Fix client.packages hook sending normal packages

* Fixed OnUpdate() not passing in deltaTime instead of totalTime.

* Missing diffs from bb21a09244

* Added networking tests for configs.

* Added missing diffs for f61f852a25.

* Some tweaks to the text

* Remove missing Value error, it should just use the default value if it's not specified

* Fix UseInternalAccessName

* Always purge cashes for plugin content on unloading.

* Fix texture not multiple of 4

* v1.12.7.0 (Spring Update 2026 Hotfix 1)

---------

Co-authored-by: Joonas Rikkonen <poe.regalis@gmail.com>
Co-authored-by: Evil Factory <36804725+evilfactory@users.noreply.github.com>
Co-authored-by: MapleWheels <njainanan@hotmail.com>
2026-04-25 12:10:24 +08:00

1752 lines
81 KiB
C#

//#define TEST_REMOTE_CONTENT
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Tutorials;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Barotrauma.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using Barotrauma.Steam;
namespace Barotrauma
{
sealed class MainMenuScreen : Screen
{
public static HashSet<Identifier> DismissedNotifications = new HashSet<Identifier>();
private enum Tab
{
NewGame = 0,
LoadGame = 1,
HostServer = 2,
Settings = 3,
Tutorials = 4,
JoinServer = 5,
CharacterEditor = 6,
SubmarineEditor = 7,
Mods = 8,
Credits = 9,
Empty = 10
}
private readonly GUIComponent buttonsParent;
private readonly Dictionary<Tab, GUIFrame> menuTabs;
private SinglePlayerCampaignSetupUI campaignSetupUI;
private GUITextBox serverNameBox, passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, wrongPasswordBanBox, karmaBox;
private GUIDropDown languageDropdown, serverExecutableDropdown;
#if DEBUG
private GUITickBox lenientHandshakeBox;
#endif
private readonly GUIButton joinServerButton, hostServerButton;
private readonly GUIFrame modsButtonContainer;
private readonly GUIButton modsButton, modUpdatesButton;
private (DateTime WhenToRefresh, int Count) modUpdateStatus = (DateTime.Now, 0);
private static readonly TimeSpan ModUpdateInterval = TimeSpan.FromSeconds(60.0f);
private readonly GameMain game;
private GUIImage playstyleBanner;
private GUITextBlock playstyleDescription;
private static string RemoteContentUrl => GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
private readonly GUIComponent remoteContentContainer;
private XDocument remoteContentDoc;
private Tab selectedTab = Tab.Empty;
private Sprite backgroundSprite;
private readonly GUIComponent titleText;
private readonly CreditsPlayer creditsPlayer;
public static readonly Queue<ulong> WorkshopItemsToUpdate = new Queue<ulong>();
private GUIImage tutorialBanner;
private GUITextBlock tutorialHeader, tutorialDescription;
private GUIListBox tutorialList;
private readonly GUITextBlock gameAnalyticsStatusText;
private readonly GUILayoutGroup leftTextFooterLayout;
private readonly GUILayoutGroup rightTextFooterLayout;
private GUIComponent versionMismatchWarning;
#region Creation
public MainMenuScreen(GameMain game) : base()
{
leftTextFooterLayout = createTextFooter();
rightTextFooterLayout = createTextFooter();
gameAnalyticsStatusText = createLeftText(TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.Consent.Unknown}"));
createLeftText("Barotrauma v" + GameMain.Version + " (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
var privacyPolicyText = createRightText(TextManager.Get("privacypolicy").Fallback("Privacy policy"));
(Rectangle Rect, bool MouseOn) getPrivacyPolicyHoverRect()
{
var textSize = privacyPolicyText.Font.MeasureString(privacyPolicyText.Text);
var bottomRight = privacyPolicyText.Rect.Location.ToVector2()
+ privacyPolicyText.TextPos
+ privacyPolicyText.TextOffset;
var rect = new Rectangle((bottomRight - textSize).ToPoint(), textSize.ToPoint());
bool mouseOn = rect.Contains(PlayerInput.LatestMousePosition) && GUI.IsMouseOn(privacyPolicyText);
return (rect, mouseOn);
}
new GUICustomComponent(new RectTransform(Vector2.One, privacyPolicyText.RectTransform),
onUpdate: (dt, component) =>
{
var (_, mouseOn) = getPrivacyPolicyHoverRect();
if (mouseOn && PlayerInput.PrimaryMouseButtonClicked())
{
GameMain.ShowOpenUriPrompt("https://privacypolicy.daedalic.com");
}
},
onDraw: (sb, component) =>
{
var (rect, mouseOn) = getPrivacyPolicyHoverRect();
Color color = mouseOn ? Color.White : Color.White * 0.7f;
privacyPolicyText.TextColor = color;
GUI.DrawLine(sb, new Vector2(rect.Left, rect.Bottom), new Vector2(rect.Right, rect.Bottom), color);
});
createRightText("© " + DateTime.Now.Year + " Undertow Games & FakeFish. All rights reserved.");
createRightText("© " + DateTime.Now.Year + " Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved.");
GUILayoutGroup createTextFooter()
=> new GUILayoutGroup(new RectTransform((1.0f, 0.06f), Frame.RectTransform, Anchor.BottomCenter))
{
ChildAnchor = Anchor.BottomLeft
};
GUITextBlock createTextInFooter(GUILayoutGroup footer, LocalizedString str, Alignment textAlignment)
{
var textBlock = new GUITextBlock(
rectT: new RectTransform((1.0f, 0.3f), footer.RectTransform),
text: str,
textAlignment: textAlignment,
font: GUIStyle.SmallFont,
textColor: Color.White * 0.7f);
textBlock.RectTransform.SetAsFirstChild();
return textBlock;
}
GUITextBlock createLeftText(LocalizedString str)
=> createTextInFooter(leftTextFooterLayout, str, Alignment.BottomLeft);
GUITextBlock createRightText(LocalizedString str)
=> createTextInFooter(rightTextFooterLayout, str, Alignment.BottomRight);
GameMain.Instance.ResolutionChanged += () =>
{
SetMenuTabPositioning();
CreateHostServerFields();
bool prevMenuOpen = GUI.SettingsMenuOpen;
SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
GUI.SettingsMenuOpen = prevMenuOpen;
if (remoteContentDoc?.Root != null)
{
remoteContentContainer.ClearChildren();
try
{
foreach (var subElement in remoteContentDoc.Root.Elements())
{
GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
}
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentParse:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Reading received remote main menu content failed. " + e.Message);
}
}
};
versionMismatchWarning = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.065f), Frame.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(15)) }, style: "InnerFrame", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
Visible = false
};
var versionMismatchContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), versionMismatchWarning.RectTransform, Anchor.Center), isHorizontal: true)
{
RelativeSpacing = 0.05f,
};
new GUIImage(new RectTransform(new Vector2(1.0f), versionMismatchContent.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUINotificationButton")
{
Color = GUIStyle.Orange
};
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1.0f), versionMismatchContent.RectTransform),
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", GameMain.Version.ToString()),
("[contentversion]", ContentPackageManager.VanillaCorePackage.GameVersion.ToString())),
wrap: true)
{
TextColor = GUIStyle.Orange
};
new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
style: "TitleText")
{
Color = Color.Black * 0.5f,
CanBeFocused = false
};
titleText = new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.08f, 0.05f) },
style: "TitleText");
buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.CenterLeft)
{
AbsoluteOffset = new Point(50, 0)
})
{
Stretch = true,
RelativeSpacing = 0.02f
};
remoteContentContainer = new GUIFrame(new RectTransform(Vector2.One, parent: Frame.RectTransform), style: null)
{
CanBeFocused = false
};
#if TEST_REMOTE_CONTENT
var doc = XMLExtensions.TryLoadXml("Content/UI/MenuContent.xml");
if (doc?.Root != null)
{
foreach (var subElement in doc?.Root.Elements())
{
GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
}
}
#else
SpamServerFilters.RequestGlobalSpamFilter();
FetchRemoteContent();
#endif
float labelHeight = 0.18f;
// === CAMPAIGN
var campaignHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, isHorizontal: true);
new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), campaignHolder.RectTransform), "MainMenuCampaignIcon")
{
CanBeFocused = false
};
//spacing
new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), campaignHolder.RectTransform), style: null);
var campaignNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: campaignHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), campaignNavigation.RectTransform),
TextManager.Get("CampaignLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
var campaignButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: campaignNavigation.RectTransform), style: "MainMenuGUIFrame");
var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: campaignButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("TutorialButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.Tutorials,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("LoadGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.LoadGame,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), campaignList.RectTransform), TextManager.Get("NewGameButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.NewGame,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
// === MULTIPLAYER
var multiplayerHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, isHorizontal: true);
new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), multiplayerHolder.RectTransform), "MainMenuMultiplayerIcon")
{
CanBeFocused = false
};
//spacing
new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), multiplayerHolder.RectTransform), style: null);
var multiplayerNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: multiplayerHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), multiplayerNavigation.RectTransform),
TextManager.Get("MultiplayerLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
var multiplayerButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: multiplayerNavigation.RectTransform), style: "MainMenuGUIFrame");
var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: multiplayerButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
};
joinServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("JoinServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.JoinServer,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
hostServerButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), multiplayerList.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.HostServer,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
// === CUSTOMIZE
var customizeHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), parent: buttonsParent.RectTransform) { RelativeOffset = new Vector2(0.15f, 0.0f) }, isHorizontal: true);
new GUIImage(new RectTransform(new Vector2(0.2f, 0.7f), customizeHolder.RectTransform), "MainMenuCustomizeIcon")
{
CanBeFocused = false
};
//spacing
new GUIFrame(new RectTransform(new Vector2(0.02f, 0.0f), customizeHolder.RectTransform), style: null);
var customizeNavigation = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.75f), parent: customizeHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, labelHeight), customizeNavigation.RectTransform),
TextManager.Get("CustomizeLabel"), textAlignment: Alignment.Left, font: GUIStyle.LargeFont, textColor: Color.Black, style: "MainMenuGUITextBlock") { ForceUpperCase = ForceUpperCase.Yes };
var customizeButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: customizeNavigation.RectTransform), style: "MainMenuGUIFrame");
var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: customizeButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
};
modsButtonContainer = new GUIFrame(new RectTransform(Vector2.One, customizeList.RectTransform),
style: null);
modsButton = new GUIButton(new RectTransform(Vector2.One, modsButtonContainer.RectTransform),
TextManager.Get("settingstab.mods"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
Enabled = true,
UserData = Tab.Mods,
OnClicked = SelectTab
};
modUpdatesButton = new GUIButton(new RectTransform(Vector2.One * 0.95f, modsButtonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight),
style: "GUIUpdateButton")
{
ToolTip = TextManager.Get("ModUpdatesAvailable"),
OnClicked = (_, _) =>
{
BulkDownloader.PrepareUpdates();
return false;
},
Visible = false
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.SubmarineEditor,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("CharacterEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.CharacterEditor,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
return true;
}
};
// === OPTION
var optionHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), parent: buttonsParent.RectTransform), isHorizontal: true);
new GUIImage(new RectTransform(new Vector2(0.15f, 0.6f), optionHolder.RectTransform), "MainMenuOptionIcon")
{
CanBeFocused = false
};
//spacing
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.0f), optionHolder.RectTransform), style: null);
var optionButtons = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), parent: optionHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) });
var optionList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.25f), parent: optionButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
};
var settingsButtonContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), style: null);
new GUIButton(new RectTransform(Vector2.One, settingsButtonContainer.RectTransform), TextManager.Get("SettingsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.Settings,
OnClicked = SelectTab
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("EditorDisclaimerWikiLink"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
OnClicked = (button, userData) =>
{
string url = TextManager.Get("EditorDisclaimerWikiUrl").Fallback("https://barotraumagame.com/wiki").Value;
GameMain.ShowOpenUriPrompt(url, promptExtensionTag: "wikinotice");
return true;
}
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
UserData = Tab.Credits,
OnClicked = SelectTab
};
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("QuitButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{
ForceUpperCase = ForceUpperCase.Yes,
OnClicked = QuitClicked
};
//debug button for quickly starting a new round
#if DEBUG
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 80) },
"Quickstart (dev)", style: "GUIButtonLarge", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
UserData = Tab.Empty,
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
QuickStart();
return true;
}
};
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 130) },
"Profiling", style: "GUIButtonLarge", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
UserData = Tab.Empty,
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.",
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
QuickStart(fixedSeed: true);
GameMain.ShowPerf = true;
GameMain.ShowFPS = true;
return true;
}
};
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 180) },
"Join Localhost", style: "GUIButtonLarge", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
UserData = Tab.Empty,
ToolTip = "Connects to a locally hosted dedicated server, assuming default port.",
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
return true;
}
};
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 230) },
"Local MP Quickstart", style: "GUIButtonLarge", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
UserData = Tab.Empty,
ToolTip = "Starts a server and another client and connects both to localhost, using names 'client1' and 'client2'.",
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
DebugConsole.StartLocalMPSession(numClients: 2);
return true;
}
};
#endif
var minButtonSize = new Point(120, 20);
var maxButtonSize = new Point(480, 80);
var relativeSize = new Vector2(0.6f, 0.65f);
var minSize = new Point(600, 400);
var maxSize = new Point(2000, 1500);
var anchor = Anchor.Center;
var pivot = Pivot.Center;
Vector2 relativeOffset = new Vector2(0.05f, 0.0f);
menuTabs = new Dictionary<Tab, GUIFrame>
{
[Tab.Settings] = new GUIFrame(new RectTransform(new Vector2(relativeSize.X, 0.8f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset },
style: null)
{
CanBeFocused = false
},
[Tab.NewGame] = new GUIFrame(new RectTransform(relativeSize * new Vector2(1.0f, 1.15f), Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset }),
[Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset })
};
CreateCampaignSetupUI();
var hostServerScale = new Vector2(0.7f, 1.2f);
menuTabs[Tab.HostServer] = new GUIFrame(new RectTransform(
Vector2.Multiply(relativeSize, hostServerScale), Frame.RectTransform, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale))
{ RelativeOffset = relativeOffset });
CreateHostServerFields();
//----------------------------------------------------------------------
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, Frame.RectTransform, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeOffset });
CreateTutorialTab();
this.game = game;
menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, Frame.RectTransform, Anchor.Center), style: null)
{
CanBeFocused = false
};
var blockerFrame = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, menuTabs[Tab.Credits].RectTransform, Anchor.Center), style: "GUIBackgroundBlocker")
{
CanBeFocused = false
};
blockerFrame.RectTransform.RelativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
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);
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
creditsPlayer.CloseButton.OnClicked = (btn, userdata) =>
{
SelectTab(Tab.Empty);
return true;
};
SetMenuTabPositioning();
SelectTab(Tab.Empty);
}
public static void AddDismissedNotification(Identifier id)
{
DismissedNotifications.Add(id);
GameSettings.SaveCurrentConfig();
}
private void SetMenuTabPositioning()
{
foreach (GUIFrame menuTab in menuTabs.Values)
{
var anchor = GUI.IsUltrawide ? Anchor.Center : Anchor.CenterRight;
var pivot = GUI.IsUltrawide ? Pivot.Center : Pivot.CenterRight;
Vector2 relativeOffset = GUI.IsUltrawide ? Vector2.Zero : new Vector2(0.05f, 0.0f);
menuTab.RectTransform.SetPosition(anchor, pivot);
menuTab.RectTransform.RelativeOffset = relativeOffset;
}
}
private void CreateTutorialTab()
{
var tutorialInnerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.Tutorials].RectTransform, Anchor.Center), style: "InnerFrame");
var tutorialContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tutorialInnerFrame.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.02f, Stretch = true };
tutorialList = new GUIListBox(new RectTransform(new Vector2(0.4f, 1.0f), tutorialContent.RectTransform))
{
PlaySoundOnSelect = true,
OnSelected = (component, obj) =>
{
SelectTutorial(obj as Tutorial);
return true;
}
};
var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent);
var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
{
AbsoluteSpacing = GUI.IntScale(10)
};
tutorialHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, font: GUIStyle.SubHeadingFont);
tutorialDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), string.Empty, wrap: true);
var startButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.0f), infoContent.RectTransform, Anchor.BottomRight), text: TextManager.Get("startgamebutton"))
{
IgnoreLayoutGroups = true,
OnClicked = (component, obj) =>
{
(tutorialList.SelectedData as Tutorial)?.Start();
return true;
}
};
Tutorial firstTutorial = null;
foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
{
var tutorial = new Tutorial(tutorialPrefab);
firstTutorial ??= tutorial;
var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), tutorialList.Content.RectTransform), tutorial.DisplayName)
{
Padding = new Vector4(30.0f * GUI.Scale, 0,0,0),
UserData = tutorial
};
tutorialText.RectTransform.MinSize = new Point(0, (int)(tutorialText.TextSize.Y * 2));
}
GUITextBlock.AutoScaleAndNormalize(tutorialList.Content.Children.Select(c => c as GUITextBlock));
tutorialList.Select(firstTutorial);
}
private void SelectTutorial(Tutorial tutorial)
{
tutorialHeader.Text = tutorial.DisplayName;
tutorialHeader.CalculateHeightFromText();
tutorialDescription.Text = tutorial.Description;
tutorialDescription.CalculateHeightFromText();
(tutorialDescription.Parent as GUILayoutGroup)?.Recalculate();
tutorial.TutorialPrefab.Banner?.EnsureLazyLoaded();
tutorialBanner.Sprite = tutorial.TutorialPrefab.Banner;
tutorialBanner.Color = tutorial.TutorialPrefab.Banner == null ? Color.Black : Color.White;
}
public static void UpdateInstanceTutorialButtons()
{
if (GameMain.MainMenuScreen is not MainMenuScreen menuScreen) { return; }
menuScreen.tutorialList.ClearChildren();
menuScreen.CreateTutorialTab();
}
#endregion
#region Selection
public override void Select()
{
ResetModUpdateButton();
if (WorkshopItemsToUpdate.Any())
{
while (WorkshopItemsToUpdate.TryDequeue(out ulong workshopId))
{
SteamManager.Workshop.OnItemDownloadComplete(workshopId, forceInstall: true);
}
}
GUI.PreventPauseMenuToggle = false;
base.Select();
if (GameMain.Client != null)
{
GameMain.Client.Quit();
GameMain.Client = null;
}
GameMain.SubEditorScreen?.ClearBackedUpSubInfo();
Submarine.Unload();
versionMismatchWarning.Visible = GameMain.Version < ContentPackageManager.VanillaCorePackage.GameVersion;
ResetButtonStates(null);
Eos.EosAccount.ExecuteAfterLogin(AchievementManager.SyncBetweenPlatforms);
}
public override void Deselect()
{
base.Deselect();
SelectTab(null, 0);
}
private bool SelectTab(GUIButton button, object obj)
{
titleText.Visible = true;
if (obj is Tab tab)
{
SelectTab(tab);
}
else
{
SelectTab(Tab.Empty);
}
return true;
}
private bool SelectTab(Tab tab)
{
titleText.Visible = true;
SettingsMenu.Instance?.Close();
#warning TODO: reimplement settings confirmation dialog
switch (tab)
{
case Tab.NewGame:
if (GameSettings.CurrentConfig.TutorialSkipWarning)
{
selectedTab = Tab.Empty;
ShowTutorialSkipWarning(Tab.NewGame);
return true;
}
campaignSetupUI.RandomizeCrew();
campaignSetupUI.SetPage(0);
campaignSetupUI.CreateDefaultSaveName();
campaignSetupUI.RandomizeSeed();
campaignSetupUI.UpdateSubList(SubmarineInfo.SavedSubmarines);
break;
case Tab.LoadGame:
campaignSetupUI.CreateLoadMenu();
break;
case Tab.Settings:
SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
break;
case Tab.JoinServer:
if (GameSettings.CurrentConfig.TutorialSkipWarning)
{
selectedTab = Tab.Empty;
ShowTutorialSkipWarning(Tab.JoinServer);
return true;
}
GameMain.ServerListScreen.Select();
break;
case Tab.HostServer:
if (GameSettings.CurrentConfig.TutorialSkipWarning)
{
selectedTab = Tab.Empty;
ShowTutorialSkipWarning(tab);
return true;
}
serverExecutableDropdown.ListBox.Content.Children.ToArray()
.Where(c => c.UserData is ServerExecutableFile f && !ContentPackageManager.EnabledPackages.All.Contains(f.ContentPackage))
.ForEach(serverExecutableDropdown.ListBox.RemoveChild);
var newServerExes
= ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<ServerExecutableFile>())
.Where(f => serverExecutableDropdown.ListBox.Content.Children.None(c => c.UserData == f))
.ToArray();
foreach (var newServerExe in newServerExes)
{
var serverExeEntry = serverExecutableDropdown.AddItem($"{newServerExe.ContentPackage.Name} - {Path.GetFileNameWithoutExtension(newServerExe.Path.Value)}", userData: newServerExe);
if (newServerExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
{
serverExeEntry.ToolTip =
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", newServerExe.ContentPackage.GameVersion.ToString()),
("[contentversion]", GameMain.VanillaContent.GameVersion.ToString()));
if (serverExeEntry is GUITextBlock serverExeText)
{
serverExeText.TextColor = GUIStyle.Red;
}
}
}
serverExecutableDropdown.ListBox.Content.Children.ForEach(c =>
{
c.RectTransform.RelativeSize = (1.0f, c.RectTransform.RelativeSize.Y);
c.ForceLayoutRecalculation();
});
bool serverExePickable = serverExecutableDropdown.ListBox.Content.CountChildren > 1;
bool wasPickable = serverExecutableDropdown.Parent.Visible;
if (wasPickable != serverExePickable)
{
serverExecutableDropdown.Parent.Visible = serverExePickable;
serverExecutableDropdown.Parent.IgnoreLayoutGroups = !serverExePickable;
(serverExecutableDropdown.Parent.Parent as GUILayoutGroup)?.Recalculate();
if (serverExecutableDropdown.SelectedComponent is null)
{
serverExecutableDropdown.Select(0);
}
}
break;
case Tab.Tutorials:
UpdateTutorialList();
break;
case Tab.CharacterEditor:
Submarine.MainSub = null;
CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.CharacterEditorScreen));
break;
case Tab.SubmarineEditor:
CoroutineManager.StartCoroutine(SelectScreenWithWaitCursor(GameMain.SubEditorScreen));
break;
case Tab.Mods:
var settings = SettingsMenu.Create(menuTabs[Tab.Settings].RectTransform);
settings.SelectTab(SettingsMenu.Tab.Mods);
tab = Tab.Settings;
break;
case Tab.Credits:
titleText.Visible = false;
creditsPlayer.Restart();
break;
case Tab.Empty:
titleText.Visible = true;
selectedTab = Tab.Empty;
break;
}
selectedTab = tab;
leftTextFooterLayout.Visible = tab != Tab.Credits;
rightTextFooterLayout.Visible = tab != Tab.Credits;
foreach (var tabFrame in menuTabs.Values)
{
tabFrame.Visible = false;
}
if (menuTabs.TryGetValue(selectedTab, out var visibleTab)) { visibleTab.Visible = true; }
return true;
}
private IEnumerable<CoroutineStatus> SelectScreenWithWaitCursor(Screen screen)
{
GUI.SetCursorWaiting();
//tiny delay to get the cursor to render
yield return new WaitForSeconds(0.02f);
GUI.ClearCursorWait();
screen.Select();
yield return CoroutineStatus.Success;
}
public bool ReturnToMainMenu(GUIButton button, object obj)
{
GUI.PreventPauseMenuToggle = false;
if (Selected != this)
{
Select();
}
else
{
ResetButtonStates(button);
}
SelectTab(null, 0);
return true;
}
private void ResetButtonStates(GUIButton button)
{
foreach (GUIComponent child in buttonsParent.Children)
{
GUIButton otherButton = child as GUIButton;
if (otherButton == null || otherButton == button) continue;
otherButton.Selected = false;
}
}
#endregion
public void ResetModUpdateButton()
{
modUpdateStatus = (DateTime.Now, 0);
modUpdatesButton.Visible = false;
}
public void QuickStart(bool fixedSeed = false, Identifier sub = default, float difficulty = 50, LevelGenerationParams levelGenerationParams = null)
{
if (fixedSeed)
{
Rand.SetSyncedSeed(1);
Rand.SetLocalRandom(1);
}
SubmarineInfo selectedSub = null;
Identifier subName = sub.IfEmpty(GameSettings.CurrentConfig.QuickStartSub);
if (!subName.IsEmpty)
{
DebugConsole.NewMessage($"Loading the predefined quick start sub \"{subName}\"", Color.White);
selectedSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName);
if (selectedSub == null)
{
DebugConsole.NewMessage($"Cannot find a sub that matches the name \"{subName}\".", Color.Red);
}
}
if (selectedSub == null)
{
DebugConsole.NewMessage("Loading a random sub.", Color.White);
var subs = SubmarineInfo.SavedSubmarines.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && !s.HasTag(SubmarineTag.HideInMenus));
selectedSub = subs.ElementAt(Rand.Int(subs.Count()));
}
var gamesession = new GameSession(
selectedSub,
Option.None,
GameModePreset.DevSandbox,
missionPrefabs: null);
//(gamesession.GameMode as SinglePlayerCampaign).GenerateMap(ToolBox.RandomSeed(8));
gamesession.StartRound(fixedSeed ? "abcd" : ToolBox.RandomSeed(8), difficulty, levelGenerationParams);
GameMain.GameScreen.Select();
// TODO: modding support
Identifier[] jobIdentifiers = new Identifier[] {
"captain".ToIdentifier(),
"engineer".ToIdentifier(),
"mechanic".ToIdentifier(),
"securityofficer".ToIdentifier(),
"medicaldoctor".ToIdentifier() };
foreach (Identifier job in jobIdentifiers)
{
var jobPrefab = JobPrefab.Get(job);
var variant = Rand.Range(0, jobPrefab.Variants);
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant);
if (characterInfo.Job == null)
{
DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!");
}
gamesession.CrewManager.AddCharacterInfo(characterInfo);
characterInfo.SetNameBasedOnJob();
}
gamesession.CrewManager.InitSinglePlayerRound();
}
private void ShowTutorialSkipWarning(Tab tabToContinueTo)
{
var tutorialSkipWarning = new GUIMessageBox("", TextManager.Get("tutorialskipwarning"), new LocalizedString[] { TextManager.Get("tutorialwarningskiptutorials"), TextManager.Get("tutorialwarningplaytutorials") });
GUIButton.OnClickedHandler proceedToTab(Tab tab)
=> (btn, userdata) =>
{
var config = GameSettings.CurrentConfig;
config.TutorialSkipWarning = false;
GameSettings.SetCurrentConfig(config);
GameSettings.SaveCurrentConfig();
tutorialSkipWarning.Close();
SelectTab(tab);
return true;
};
tutorialSkipWarning.Buttons[0].OnClicked += proceedToTab(tabToContinueTo);
tutorialSkipWarning.Buttons[1].OnClicked += proceedToTab(Tab.Tutorials);
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.CharacterMenus?.ForEach(static m => m.AddToGUIUpdateList());
break;
}
}
private void UpdateTutorialList()
{
foreach (GUITextBlock tutorialText in tutorialList.Content.Children)
{
var tutorial = (Tutorial)tutorialText.UserData;
if (CompletedTutorials.Instance.Contains(tutorial.Identifier) && tutorialText.GetChild<GUIImage>() == null)
{
new GUIImage(new RectTransform(new Point((int)(tutorialText.Padding.X * 0.8f)), tutorialText.RectTransform, Anchor.CenterLeft), style: "ObjectiveIndicatorCompleted");
}
}
}
private bool ChangeMaxPlayers(GUIButton button, object obj)
{
int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers + (int)button.UserData, 1, NetConfig.MaxPlayers);
maxPlayersBox.Text = currMaxPlayers.ToString();
return true;
}
private void TryStartServer()
{
if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
{
var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
{
CoroutineManager.StopCoroutines(waitCoroutine);
return true;
};
}
else
{
StartServer();
}
}
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
{
LocalizedString originalText = messageBox.Text.Text;
int doneCount = 0;
do
{
doneCount = SubmarineInfo.SavedSubmarines.Count(s => !s.CalculatingHash);
messageBox.Text.Text = originalText + $" ({doneCount}/{SubmarineInfo.SavedSubmarines.Count()})";
yield return CoroutineStatus.Running;
} while (doneCount < SubmarineInfo.SavedSubmarines.Count());
messageBox.Close();
StartServer();
yield return CoroutineStatus.Success;
}
private void StartServer()
{
string name = serverNameBox.Text;
GameMain.ResetNetLobbyScreen();
try
{
string fileName;
if (serverExecutableDropdown.SelectedComponent?.UserData is ServerExecutableFile f &&
f.ContentPackage != GameMain.VanillaContent)
{
fileName = Path.Combine(
Path.GetDirectoryName(f.Path.Value),
Path.GetFileNameWithoutExtension(f.Path.Value));
#if WINDOWS
fileName += ".exe";
#endif
}
else
{
#if WINDOWS
fileName = "DedicatedServer.exe";
#else
fileName = "./DedicatedServer";
#endif
}
var arguments = new List<string>
{
"-name", name,
"-public", isPublicBox.Selected.ToString(),
"-playstyle", ((PlayStyle)playstyleBanner.UserData).ToString(),
"-banafterwrongpassword", wrongPasswordBanBox.Selected.ToString(),
"-karmaenabled", (karmaBox.Selected).ToString(),
"-maxplayers", maxPlayersBox.Text,
"-language", languageDropdown.SelectedData.ToString()
};
if (!string.IsNullOrWhiteSpace(passwordBox.Text))
{
arguments.Add("-password");
arguments.Add(passwordBox.Text);
}
else
{
arguments.Add("-nopassword");
}
var puids = EosInterface.IdQueries.GetLoggedInPuids();
var endpoints = new List<Endpoint>();
if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
{
endpoints.Add(new SteamP2PEndpoint(steamId));
}
if (puids.Length > 0)
{
endpoints.Add(new EosP2PEndpoint(puids[0]));
}
if (endpoints.Count == 0)
{
endpoints.Add(new LidgrenEndpoint(IPAddress.Loopback, NetConfig.DefaultPort));
}
if (endpoints.First() is P2PEndpoint firstEndpoint)
{
arguments.Add("-endpoint");
arguments.Add(firstEndpoint.StringRepresentation);
}
int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments.Add("-ownerkey");
arguments.Add(ownerKey.ToString());
#if DEBUG
if (lenientHandshakeBox.Selected)
{
arguments.Add("-lenienthandshake");
NetConfig.UseLenientHandshake = true;
}
#endif
var processInfo = new ProcessStartInfo
{
FileName = fileName,
WorkingDirectory = Directory.GetCurrentDirectory(),
#if !DEBUG
CreateNoWindow = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden
#endif
};
arguments.ForEach(processInfo.ArgumentList.Add);
ChildServerRelay.Start(processInfo);
Thread.Sleep(1000); //wait until the server is ready before connecting
GameMain.Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(
SteamManager.GetUsername().FallbackNullOrEmpty(name)),
endpoints.ToImmutableArray(),
name,
Option.Some(ownerKey));
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to start server", e);
}
}
private bool QuitClicked(GUIButton button, object obj)
{
game.Exit();
return true;
}
private void UpdateOutOfDateWorkshopItemCount()
{
if (DateTime.Now < modUpdateStatus.WhenToRefresh) { return; }
if (!SteamManager.IsInitialized) { return; }
var installedPackages = ContentPackageManager.WorkshopPackages;
var ids = SteamManager.Workshop.GetSubscribedItemIds()
.Select(id => id.Value)
.Union(installedPackages
.Select(pkg => pkg.UgcId)
.NotNone()
.OfType<SteamWorkshopId>()
.Select(id => id.Value));
var count = ids
// Deliberately construct Steamworks.Ugc.Item directly
// to not immediately generate a Workshop data request
.Select(id => new Steamworks.Ugc.Item(id))
.Count(item =>
installedPackages.FirstOrDefault(p
=> p.UgcId.TryUnwrap(out SteamWorkshopId id) && id.Value == item.Id)
is { } pkg
// Checking that this item is downloading, waiting to be downloaded
// or is newer than the currently installed copy should be good enough,
// and should still not make a Workshop data request
&& (item.IsDownloading
|| item.IsDownloadPending
|| (item.InstallTime.TryGetValue(out var workshopInstallTime)
&& pkg.InstallTime.TryUnwrap(out var localInstallTime)
&& localInstallTime.ToUtcValue() < workshopInstallTime)));
modUpdateStatus = (DateTime.Now + ModUpdateInterval, count);
}
private static bool CanHostServer()
=> EosInterface.IdQueries.IsLoggedIntoEosConnect
|| SteamManager.IsInitialized
|| AssemblyInfo.CurrentConfiguration == AssemblyInfo.Configuration.Debug;
public override void Update(double deltaTime)
{
hostServerButton.Enabled = CanHostServer();
gameAnalyticsStatusText.Text = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
UpdateOutOfDateWorkshopItemCount();
modUpdatesButton.Visible = modUpdateStatus.Count > 0;
if (modUpdatesButton.Visible)
{
var modButtonLabelSize =
modsButton.Font.MeasureString(modsButton.Text).ToPoint()
+ new Point(GUI.IntScale(25));
modUpdatesButton.RectTransform.AbsoluteOffset =
(modButtonLabelSize.X, modsButton.Rect.Height / 2 - modUpdatesButton.Rect.Height / 2);
}
switch (selectedTab)
{
case Tab.NewGame:
campaignSetupUI.Update();
break;
}
}
public void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
{
graphics.Clear(Color.Black);
if (backgroundSprite == null)
{
#if UNSTABLE
backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
#endif
if (GUIStyle.GetComponentStyle("MainMenuBackground") is { } mainMenuStyle &&
mainMenuStyle.Sprites.TryGetValue(GUIComponent.ComponentState.None, out var sprites))
{
backgroundSprite = sprites.GetRandomUnsynced()?.Sprite;
}
backgroundSprite ??= LocationType.Prefabs.GetRandomUnsynced()?.GetPortrait(0);
}
var vignette = GUIStyle.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
float vignetteScale = Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y);
Rectangle drawArea = new Rectangle(
(int)(vignette.size.X * vignetteScale / 2), 0,
(int)(GameMain.GraphicsWidth - vignette.size.X * vignetteScale / 2), GameMain.GraphicsHeight);
if (backgroundSprite?.Texture != null)
{
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White, drawArea);
}
if (vignette != null)
{
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
}
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable);
DrawBackground(graphics, spriteBatch);
GUI.Draw(Cam, spriteBatch);
spriteBatch.End();
}
private void StartGame(SubmarineInfo selectedSub, string savePath, string mapSeed, CampaignSettings settings)
{
if (string.IsNullOrEmpty(savePath)) { return; }
var existingSaveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
if (existingSaveFiles.Any(s => s.FilePath == savePath))
{
new GUIMessageBox(TextManager.Get("SaveNameInUseHeader"), TextManager.Get("SaveNameInUseText"));
return;
}
if (selectedSub == null)
{
new GUIMessageBox(TextManager.Get("SubNotSelected"), TextManager.Get("SelectSubRequest"));
return;
}
if (!Directory.Exists(SaveUtil.TempPath))
{
Directory.CreateDirectory(SaveUtil.TempPath, catchUnauthorizedAccessExceptions: true);
}
try
{
File.Copy(selectedSub.FilePath, Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"), overwrite: true, catchUnauthorizedAccessExceptions: false);
}
catch (System.IO.IOException e)
{
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);
GameAnalyticsManager.AddErrorEventOnce(
"MainMenuScreen.StartGame:IOException" + selectedSub.Name,
GameAnalyticsManager.ErrorSeverity.Error,
"Copying a submarine file failed. " + e.Message + "\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
selectedSub = new SubmarineInfo(Path.Combine(SaveUtil.TempPath, selectedSub.Name + ".sub"));
GameMain.GameSession = new GameSession(selectedSub, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.SinglePlayerCampaign, settings, mapSeed);
GameMain.GameSession.CrewManager.ClearCharacterInfos();
foreach (var characterInfo in campaignSetupUI.CharacterMenus.Select(m => m.CharacterInfo))
{
GameMain.GameSession.CrewManager.AddCharacterInfo(characterInfo);
}
((SinglePlayerCampaign)GameMain.GameSession.GameMode).LoadNewLevel();
}
private void LoadGame(string path, Option<uint> backupIndex)
{
if (string.IsNullOrWhiteSpace(path)) return;
try
{
CampaignDataPath dataPath =
backupIndex.TryUnwrap(out uint index)
? new CampaignDataPath(loadPath: SaveUtil.GetBackupPath(path, index), path)
: CampaignDataPath.CreateRegular(path);
SaveUtil.LoadGame(dataPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading save \"" + path + "\" failed", e);
GameMain.GameSession = null;
return;
}
}
#region UI Methods
private void CreateCampaignSetupUI()
{
menuTabs[Tab.NewGame].ClearChildren();
menuTabs[Tab.LoadGame].ClearChildren();
var innerNewGame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.NewGame].RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.02f
};
var newGameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.95f), innerNewGame.RectTransform, Anchor.Center),
style: "InnerFrame");
var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[Tab.LoadGame].RectTransform, Anchor.Center) { AbsoluteOffset = new Point(0, 10) },
style: null);
campaignSetupUI = new SinglePlayerCampaignSetupUI(newGameContent, paddedLoadGame)
{
LoadGame = LoadGame,
StartNewGame = StartGame
};
}
private void CreateHostServerFields()
{
menuTabs[Tab.HostServer].ClearChildren();
var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");
var name = serverSettings.GetAttributeString(nameof(ServerSettings.ServerName), "");
var password = serverSettings.GetAttributeString("password", "");
var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);
int maxPlayersElement = serverSettings.GetAttributeInt("maxplayers", 8);
if (maxPlayersElement > NetConfig.MaxPlayers)
{
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.");
}
int maxPlayers = Math.Clamp(maxPlayersElement, min: 1, max: NetConfig.MaxPlayers);
var karmaEnabled = serverSettings.GetAttributeBool("karmaenabled", false);
var selectedPlayStyle = serverSettings.GetAttributeEnum("playstyle", PlayStyle.Casual);
Vector2 textLabelSize = new Vector2(1.0f, 0.05f);
Alignment textAlignment = Alignment.CenterLeft;
Vector2 textFieldSize = new Vector2(0.5f, 1.0f);
Vector2 tickBoxSize = new Vector2(0.4f, 0.04f);
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 0.95f), menuTabs[Tab.HostServer].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{
RelativeSpacing = 0.01f,
Stretch = true
};
GUIComponent parent = content;
var header = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("HostServerButton"), textAlignment: Alignment.Center, font: GUIStyle.LargeFont) { ForceUpperCase = ForceUpperCase.Yes };
header.RectTransform.IsFixedSize = true;
//play style -----------------------------------------------------
var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent)
{
UserData = PlayStyle.Serious
};
float bannerAspectRatio = (float) playstyleBanner.Sprite.SourceRect.Width / playstyleBanner.Sprite.SourceRect.Height;
playstyleBanner.RectTransform.NonScaledSize = new Point(playstyleBanner.Rect.Width, (int)(playstyleBanner.Rect.Width / bannerAspectRatio));
playstyleBanner.RectTransform.IsFixedSize = true;
new GUIFrame(new RectTransform(playstyleBanner.Rect.Size + new Point(1), playstyleBanner.RectTransform, Anchor.Center), "InnerGlow", color: Color.Black);
new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.05f), playstyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.03f) },
"playstyle name goes here", font: GUIStyle.SmallFont, textAlignment: Alignment.Center, textColor: Color.White, style: "GUISlopedHeader");
new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterLeft)
{ RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
style: "UIToggleButton")
{
OnClicked = (btn, userdata) =>
{
int playStyleIndex = (int)playstyleBanner.UserData - 1;
if (playStyleIndex < 0) { playStyleIndex = Enum.GetValues(typeof(PlayStyle)).Length - 1; }
SetServerPlayStyle((PlayStyle)playStyleIndex);
return true;
}
}.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), playstyleContainer.RectTransform, Anchor.CenterRight)
{ RelativeOffset = new Vector2(0.02f, 0.0f), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
style: "UIToggleButton")
{
OnClicked = (btn, userdata) =>
{
int playStyleIndex = (int)playstyleBanner.UserData + 1;
if (playStyleIndex >= Enum.GetValues(typeof(PlayStyle)).Length) { playStyleIndex = 0; }
SetServerPlayStyle((PlayStyle)playStyleIndex);
return true;
}
};
LocalizedString longestPlayStyleStr = "";
foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
{
LocalizedString playStyleStr = TextManager.Get("servertagdescription." + playStyle);
if (playStyleStr.Length > longestPlayStyleStr.Length) { longestPlayStyleStr = playStyleStr; }
}
playstyleDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), playstyleContainer.RectTransform, Anchor.BottomCenter),
longestPlayStyleStr, style: null, wrap: true)
{
Color = Color.Black * 0.8f,
TextColor = GUIStyle.GetComponentStyle("GUITextBlock").TextColor
};
playstyleDescription.Padding = Vector4.One * 10.0f * GUI.Scale;
playstyleDescription.CalculateHeightFromText(padding: (int)(15 * GUI.Scale));
playstyleDescription.RectTransform.NonScaledSize = new Point(playstyleDescription.Rect.Width, playstyleDescription.Rect.Height);
playstyleDescription.RectTransform.IsFixedSize = true;
playstyleContainer.RectTransform.NonScaledSize = new Point(playstyleContainer.Rect.Width, playstyleBanner.Rect.Height + playstyleDescription.Rect.Height);
playstyleContainer.RectTransform.IsFixedSize = true;
SetServerPlayStyle(selectedPlayStyle);
//other settings -----------------------------------------------------
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
var label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerName"), textAlignment: textAlignment);
serverNameBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: name, textAlignment: textAlignment)
{
MaxTextLength = NetConfig.ServerNameMaxLength,
OverflowClip = true
};
label.RectTransform.IsFixedSize = true;
var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment);
var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.1f
};
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIMinusButton", textAlignment: Alignment.Center)
{
UserData = -1,
OnClicked = ChangeMaxPlayers,
ClickSound = GUISoundType.Decrease
};
maxPlayersBox = new GUITextBox(new RectTransform(new Vector2(0.6f, 1.0f), buttonContainer.RectTransform), textAlignment: Alignment.Center)
{
Text = maxPlayers.ToString()
};
maxPlayersBox.OnEnterPressed += (GUITextBox sender, string text) =>
{
maxPlayersBox.Deselect();
return true;
};
maxPlayersBox.OnDeselected += (GUITextBox sender, Microsoft.Xna.Framework.Input.Keys key) =>
{
int.TryParse(maxPlayersBox.Text, out int currMaxPlayers);
currMaxPlayers = (int)MathHelper.Clamp(currMaxPlayers, 1, NetConfig.MaxPlayers);
maxPlayersBox.Text = currMaxPlayers.ToString();
};
new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "GUIPlusButton", textAlignment: Alignment.Center)
{
UserData = 1,
OnClicked = ChangeMaxPlayers,
ClickSound = GUISoundType.Increase
};
maxPlayersLabel.RectTransform.IsFixedSize = true;
label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("Password"), textAlignment: textAlignment);
passwordBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), text: password, textAlignment: textAlignment)
{
Censor = true
};
label.RectTransform.IsFixedSize = true;
var languageLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
TextManager.Get("Language"), textAlignment: textAlignment);
languageDropdown = new GUIDropDown(new RectTransform(textFieldSize, languageLabel.RectTransform, Anchor.CenterRight));
foreach (var language in ServerLanguageOptions.Options)
{
languageDropdown.AddItem(language.Label, language.Identifier);
}
var defaultLanguage = ServerLanguageOptions.PickLanguage(GameSettings.CurrentConfig.Language);
var settingsLanguage = serverSettings.GetAttributeIdentifier("language", defaultLanguage.Value).ToLanguageIdentifier();
if (!ServerLanguageOptions.Options.Any(o => o.Identifier == settingsLanguage))
{
settingsLanguage = defaultLanguage;
}
languageDropdown.Select(ServerLanguageOptions.Options.FindIndex(o => o.Identifier == settingsLanguage));
var serverExecutableLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform),
TextManager.Get("ServerExecutable"), textAlignment: textAlignment);
const string vanillaServerOption = "Vanilla";
serverExecutableDropdown
= new GUIDropDown(new RectTransform(textFieldSize, serverExecutableLabel.RectTransform, Anchor.CenterRight),
vanillaServerOption);
var listBoxSize = serverExecutableDropdown.ListBox.RectTransform.RelativeSize;
serverExecutableDropdown.ListBox.RectTransform.RelativeSize = new Vector2(listBoxSize.X * 1.5f, listBoxSize.Y);
serverExecutableDropdown.AddItem(vanillaServerOption, userData: null);
serverExecutableDropdown.OnSelected = (selected, userData) =>
{
if (userData != null)
{
var warningBox = new GUIMessageBox(headerText: TextManager.Get("Warning"),
text: TextManager.GetWithVariable("ModServerExesAtYourOwnRisk", "[exename]", serverExecutableDropdown.Text),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
warningBox.Buttons[0].OnClicked = (_, __) =>
{
warningBox.Close();
return false;
};
warningBox.Buttons[1].OnClicked = (_, __) =>
{
serverExecutableDropdown.Select(0);
warningBox.Close();
return false;
};
}
serverExecutableDropdown.Text = ToolBox.LimitString(serverExecutableDropdown.Text,
serverExecutableDropdown.Font, serverExecutableDropdown.Rect.Width * 8 / 10);
return true;
};
serverExecutableLabel.RectTransform.IsFixedSize = true;
// tickbox upper ---------------
var tickboxAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
isPublicBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("PublicServer"))
{
Selected = isPublic,
ToolTip = TextManager.Get("PublicServerToolTip")
};
wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaUpper.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"))
{
Selected = banAfterWrongPassword
};
tickboxAreaUpper.RectTransform.IsFixedSize = true;
// tickbox lower ---------------
var tickboxAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, tickBoxSize.Y), parent.RectTransform), isHorizontal: true);
karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), TextManager.Get("HostServerKarmaSetting"))
{
Selected = karmaEnabled,
ToolTip = TextManager.Get("hostserverkarmasettingtooltip")
};
#if DEBUG
lenientHandshakeBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickboxAreaLower.RectTransform), "DEBUG: Lenient server startup timeouts")
{
Selected = true,
ToolTip = "Start with more lenient Lidgren handshake timeouts. The server is more likely to start even when running multiple instances on the same machine under heavy load."
};
#endif
tickboxAreaLower.RectTransform.IsFixedSize = true;
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), content.RectTransform), style: null);
new GUIButton(new RectTransform(new Vector2(0.4f, 0.07f), content.RectTransform), TextManager.Get("StartServerButton"), style: "GUIButtonLarge")
{
OnClicked = (btn, userdata) =>
{
CheckServerName();
return true;
}
};
void CheckServerName()
{
string name = serverNameBox.Text;
if (string.IsNullOrEmpty(name))
{
serverNameBox.Flash();
return;
}
if (isPublicBox.Selected && ForbiddenWordFilter.IsForbidden(name, out string forbiddenWord))
{
var msgBox = new GUIMessageBox("",
TextManager.GetWithVariables("forbiddenservernameverification", ("[forbiddenword]", forbiddenWord), ("[servername]", name)),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked += (_, __) =>
{
CheckServerExe();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
return;
}
CheckServerExe();
}
void CheckServerExe()
{
if (serverExecutableDropdown?.SelectedData is ServerExecutableFile serverExe &&
serverExe.ContentPackage.GameVersion < GameMain.VanillaContent.GameVersion)
{
var msgBox = new GUIMessageBox(string.Empty,
TextManager.GetWithVariables("versionmismatchwarning",
("[gameversion]", serverExe.ContentPackage.GameVersion.ToString()),
("[contentversion]", GameMain.VanillaContent.GameVersion.ToString())) + "\n\n"+
TextManager.GetWithVariable("versionmismatch.verifylaunch", "[exename]", serverExe.ContentPackage.Name),
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
msgBox.Buttons[0].OnClicked += (_, __) =>
{
TryStartServer();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked += msgBox.Close;
return;
}
TryStartServer();
}
}
private void SetServerPlayStyle(PlayStyle playStyle)
{
playstyleBanner.Sprite = GUIStyle
.GetComponentStyle($"PlayStyleBanner.{playStyle}")
.GetSprite(GUIComponent.ComponentState.None);
playstyleBanner.UserData = playStyle;
var nameText = playstyleBanner.GetChild<GUITextBlock>();
nameText.Text = TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag." + playStyle));
nameText.Color = playstyleBanner.Sprite
.SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();
playstyleDescription.Text = TextManager.Get("servertagdescription." + playStyle);
playstyleDescription.TextAlignment = playstyleDescription.WrappedText.Contains('\n') ?
Alignment.CenterLeft : Alignment.Center;
}
#endregion
private void FetchRemoteContent()
{
string remoteContentUrl = GameSettings.CurrentConfig.RemoteMainMenuContentUrl;
if (string.IsNullOrEmpty(remoteContentUrl)) { return; }
try
{
var client = RestFactory.CreateClient(remoteContentUrl);
var request = RestFactory.CreateRequest("MenuContent.xml");
TaskPool.Add("RequestMainMenuRemoteContent", client.ExecuteAsync(request),
RemoteContentReceived);
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Fetching remote content to the main menu failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.FetchRemoteContent:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Fetching remote content to the main menu failed. " + e.Message);
return;
}
}
private void RemoteContentReceived(Task t)
{
try
{
if (!t.TryGetResult(out IRestResponse remoteContentResponse)) { throw new Exception("Task did not return a valid result"); }
if (remoteContentResponse.ErrorException != null)
{
DebugConsole.AddWarning($"Connection error: Failed to fetch remote main menu content " +
$"({remoteContentResponse.ErrorException.Message}).");
return;
}
if (remoteContentResponse.StatusCode != HttpStatusCode.OK)
{
DebugConsole.AddWarning(
"Failed to receive remote main menu content. " +
$"The master server might be temporarily unavailable (HTTP error: {remoteContentResponse.StatusCode})");
return;
}
string xml = remoteContentResponse.Content;
int index = xml.IndexOf('<');
if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
if (!string.IsNullOrWhiteSpace(xml))
{
remoteContentDoc = XDocument.Parse(xml);
foreach (var subElement in remoteContentDoc?.Root.Elements())
{
GUIComponent.FromXML(subElement.FromPackage(null), remoteContentContainer.RectTransform);
}
}
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.RemoteContentReceived:Exception", GameAnalyticsManager.ErrorSeverity.Error,
"Reading received remote main menu content failed. " + e.Message);
}
}
}
}