1757 lines
81 KiB
C#
1757 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()
|
|
{
|
|
LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() =>
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|