using Barotrauma.Tutorials; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; namespace Barotrauma { class CampaignSetupUI { private GUIComponent newGameContainer, loadGameContainer; private GUIListBox subList; private GUIListBox saveList; private GUITextBox saveNameBox, seedBox; private GUITickBox contextualTutorialBox; private GUIButton loadGameButton; public Action StartNewGame; public Action LoadGame; public bool TutorialSelected { get { if (contextualTutorialBox == null) return false; return contextualTutorialBox.Selected; } } private readonly bool isMultiplayer; public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable submarines, IEnumerable saveFiles = null) { this.isMultiplayer = isMultiplayer; this.newGameContainer = newGameContainer; this.loadGameContainer = loadGameContainer; var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) { Stretch = true, RelativeSpacing = 0.02f }; var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) { RelativeSpacing = 0.02f }; // New game left side new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), leftColumn.RectTransform), TextManager.Get("SelectedSub") + ":", textAlignment: Alignment.BottomLeft); subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)) { OnSelected = CheckForPax }; UpdateSubList(submarines); // New game right sideon new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("SaveName") + ":", textAlignment: Alignment.BottomLeft); saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), string.Empty); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("MapSeed") + ":", textAlignment: Alignment.BottomLeft); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), ToolBox.RandomSeed(8)); if (!isMultiplayer) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("TutorialActive") + ":", textAlignment: Alignment.BottomLeft); contextualTutorialBox = new GUITickBox(new RectTransform(new Point(30, 30), rightColumn.RectTransform), string.Empty); UpdateTutorialSelection(); } var startButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.13f), rightColumn.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton"), style: "GUIButtonLarge") { IgnoreLayoutGroups = true, OnClicked = (GUIButton btn, object userData) => { if (string.IsNullOrWhiteSpace(saveNameBox.Text)) { saveNameBox.Flash(Color.Red); return false; } if (!(subList.SelectedData is Submarine selectedSub)) { return false; } if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) { ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f; subList.SelectedComponent.CanBeFocused = false; subList.Deselect(); return false; } string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer, saveNameBox.Text); bool hasRequiredContentPackages = selectedSub.RequiredContentPackages.All(cp => GameMain.SelectedPackages.Any(cp2 => cp2.Name == cp)); if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) { if (!hasRequiredContentPackages) { var msgBox = new GUIMessageBox(TextManager.Get("ContentPackageMismatch"), TextManager.Get("ContentPackageMismatchWarning") .Replace("[requiredcontentpackages]", string.Join(", ", selectedSub.RequiredContentPackages)), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); msgBox.Buttons[0].OnClicked = msgBox.Close; msgBox.Buttons[0].OnClicked += (button, obj) => { if (GUIMessageBox.MessageBoxes.Count == 0) { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); if (isMultiplayer) { CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } } return true; }; msgBox.Buttons[1].OnClicked = msgBox.Close; } if (selectedSub.HasTag(SubmarineTag.Shuttle)) { var msgBox = new GUIMessageBox(TextManager.Get("ShuttleSelected"), TextManager.Get("ShuttleWarning"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }); msgBox.Buttons[0].OnClicked = (button, obj) => { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); if (isMultiplayer) { CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked = msgBox.Close; return false; } } else { StartNewGame?.Invoke(selectedSub, savePath, seedBox.Text); if (isMultiplayer) { CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } } return true; } }; UpdateLoadMenu(saveFiles); } private IEnumerable WaitForCampaignSetup() { string headerText = TextManager.Get("CampaignStartingPleaseWait"); var msgBox = new GUIMessageBox(headerText, TextManager.Get("CampaignStarting"), new string[] { TextManager.Get("Cancel") }); msgBox.Buttons[0].OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.SelectMode(0); CoroutineManager.StopCoroutines("WaitForCampaignSetup"); return true; }; msgBox.Buttons[0].OnClicked += msgBox.Close; DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10); while (GameMain.NetLobbyScreen.CampaignUI == null && DateTime.Now < timeOut) { msgBox.Header.Text = headerText + new string('.', ((int)Timing.TotalTime % 3 + 1)); yield return CoroutineStatus.Running; } msgBox.Close(); yield return CoroutineStatus.Success; } public void CreateDefaultSaveName() { string savePath = SaveUtil.CreateSavePath(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); saveNameBox.Text = Path.GetFileNameWithoutExtension(savePath); } public void UpdateSubList(IEnumerable submarines) { #if DEBUG var subsToShow = submarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)); #else var subsToShow = submarines; #endif subList.ClearChildren(); foreach (Submarine sub in subsToShow) { var textBlock = new GUITextBlock( new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { AbsoluteOffset = new Point(10, 0) }, ToolBox.LimitString(sub.Name, GUI.Font, subList.Rect.Width - 65), style: "ListBoxElement") { ToolTip = sub.Description, UserData = sub }; var infoButton = new GUIButton(new RectTransform(new Vector2(0.12f, 0.8f), textBlock.RectTransform, Anchor.CenterRight), text: "?") { UserData = sub }; infoButton.OnClicked += (component, userdata) => { // TODO: use relative size ((Submarine)userdata).CreatePreviewWindow(new GUIMessageBox("", "", 550, 400)); return true; }; if (sub.HasTag(SubmarineTag.Shuttle)) { textBlock.TextColor = textBlock.TextColor * 0.85f; var shuttleText = new GUITextBlock(new RectTransform(new Point(100, textBlock.Rect.Height), textBlock.RectTransform, Anchor.CenterRight) { IsFixedSize = false, RelativeOffset = new Vector2(infoButton.RectTransform.RelativeSize.X + 0.01f, 0) }, TextManager.Get("Shuttle"), textAlignment: Alignment.Right, font: GUI.SmallFont) { TextColor = textBlock.TextColor * 0.8f, ToolTip = textBlock.ToolTip }; } } if (Submarine.SavedSubmarines.Any()) { var nonShuttles = subsToShow.Where(s => !s.HasTag(SubmarineTag.Shuttle)).ToList(); if (nonShuttles.Count > 0) { subList.Select(nonShuttles[Rand.Int(nonShuttles.Count)]); } } } public void UpdateLoadMenu(IEnumerable saveFiles=null) { loadGameContainer.ClearChildren(); if (saveFiles == null) { saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); } saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft)) { OnSelected = SelectSaveFile }; saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft)) { OnSelected = SelectSaveFile }; foreach (string saveFile in saveFiles) { XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile); var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform), style: "ListBoxElement") { UserData = saveFile }; var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), text: Path.GetFileNameWithoutExtension(saveFile)); if (doc?.Root == null) { DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted."); nameText.Color = Color.Red; continue; } string submarineName = doc.Root.GetAttributeString("submarine", ""); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), text: submarineName, font: GUI.SmallFont) { UserData = saveFile }; string saveTime = doc.Root.GetAttributeString("savetime", ""); new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont) { UserData = saveFile }; } saveList.Content.RectTransform.SortChildren((c1, c2) => { string file1 = c1.GUIComponent.UserData as string; string file2 = c2.GUIComponent.UserData as string; DateTime file1WriteTime = DateTime.MinValue; DateTime file2WriteTime = DateTime.MinValue; try { file1WriteTime = File.GetLastWriteTime(file1); } catch { //do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list }; try { file2WriteTime = File.GetLastWriteTime(file2); } catch { //do nothing - DateTime.MinValue will be used and the element will get sorted at the bottom of the list }; return file2WriteTime.CompareTo(file1WriteTime); }); loadGameButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.12f), loadGameContainer.RectTransform, Anchor.BottomRight), TextManager.Get("LoadButton"), style: "GUIButtonLarge") { OnClicked = (btn, obj) => { if (string.IsNullOrWhiteSpace(saveList.SelectedData as string)) return false; LoadGame?.Invoke(saveList.SelectedData as string); if (isMultiplayer) { CoroutineManager.StartCoroutine(WaitForCampaignSetup(), "WaitForCampaignSetup"); } return true; }, Enabled = false }; } public void UpdateTutorialSelection() { if (isMultiplayer) return; Tutorial contextualTutorial = Tutorial.Tutorials.Find(t => t is ContextualTutorial); Submarine selectedSub = subList.SelectedData as Submarine; if (selectedSub == null || selectedSub.Name != "PAX") { contextualTutorialBox.Selected = (contextualTutorial != null) ? !GameMain.Config.CompletedTutorialNames.Contains(contextualTutorial.Name) : true; } else { contextualTutorialBox.Selected = true; } } private bool CheckForPax(GUIComponent component, object obj) { if (!(obj is Submarine) || contextualTutorialBox == null) return false; Submarine sub = obj as Submarine; contextualTutorialBox.Selected = sub.Name == "PAX"; return true; } private bool SelectSaveFile(GUIComponent component, object obj) { if (isMultiplayer) { loadGameButton.Enabled = true; return true; } string fileName = (string)obj; XDocument doc = SaveUtil.LoadGameSessionDoc(fileName); if (doc == null) { DebugConsole.ThrowError("Error loading save file \"" + fileName + "\". The file may be corrupted."); return false; } loadGameButton.Enabled = true; RemoveSaveFrame(); string subName = doc.Root.GetAttributeString("submarine", ""); string saveTime = doc.Root.GetAttributeString("savetime", "unknown"); string mapseed = doc.Root.GetAttributeString("mapseed", "unknown"); var saveFileFrame = new GUIFrame(new RectTransform(new Vector2(0.45f, 0.6f), loadGameContainer.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "InnerFrame") { UserData = "savefileframe" }; new GUITextBlock(new RectTransform(new Vector2(1, 0.2f), saveFileFrame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0, 0.05f) }, Path.GetFileNameWithoutExtension(fileName), font: GUI.LargeFont, textAlignment: Alignment.Center); var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), saveFileFrame.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0, 0.1f) }); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("Submarine")} : {subName}", font: GUI.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("LastSaved")} : {saveTime}", font: GUI.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1, 0), layoutGroup.RectTransform), $"{TextManager.Get("MapSeed")} : {mapseed}", font: GUI.SmallFont); new GUIButton(new RectTransform(new Vector2(0.4f, 0.15f), saveFileFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0, 0.1f) }, TextManager.Get("Delete")) { UserData = fileName, OnClicked = DeleteSave }; return true; } private bool DeleteSave(GUIButton button, object obj) { string saveFile = obj as string; if (obj == null) return false; SaveUtil.DeleteSave(saveFile); UpdateLoadMenu(); return true; } private void RemoveSaveFrame() { GUIComponent prevFrame = null; foreach (GUIComponent child in loadGameContainer.Children) { if (child.UserData as string != "savefileframe") continue; prevFrame = child; break; } loadGameContainer.RemoveChild(prevFrame); } } }