3 using System.Collections.Generic;
4 using System.Diagnostics.CodeAnalysis;
10 using Microsoft.Xna.Framework;
11 using Microsoft.Xna.Framework.Graphics;
12 using Color = Microsoft.Xna.Framework.Color;
19 private readonly Queue<ServerContentPackage> pendingDownloads =
20 new Queue<ServerContentPackage>();
23 private readonly List<ContentPackage> downloadedPackages =
new List<ContentPackage>();
26 private bool confirmDownload;
30 pendingDownloads.Clear();
31 downloadedPackages.Clear();
32 currentDownload =
null;
33 confirmDownload =
false;
36 private void DeletePrevDownloads()
38 if (Directory.Exists(ModReceiver.DownloadFolder))
40 Directory.Delete(ModReceiver.DownloadFolder, recursive:
true);
45 private static void LogAndThrowException(
string errorMsg,
string analyticsId)
47 GameAnalyticsManager.AddErrorEventOnce(analyticsId, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
48 throw new InvalidOperationException(errorMsg);
54 DeletePrevDownloads();
64 void mainLayoutSpacing()
68 "", font: GUIStyle.LargeFont,
69 textAlignment: Alignment.CenterLeft)
77 TextManager.Get(
"Disconnect"))
79 OnClicked = (guiButton, o) =>
89 LogAndThrowException(
"Error in ModDownloadScreen: the list of mods the server has enabled was empty. "
90 +$
"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
91 analyticsId:
"ModDownloadScreen.Select:NoContentPackages");
95 .Where(sp => sp.ContentPackage is
null).ToArray();
96 if (!missingPackages.Any(p => p.IsMandatory))
101 .Select(p => p.CorePackage)
103 if (corePackage is
null)
105 LogAndThrowException($
"Error in ModDownloadScreen: no core packages in the list of mods the server has enabled. " +
106 $
"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
107 analyticsId:
"ModDownloadScreen.Select:NoCorePackage");
110 ContentPackageManager.EnabledPackages.BackUp();
111 ContentPackageManager.EnabledPackages.SetCore(corePackage);
112 List<RegularPackage> regularPackages =
114 .Select(p => p.RegularPackage)
117 regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent && !regularPackages.Contains(p)));
118 ContentPackageManager.EnabledPackages.SetRegular(regularPackages);
125 if (missingPackages.FirstOrDefault(p => p.IsVanilla) is { } mismatchedVanilla)
127 LogAndThrowException(
"Error in ModDownloadScreen: mismatched Vanilla package: "
128 +$
"local hash is {ContentPackageManager.VanillaCorePackage?.Hash.StringRepresentation ?? "[NULL]
"}, "
129 +$
"remote hash is {mismatchedVanilla.Hash.StringRepresentation}. "
130 +$
"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
131 analyticsId:
"ModDownloadScreen.Select:MismatchedVanilla");
135 TextManager.Get(
"ModDownloadTitle"),
138 relativeSize: (0.5f, 0.75f));
143 void innerLayoutSpacing(
float height)
149 wrap:
true, textAlignment: alignment, font: font);
151 (deltaTime, component) =>
153 if (tb.RectTransform.NonScaledSize.X != innerLayout.Rect.Width)
155 tb.RectTransform.NonScaledSize = (innerLayout.Rect.Width, 0);
156 tb.RectTransform.NonScaledSize = (innerLayout.Rect.Width,
157 (int)tb.Font.MeasureString(tb.WrappedText).Y);
163 var header = textBlock(TextManager.Get(
"ModDownloadHeader"), GUIStyle.Font);
164 innerLayoutSpacing(0.05f);
168 innerLayoutSpacing(0.05f);
169 var footer = textBlock(TextManager.Get(allowDownloads ?
"ModDownloadFooter" :
"ModDownloadFooterFail"), GUIStyle.Font, Alignment.Center);
171 innerLayoutSpacing(0.05f);
174 void buttonContainerSpacing(
float width)
180 OnClicked = (_, __) =>
190 buttonContainerSpacing(0.1f);
191 button(TextManager.Get(
"Yes"), () => confirmDownload =
true);
192 buttonContainerSpacing(0.2f);
193 button(TextManager.Get(
"No"), () =>
195 GameMain.Client?.Quit();
196 GameMain.MainMenuScreen.Select();
198 buttonContainerSpacing(0.1f);
202 buttonContainerSpacing(0.15f);
203 button(TextManager.Get(
"Cancel"), () =>
205 GameMain.Client?.Quit();
206 GameMain.MainMenuScreen.Select();
208 buttonContainerSpacing(0.15f);
211 var missingIds = missingPackages
212 .Where(p => p.IsMandatory)
213 .Select(mp => ContentPackageId.Parse(mp.UgcId))
215 .Where(
id => ContentPackageManager.WorkshopPackages.All(wp => !wp.UgcId.Equals(
id)))
217 if (missingIds.Any() && SteamManager.IsInitialized)
219 buttonContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.1f), innerLayout.RectTransform), isHorizontal:
true);
220 buttonContainerSpacing(0.15f);
221 button(TextManager.Get(
"SubscribeToAllOnWorkshop"), () =>
223 if (GameMain.Client != null)
225 BulkDownloader.SubscribeToServerMods(missingIds.OfType<SteamWorkshopId>().Select(id => id.Value),
227 serverName: GameMain.Client.ServerName,
228 endpoint: GameMain.Client.ClientPeer.ServerEndpoint));
229 GameMain.Client.Quit();
231 GameMain.MainMenuScreen.Select();
233 buttonContainerSpacing(0.15f);
236 foreach (var p
in missingPackages.Where(p => p.IsMandatory))
238 pendingDownloads.Enqueue(p);
241 new GUITextBlock(
new RectTransform((1.0f, 0.1f), msgBoxModList.Content.RectTransform), p.Name)
247 var downloadFrame =
new GUIFrame(
new RectTransform((1.0f, 0.06f), downloadList.Content.RectTransform),
248 style:
"ListBoxElement")
253 new GUITextBlock(
new RectTransform((0.5f, 1.0f), downloadFrame.RectTransform), p.Name)
257 var downloadProgress =
new GUIProgressBar(
258 new RectTransform((0.5f, 0.75f), downloadFrame.RectTransform,
Anchor.CenterRight),
259 0.0f, color: GUIStyle.Green);
260 downloadProgress.ProgressGetter = () =>
262 if (currentDownload == p)
266 if (downloadProgress.GetAnyChild<GUITextBlock>() is
null)
268 GUILayoutGroup progressBarLayout
269 =
new GUILayoutGroup(
new RectTransform(Vector2.One, downloadProgress.RectTransform), isHorizontal:
true);
271 void progressBarText(
float width, Alignment textAlignment, Func<string> getter)
273 var textContainer =
new GUIFrame(
new RectTransform((width, 1.0f), progressBarLayout.RectTransform),
275 var textShadow =
new GUITextBlock(
new RectTransform(Vector2.One, textContainer.RectTransform) { AbsoluteOffset = new Point(GUI.IntScale(3)) },
"",
276 textColor: Color.Black, textAlignment: textAlignment);
277 var text =
new GUITextBlock(
new RectTransform(Vector2.One, textContainer.RectTransform),
"",
278 textAlignment: textAlignment);
279 new GUICustomComponent(
new RectTransform(Vector2.Zero, textContainer.RectTransform), onUpdate:
282 string str = getter();
283 if (text.Text?.SanitizedValue != str)
286 textShadow.Text = str;
290 progressBarText(0.475f, Alignment.CenterRight, () => MathUtils.GetBytesReadable(getTransfer()?.Received ?? 0));
291 progressBarText(0.05f, Alignment.Center, () =>
"/");
292 progressBarText(0.475f, Alignment.CenterLeft, () => MathUtils.GetBytesReadable(getTransfer()?.FileSize ?? 0));
295 return getTransfer()?.Progress ?? 0.0f;
298 if (!pendingDownloads.Contains(p))
300 downloadProgress.GetAllChildren<GUITextBlock>().ToArray().ForEach(c => downloadProgress.RemoveChild(c));
309 public override void Update(
double deltaTime)
311 base.Update(deltaTime);
313 if (!confirmDownload) {
return; }
314 if (currentDownload is
null)
316 if (pendingDownloads.TryDequeue(out currentDownload))
325 ?? serverPackages.FirstOrDefault(p => p.CorePackage !=
null)?.
CorePackage
326 ??
throw new Exception($
"Failed to find core package to enable");
328 List<RegularPackage> regularPackages =
new List<RegularPackage>();
329 foreach (var p
in serverPackages)
331 if (p.CorePackage !=
null)
337 if (corePackage.Hash.Equals(p.Hash))
344 if (matchingPackage is
null)
353 throw new Exception($
"Could not find regular package \"{p.Name}\"");
356 regularPackages.Add(matchingPackage);
358 foreach (var regularPackage
in regularPackages)
360 DebugConsole.NewMessage($
"Enabling \"{regularPackage.Name}\" ({regularPackage.Dir})", Color.Lime);
364 regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent && !regularPackages.Contains(p)));
366 ContentPackageManager.EnabledPackages.BackUp();
367 ContentPackageManager.EnabledPackages.SetCore(corePackage);
368 ContentPackageManager.EnabledPackages.SetRegular(regularPackages);
373 if (File.Exists(serverSub.FilePath)) {
continue; }
375 if (matchingSub !=
null)
377 serverSub.FilePath = matchingSub.FilePath;
389 if (currentDownload is
null) {
throw new Exception(
"Current download is null"); }
391 string path = transfer.FilePath;
392 if (!path.EndsWith(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase))
396 string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase);
398 SaveUtil.DecompressToDirectory(path, dir);
401 if (!result.TryUnwrapSuccess(out var newPackage))
403 throw new Exception($
"Failed to load downloaded mod \"{currentDownload.Name}\"",
404 result.TryUnwrapFailure(out var exception) ? exception :
null);
406 if (!currentDownload.
Hash.
Equals(newPackage.Hash))
408 throw new Exception($
"Hash mismatch for downloaded mod \"{currentDownload.Name}\" (expected {currentDownload.Hash}, got {newPackage.Hash})");
410 downloadedPackages.Add(newPackage);
412 currentDownload =
null;
416 public override void Draw(
double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
420 GUI.Draw(Cam, spriteBatch);
const string FileListFileName
static Result< ContentPackage, Exception > TryLoad(string path)
CorePackage(XDocument doc, string path)
virtual void ClearChildren()
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
bool Stretch
Note that stretching cannot be undone, because the previous child sizes are not stored.
static NetLobbyScreen NetLobbyScreen
static RasterizerState ScissorTestEnable
static MainMenuScreen MainMenuScreen
void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
override bool Equals(object? obj)
IEnumerable< ContentPackage > DownloadedPackages
void CurrentDownloadFinished(FileReceiver.FileTransferIn transfer)
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
override void Update(double deltaTime)
void RequestFile(FileTransferType fileType, string file, string fileHash)
readonly List< SubmarineInfo > ServerSubmarines
RegularPackage(XDocument doc, string path)
static IEnumerable< SubmarineInfo > SavedSubmarines
void UpdateSubList(GUIComponent subList, IEnumerable< SubmarineInfo > submarines)