#nullable enable using System; using System.Collections.Generic; using System.Linq; using Barotrauma.Extensions; using Barotrauma.IO; using Barotrauma.Networking; using Barotrauma.Steam; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Color = Microsoft.Xna.Framework.Color; using ServerContentPackage = Barotrauma.Networking.ClientPeer.ServerContentPackage; namespace Barotrauma { class ModDownloadScreen : Screen { private readonly Queue pendingDownloads = new Queue(); private ServerContentPackage? currentDownload; private readonly List downloadedPackages = new List(); private bool confirmDownload; private void Reset() { pendingDownloads.Clear(); downloadedPackages.Clear(); currentDownload = null; confirmDownload = false; } private void DeletePrevDownloads() { if (Directory.Exists(ModReceiver.DownloadFolder)) { Directory.Delete(ModReceiver.DownloadFolder, recursive: true); } } public override void Select() { base.Select(); DeletePrevDownloads(); Reset(); Frame.ClearChildren(); var mainVisibleFrame = new GUIFrame(new RectTransform((0.6f, 0.8f), Frame.RectTransform, Anchor.Center)); GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.93f, mainVisibleFrame.RectTransform, Anchor.Center)); void mainLayoutSpacing() => new GUIFrame(new RectTransform((1.0f, 0.02f), mainLayout.RectTransform), style: null); var serverName = new GUITextBlock(new RectTransform((1.0f, 0.08f), mainLayout.RectTransform), "", font: GUIStyle.LargeFont, textAlignment: Alignment.CenterLeft) { TextGetter = () => GameMain.NetLobbyScreen.ServerName.Text }; mainLayoutSpacing(); var downloadList = new GUIListBox(new RectTransform((1.0f, 0.76f), mainLayout.RectTransform)); mainLayoutSpacing(); var disconnectButton = new GUIButton(new RectTransform((0.3f, 0.1f), mainLayout.RectTransform), TextManager.Get("Disconnect")) { OnClicked = (guiButton, o) => { GameMain.Client?.Disconnect(); GameMain.MainMenuScreen.Select(); return false; } }; var missingPackages = GameMain.Client.ClientPeer.ServerContentPackages .Where(sp => sp.ContentPackage is null).ToArray(); if (!missingPackages.Any()) { if (!GameMain.Client.IsServerOwner) { ContentPackageManager.EnabledPackages.BackUp(); ContentPackageManager.EnabledPackages.SetCore( GameMain.Client.ClientPeer.ServerContentPackages .Select(p => p.CorePackage) .First(p => p != null)); ContentPackageManager.EnabledPackages.SetRegular( GameMain.Client.ClientPeer.ServerContentPackages .Select(p => p.RegularPackage) .Where(p => p != null).ToArray()); } GameMain.NetLobbyScreen.Select(); GameMain.LuaCs.Initialize(); return; } GUIMessageBox msgBox = new GUIMessageBox( TextManager.Get("ModDownloadTitle"), "", Array.Empty(), relativeSize: (0.5f, 0.75f)); GUILayoutGroup innerLayout = msgBox.Content; innerLayout.Stretch = true; void innerLayoutSpacing(float height) => new GUIFrame(new RectTransform((1.0f, height), innerLayout.RectTransform), style: null); GUITextBlock textBlock(LocalizedString str, GUIFont font, Alignment alignment = Alignment.CenterLeft) { var tb = new GUITextBlock(new RectTransform(Point.Zero, innerLayout.RectTransform), str, wrap: true, textAlignment: alignment, font: font); new GUICustomComponent(new RectTransform(Vector2.Zero, tb.RectTransform), onUpdate: (deltaTime, component) => { if (tb.RectTransform.NonScaledSize.X != innerLayout.Rect.Width) { tb.RectTransform.NonScaledSize = (innerLayout.Rect.Width, 0); tb.RectTransform.NonScaledSize = (innerLayout.Rect.Width, (int)tb.Font.MeasureString(tb.WrappedText).Y); } }); return tb; } var header = textBlock(TextManager.Get("ModDownloadHeader"), GUIStyle.Font); innerLayoutSpacing(0.05f); var msgBoxModList = new GUIListBox(new RectTransform(Vector2.One, innerLayout.RectTransform)); innerLayoutSpacing(0.05f); var footer = textBlock(TextManager.Get("ModDownloadFooter"), GUIStyle.Font, Alignment.Center); innerLayoutSpacing(0.05f); GUILayoutGroup buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), innerLayout.RectTransform), isHorizontal: true); void buttonContainerSpacing(float width) => new GUIFrame(new RectTransform((width, 1.0f), buttonContainer.RectTransform), style: null); void button(LocalizedString text, Action action, float width = 0.3f) => new GUIButton(new RectTransform((width, 1.0f), buttonContainer.RectTransform), text) { OnClicked = (_, __) => { action(); msgBox.Close(); return false; } }; buttonContainerSpacing(0.1f); button(TextManager.Get("Yes"), () => confirmDownload = true); buttonContainerSpacing(0.2f); button(TextManager.Get("No"), () => { GameMain.Client?.Disconnect(); GameMain.MainMenuScreen.Select(); }); buttonContainerSpacing(0.1f); var missingIds = missingPackages.Where( mp => mp.WorkshopId != 0 && ContentPackageManager.WorkshopPackages.All(wp => wp.SteamWorkshopId != mp.WorkshopId)) .Select(mp => mp.WorkshopId) .ToArray(); if (missingIds.Any() && SteamManager.IsInitialized) { buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), innerLayout.RectTransform), isHorizontal: true); buttonContainerSpacing(0.15f); button(TextManager.Get("SubscribeToAllOnWorkshop"), () => { if (GameMain.Client != null) { BulkDownloader.SubscribeToServerMods(missingIds, rejoinEndpoint: GameMain.Client.ClientPeer.ServerConnection.EndPointString, rejoinLobby: SteamManager.CurrentLobbyID, rejoinServerName: GameMain.NetLobbyScreen.ServerName.Text); GameMain.Client.Disconnect(); } GameMain.MainMenuScreen.Select(); }, width: 0.7f); buttonContainerSpacing(0.15f); } foreach (var p in missingPackages) { pendingDownloads.Enqueue(p); //Message box frame new GUITextBlock(new RectTransform((1.0f, 0.1f), msgBoxModList.Content.RectTransform), p.Name) { CanBeFocused = false }; //Download progress frame var downloadFrame = new GUIFrame(new RectTransform((1.0f, 0.06f), downloadList.Content.RectTransform), style: "ListBoxElement") { UserData = p, CanBeFocused = false }; new GUITextBlock(new RectTransform((0.5f, 1.0f), downloadFrame.RectTransform), p.Name) { CanBeFocused = false }; var downloadProgress = new GUIProgressBar( new RectTransform((0.5f, 0.75f), downloadFrame.RectTransform, Anchor.CenterRight), 0.0f, color: GUIStyle.Green); downloadProgress.ProgressGetter = () => { if (currentDownload == p) { FileReceiver.FileTransferIn? getTransfer() => GameMain.Client?.FileReceiver.ActiveTransfers.FirstOrDefault(t => t.FileType == FileTransferType.Mod); if (downloadProgress.GetAnyChild() is null) { GUILayoutGroup progressBarLayout = new GUILayoutGroup(new RectTransform(Vector2.One, downloadProgress.RectTransform), isHorizontal: true); void progressBarText(float width, Alignment textAlignment, Func getter) { var textContainer = new GUIFrame(new RectTransform((width, 1.0f), progressBarLayout.RectTransform), style: null); var textShadow = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(3)) }, "", textColor: Color.Black, textAlignment: textAlignment); var text = new GUITextBlock(new RectTransform(Vector2.One, textContainer.RectTransform), "", textAlignment: textAlignment); new GUICustomComponent(new RectTransform(Vector2.Zero, textContainer.RectTransform), onUpdate: (f, component) => { string str = getter(); if (text.Text?.SanitizedValue != str) { text.Text = str; textShadow.Text = str; } }); } progressBarText(0.475f, Alignment.CenterRight, () => MathUtils.GetBytesReadable(getTransfer()?.Received ?? 0)); progressBarText(0.05f, Alignment.Center, () => "/"); progressBarText(0.475f, Alignment.CenterLeft, () => MathUtils.GetBytesReadable(getTransfer()?.FileSize ?? 0)); } return getTransfer()?.Progress ?? 0.0f; } if (!pendingDownloads.Contains(p)) { downloadProgress.GetAllChildren().ToArray().ForEach(c => downloadProgress.RemoveChild(c)); return 1.0f; } return 0.0f; }; } } public override void Deselect() { Reset(); base.Deselect(); } public override void Update(double deltaTime) { base.Update(deltaTime); if (GameMain.Client is null) { return; } if (!confirmDownload) { return; } if (currentDownload is null) { if (pendingDownloads.TryDequeue(out currentDownload)) { GameMain.Client.RequestFile(FileTransferType.Mod, currentDownload.Name, currentDownload.Hash.StringRepresentation); } else { var serverPackages = GameMain.Client.ClientPeer.ServerContentPackages; CorePackage corePackage = downloadedPackages.FirstOrDefault(p => p is CorePackage) as CorePackage ?? serverPackages.FirstOrDefault(p => p.CorePackage != null) ?.CorePackage ?? throw new Exception($"Failed to find core package to enable"); RegularPackage[] regularPackages = serverPackages.Where(p => p.CorePackage is null) .Select(p => p.RegularPackage ?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash)) ?? throw new Exception($"Could not find regular package \"{p.Name}\"")) .Cast() .ToArray(); foreach (var regularPackage in regularPackages) { DebugConsole.NewMessage($"Enabling \"{regularPackage.Name}\" ({regularPackage.Dir})", Color.Lime); } ContentPackageManager.EnabledPackages.BackUp(); ContentPackageManager.EnabledPackages.SetCore(corePackage); ContentPackageManager.EnabledPackages.SetRegular(regularPackages); GameMain.NetLobbyScreen.Select(); GameMain.LuaCs.Initialize(); } } } public void CurrentDownloadFinished(FileReceiver.FileTransferIn transfer) { if (currentDownload is null) { throw new Exception("Current download is null"); } string path = transfer.FilePath; if (!path.EndsWith(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase)) { return; } string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase); SaveUtil.DecompressToDirectory(path, dir, file => { }); ContentPackage newPackage = ContentPackage.TryLoad($"{dir}/{ContentPackage.FileListFileName}") ?? throw new Exception($"Failed to load downloaded mod \"{currentDownload.Name}\""); if (!currentDownload.Hash.Equals(newPackage.Hash)) { throw new Exception($"Hash mismatch for downloaded mod \"{currentDownload.Name}\" (expected {currentDownload.Hash}, got {newPackage.Hash})"); } downloadedPackages.Add(newPackage); currentDownload = null; } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { GameMain.MainMenuScreen.DrawBackground(graphics, spriteBatch); //wtf spriteBatch.Begin(SpriteSortMode.Deferred, null, GUI.SamplerState, null, GameMain.ScissorTestEnable); GUI.Draw(Cam, spriteBatch); spriteBatch.End(); } } }