using Microsoft.Xna.Framework; using System; using System.Globalization; using System.Linq; namespace Barotrauma { partial class SubmarineInfo : IDisposable { public Sprite PreviewImage; partial void InitProjectSpecific() { string previewImageData = SubmarineElement.GetAttributeString("previewimage", ""); if (!string.IsNullOrEmpty(previewImageData)) { try { using (System.IO.MemoryStream mem = new System.IO.MemoryStream(Convert.FromBase64String(previewImageData))) { var texture = TextureLoader.FromStream(mem, path: FilePath, compress: false); if (texture == null) { throw new Exception("PreviewImage texture returned null"); } PreviewImage = new Sprite(texture, sourceRectangle: null, newOffset: null, path: FilePath); } } catch (Exception e) { DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e); GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsManager.ErrorSeverity.Error, "Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted."); PreviewImage = null; } } } public void CreatePreviewWindow(GUIComponent parent) { var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null); var previewButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), content.RectTransform), style: null) { CanBeFocused = SubmarineElement != null, OnClicked = (btn, obj) => { SubmarinePreview.Create(this); return false; }, }; var previewImage = PreviewImage ?? savedSubmarines.Find(s => s.Name.Equals(Name, StringComparison.OrdinalIgnoreCase))?.PreviewImage; if (previewImage == null) { new GUITextBlock(new RectTransform(Vector2.One, previewButton.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded")); } else { var submarinePreviewBackground = new GUIFrame(new RectTransform(Vector2.One, previewButton.RectTransform), style: null) { Color = Color.Black, HoverColor = Color.Black, SelectedColor = Color.Black, PressedColor = Color.Black, CanBeFocused = false, }; new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), previewImage, scaleToFit: true) { CanBeFocused = false }; new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black) { CanBeFocused = false }; } if (SubmarineElement != null) { new GUIFrame(new RectTransform(Vector2.One * 0.12f, previewButton.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * previewButton.Rect.Height)) }, "ExpandButton", Color.White) { Color = Color.White, HoverColor = Color.White, PressedColor = Color.White }; } var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter)) { UserData = "descriptionbox", ScrollBarVisible = true, Spacing = 5, CurrentSelectMode = GUIListBox.SelectMode.None }; GUIFont font = parent.Rect.Width < 350 ? GUIStyle.SmallFont : GUIStyle.Font; CreateSpecsWindow(descriptionBox, font, includeDescription: true); } public void CreateSpecsWindow(GUIListBox parent, GUIFont font, bool includeTitle = true, bool includeClass = true, bool includeDescription = false, bool includeCrushDepth = false) { float leftPanelWidth = 0.6f; float rightPanelWidth = 0.4f / leftPanelWidth; LocalizedString className = !HasTag(SubmarineTag.Shuttle) ? TextManager.GetWithVariables("submarine.classandtier", ("[class]", TextManager.Get($"submarineclass.{SubmarineClass}")), ("[tier]", TextManager.Get($"submarinetier.{Tier}"))) : TextManager.Get("shuttle"); int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y; int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth); GUITextBlock submarineNameText = null; GUITextBlock submarineClassText = null; if (includeTitle) { int nameHeight = (int)GUIStyle.LargeFont.MeasureString(DisplayName, true).Y; submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUIStyle.LargeFont) { CanBeFocused = false }; submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y); } if (includeClass) { submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUIStyle.SubHeadingFont) { ToolTip = TextManager.Get("submarinetierandclass.description")+"\n\n"+ TextManager.Get($"submarineclass.{SubmarineClass}.description") }; submarineClassText.HoverColor = Color.Transparent; submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y); } if (Price > 0) { var priceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("subeditor.price"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), priceText.RectTransform, Anchor.TopRight, Pivot.TopLeft), TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", Price)), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; } Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio; if (realWorldDimensions != Vector2.Zero) { LocalizedString dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", ("[width]", ((int)realWorldDimensions.X).ToString()), ("[height]", ((int)realWorldDimensions.Y).ToString())); var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft), dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height); } var cargoCapacityStr = CargoCapacity < 0 ? TextManager.Get("unknown") : TextManager.GetWithVariable("cargocapacityformat", "[cratecount]", CargoCapacity.ToString()); var cargoCapacityText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("cargocapacity"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), cargoCapacityText.RectTransform, Anchor.TopRight, Pivot.TopLeft), cargoCapacityStr, textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; cargoCapacityText.RectTransform.MinSize = new Point(0, cargoCapacityText.Children.First().Rect.Height); if (includeCrushDepth) { var crushDepthText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("CrushDepth"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crushDepthText.RectTransform, Anchor.TopRight, Pivot.TopLeft), TextManager.GetWithVariable("meterformat", "[meters]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetSubCrushDepth())), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; crushDepthText.RectTransform.MinSize = new Point(0, crushDepthText.Children.First().Rect.Height); } if (RecommendedCrewSizeMax > 0) { var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft), RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height); } if (RecommendedCrewExperience != CrewExperienceLevel.Unknown) { var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft), TextManager.Get(RecommendedCrewExperience.ToIdentifier()), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height); } if (RequiredContentPackages.Any()) { var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft), string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height); } // show what game version the submarine was created on if (!IsVanillaSubmarine() && GameVersion != null) { var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform), TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft), GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true) { CanBeFocused = false }; versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height); } if (submarineNameText != null) { submarineNameText.AutoScaleHorizontal = true; } GUITextBlock descBlock = null; if (includeDescription) { //space new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), parent.Content.RectTransform), style: null); if (!Description.IsNullOrEmpty()) { var wsItemDesc = new GUITextBlock(new RectTransform(new Vector2(1, 0), parent.Content.RectTransform), TextManager.Get("SaveSubDialogDescription", "WorkshopItemDescription"), font: GUIStyle.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = ForceUpperCase.Yes }; descBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), parent.Content.RectTransform), Description, font: font, wrap: true) { CanBeFocused = false }; } } GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren().Where(c => c != submarineNameText && c != descBlock)); parent.ForceLayoutRecalculation(); } public readonly record struct PendingSubInfo(SubmarineInfo PendingSub = null, bool StructuresDefineRealWorldCrushDepth = false, float RealWorldCrushDepth = Level.DefaultRealWorldCrushDepth); private float GetSubCrushDepth() { var pendingSubInfo = new PendingSubInfo(); return GetSubCrushDepth(this, ref pendingSubInfo); } public static float GetSubCrushDepth(SubmarineInfo subInfo, ref PendingSubInfo pendingSubInfo) { float subCrushDepth = Level.DefaultRealWorldCrushDepth; if (Submarine.MainSub != null && Submarine.MainSub.Info == subInfo) { subCrushDepth = Submarine.MainSub.RealWorldCrushDepth; } else if (subInfo != null) { if (pendingSubInfo.PendingSub != subInfo) { // Store the real world crush depth for the pending sub so that we don't have to calculate it again every time pendingSubInfo = new PendingSubInfo(subInfo, subInfo.IsCrushDepthDefinedInStructures(out float realWorldCrushDepth), realWorldCrushDepth); } subCrushDepth = pendingSubInfo.RealWorldCrushDepth; } if (GameMain.GameSession?.Campaign?.UpgradeManager != null && UpgradePrefab.Find("increasewallhealth".ToIdentifier()) is UpgradePrefab hullUpgradePrefab) { int pendingLevel = GameMain.GameSession.Campaign.UpgradeManager.GetUpgradeLevel(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), info: subInfo); // If there is a sub switch pending, unless its structures have crush depth defined in their elements, // calculate the value based on the default crush depth and pending upgrade level int currentLevel = 0; if (pendingSubInfo.PendingSub is null || pendingSubInfo.StructuresDefineRealWorldCrushDepth) { currentLevel = GameMain.GameSession.Campaign.UpgradeManager.GetRealUpgradeLevelForSub(hullUpgradePrefab, hullUpgradePrefab.UpgradeCategories.First(), subInfo); } if (pendingLevel > currentLevel) { string updateValueStr = hullUpgradePrefab.SourceElement?.GetChildElement("Structure")?.GetAttributeString("crushdepth", null); if (!string.IsNullOrEmpty(updateValueStr)) { if (currentLevel > 0) { // If the current level is greater than 0, reset the crush depth value back to the base value before calculating the upgrade int upgradePercentage = UpgradePrefab.ParsePercentage(updateValueStr, Identifier.Empty, suppressWarnings: true); subCrushDepth /= (1f + (upgradePercentage / 100f * currentLevel)); } subCrushDepth = PropertyReference.CalculateUpgrade(subCrushDepth, pendingLevel, updateValueStr); } } } return subCrushDepth; } } }