Add new content installer framework.

This commit is contained in:
Paul Chote
2016-04-29 17:58:05 +01:00
parent 054b3a43a4
commit ff147f39b9
18 changed files with 1533 additions and 119 deletions

View File

@@ -0,0 +1,234 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Zip;
using OpenRA.Support;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class DownloadPackageLogic : ChromeLogic
{
static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
readonly ModContent.ModDownload download;
readonly Action onSuccess;
readonly Widget panel;
readonly ProgressBarWidget progressBar;
Func<string> getStatusText = () => "";
string downloadHost;
[ObjectCreator.UseCtor]
public DownloadPackageLogic(Widget widget, ModContent.ModDownload download, Action onSuccess)
{
this.download = download;
this.onSuccess = onSuccess;
Log.AddChannel("install", "install.log");
panel = widget.Get("PACKAGE_DOWNLOAD_PANEL");
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
var statusLabel = panel.Get<LabelWidget>("STATUS_LABEL");
var statusFont = Game.Renderer.Fonts[statusLabel.Font];
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, statusLabel.Bounds.Width, statusFont));
statusLabel.GetText = () => status.Update(getStatusText());
var text = "Downloading {0}".F(download.Title);
panel.Get<LabelWidget>("TITLE").Text = text;
ShowDownloadDialog();
}
void ShowDownloadDialog()
{
getStatusText = () => "Fetching list of mirrors...";
progressBar.Indeterminate = true;
var retryButton = panel.Get<ButtonWidget>("RETRY_BUTTON");
retryButton.IsVisible = () => false;
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Action deleteTempFile = () =>
{
Log.Write("install", "Deleting temporary file " + file);
File.Delete(file);
};
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
{
var dataReceived = 0.0f;
var dataTotal = 0.0f;
var mag = 0;
var dataSuffix = "";
if (i.TotalBytesToReceive < 0)
{
dataTotal = float.NaN;
dataReceived = i.BytesReceived;
dataSuffix = SizeSuffixes[0];
}
else
{
mag = (int)Math.Log(i.TotalBytesToReceive, 1024);
dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10));
dataReceived = i.BytesReceived / (float)(1L << (mag * 10));
dataSuffix = SizeSuffixes[mag];
}
progressBar.Indeterminate = false;
progressBar.Percentage = i.ProgressPercentage;
getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage,
dataReceived, dataTotal, dataSuffix,
downloadHost ?? "unknown host");
};
Action<string> onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s);
Action<string> onError = s => Game.RunAfterTick(() =>
{
Log.Write("install", "Download failed: " + s);
progressBar.Indeterminate = false;
progressBar.Percentage = 100;
getStatusText = () => "Error: " + s;
retryButton.IsVisible = () => true;
});
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) =>
{
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
// Automatically extract
getStatusText = () => "Extracting...";
progressBar.Indeterminate = true;
var extracted = new List<string>();
try
{
using (var stream = File.OpenRead(file))
using (var z = new ZipFile(stream))
{
foreach (var kv in download.Extract)
{
var entry = z.GetEntry(kv.Value);
if (!entry.IsFile)
continue;
onExtractProgress("Extracting " + entry.Name);
Log.Write("install", "Extracting " + entry.Name);
var targetPath = Platform.ResolvePath(kv.Key);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
extracted.Add(targetPath);
using (var zz = z.GetInputStream(entry))
using (var f = File.Create(targetPath))
zz.CopyTo(f);
}
z.Close();
}
Game.RunAfterTick(() => { Ui.CloseWindow(); onSuccess(); });
}
catch (Exception)
{
Log.Write("install", "Extraction failed");
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
onError("Invalid archive");
}
finally
{
deleteTempFile();
}
};
Action<string> downloadUrl = url =>
{
Log.Write("install", "Downloading " + url);
downloadHost = new Uri(url).Host;
var dl = new Download(url, file, onDownloadProgress, onDownloadComplete);
cancelButton.OnClick = () => { dl.Cancel(); deleteTempFile(); Ui.CloseWindow(); };
retryButton.OnClick = () => { dl.Cancel(); ShowDownloadDialog(); };
};
if (download.MirrorList != null)
{
Log.Write("install", "Fetching mirrors from " + download.MirrorList);
Action<DownloadDataCompletedEventArgs, bool> onFetchMirrorsComplete = (i, cancelled) =>
{
progressBar.Indeterminate = true;
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
if (cancelled)
{
onError("Download cancelled");
return;
}
try
{
var data = Encoding.UTF8.GetString(i.Result);
var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
downloadUrl(mirrorList.Random(new MersenneTwister()));
}
catch (Exception e)
{
Log.Write("install", "Mirror selection failed with error:");
Log.Write("install", e.ToString());
onError("Online mirror is not available. Please install from an original disc.");
}
};
var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete);
cancelButton.OnClick = () => { updateMirrors.Cancel(); Ui.CloseWindow(); };
retryButton.OnClick = () => { updateMirrors.Cancel(); ShowDownloadDialog(); };
}
else
downloadUrl(download.URL);
}
}
}

View File

@@ -0,0 +1,422 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
using OpenRA.FileFormats;
using OpenRA.Mods.Common.FileFormats;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class InstallFromDiscLogic : ChromeLogic
{
enum Mode { Progress, Message, List }
readonly ModContent content;
readonly Widget panel;
readonly LabelWidget titleLabel;
readonly ButtonWidget primaryButton;
readonly ButtonWidget secondaryButton;
// Progress panel
readonly Widget progressContainer;
readonly ProgressBarWidget progressBar;
readonly LabelWidget progressLabel;
// Message panel
readonly Widget messageContainer;
readonly LabelWidget messageLabel;
// List Panel
readonly Widget listContainer;
readonly ScrollPanelWidget listPanel;
readonly LabelWidget listTemplate;
readonly LabelWidget listLabel;
Mode visible = Mode.Progress;
[ObjectCreator.UseCtor]
public InstallFromDiscLogic(Widget widget, ModContent content, Action afterInstall)
{
this.content = content;
Log.AddChannel("install", "install.log");
// this.afterInstall = afterInstall;
panel = widget.Get("DISC_INSTALL_PANEL");
titleLabel = panel.Get<LabelWidget>("TITLE");
primaryButton = panel.Get<ButtonWidget>("PRIMARY_BUTTON");
secondaryButton = panel.Get<ButtonWidget>("SECONDARY_BUTTON");
// Progress view
progressContainer = panel.Get("PROGRESS");
progressContainer.IsVisible = () => visible == Mode.Progress;
progressBar = panel.Get<ProgressBarWidget>("PROGRESS_BAR");
progressLabel = panel.Get<LabelWidget>("PROGRESS_MESSAGE");
progressLabel.IsVisible = () => visible == Mode.Progress;
// Message view
messageContainer = panel.Get("MESSAGE");
messageContainer.IsVisible = () => visible == Mode.Message;
messageLabel = messageContainer.Get<LabelWidget>("MESSAGE_MESSAGE");
// List view
listContainer = panel.Get("LIST");
listContainer.IsVisible = () => visible == Mode.List;
listPanel = listContainer.Get<ScrollPanelWidget>("LIST_PANEL");
listTemplate = listPanel.Get<LabelWidget>("LIST_TEMPLATE");
listPanel.RemoveChildren();
listLabel = listContainer.Get<LabelWidget>("LIST_MESSAGE");
DetectContentDisks();
}
void DetectContentDisks()
{
var message = "Detecting drives";
ShowProgressbar("Checking Discs", () => message);
ShowBackRetry(DetectContentDisks);
new Task(() =>
{
var volumes = DriveInfo.GetDrives()
.Where(v => v.DriveType == DriveType.CDRom && v.IsReady)
.Select(v => v.RootDirectory.FullName);
foreach (var kv in content.Discs)
{
message = "Searching for " + kv.Value.Title;
foreach (var volume in volumes)
{
if (PathIsDiscMount(volume, kv.Value))
{
var packages = content.Packages.Values
.Where(p => p.Discs.Contains(kv.Key) && !p.IsInstalled())
.Select(p => p.Title);
// Ignore disc if content is already installed
if (packages.Any())
{
Game.RunAfterTick(() =>
{
ShowList(kv.Value.Title, "The following content packages will be installed:", packages);
ShowContinueCancel(() => InstallFromDisc(volume, kv.Value));
});
return;
}
}
}
}
var discTitles = content.Packages.Values
.Where(p => !p.IsInstalled())
.SelectMany(p => p.Discs)
.Select(d => content.Discs[d].Title)
.Distinct();
Game.RunAfterTick(() =>
{
ShowList("Disc Content Not Found", "Please insert or mount one of the following discs and try again", discTitles);
ShowBackRetry(DetectContentDisks);
});
}).Start();
}
void InstallFromDisc(string path, ModContent.ModDisc disc)
{
var message = "";
ShowProgressbar("Installing Content", () => message);
ShowDisabledCancel();
new Task(() =>
{
var extracted = new List<string>();
try
{
foreach (var i in disc.Install)
{
switch (i.Key)
{
case "copy":
{
var sourceDir = Path.Combine(path, i.Value.Value);
foreach (var node in i.Value.Nodes)
{
var sourcePath = Path.Combine(sourceDir, node.Value.Value);
var targetPath = Platform.ResolvePath(node.Key);
if (File.Exists(targetPath))
{
Log.Write("install", "Ignoring installed file " + targetPath);
continue;
}
Log.Write("install", "Copying {0} -> {1}".F(sourcePath, targetPath));
message = "Copying " + Path.GetFileName(sourcePath);
extracted.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
File.Copy(sourcePath, targetPath);
}
break;
}
case "extract-raw":
{
ExtractFromPackage(ExtractionType.Raw, path, i.Value, extracted, ref message);
break;
}
case "extract-blast":
{
ExtractFromPackage(ExtractionType.Blast, path, i.Value, extracted, ref message);
break;
}
case "extract-mscab":
{
ExtractFromMSCab(path, i.Value, extracted, m => message = m);
break;
}
default:
Game.Debug("debug", "Unknown installation command {0} - ignoring", i.Key);
break;
}
}
Game.RunAfterTick(Ui.CloseWindow);
}
catch (Exception e)
{
Log.Write("install", e.ToString());
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
Game.RunAfterTick(() =>
{
ShowMessage("Installation Failed", "Refer to install.log in the logs directory for details.");
ShowBackRetry(() => InstallFromDisc(path, disc));
});
}
}).Start();
}
enum ExtractionType { Raw, Blast }
static void ExtractFromPackage(ExtractionType type, string path, MiniYaml actionYaml, List<string> extractedFiles, ref string progressMessage)
{
var sourcePath = Path.Combine(path, actionYaml.Value);
using (var source = File.OpenRead(sourcePath))
{
foreach (var node in actionYaml.Nodes)
{
var targetPath = Platform.ResolvePath(node.Key);
if (File.Exists(targetPath))
{
Log.Write("install", "Skipping installed file " + targetPath);
continue;
}
var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset");
if (offsetNode == null)
{
Log.Write("install", "Skipping entry with missing Offset definition " + targetPath);
continue;
}
var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length");
if (lengthNode == null)
{
Log.Write("install", "Skipping entry with missing Length definition " + targetPath);
continue;
}
var length = FieldLoader.GetValue<int>("Length", lengthNode.Value.Value);
source.Position = FieldLoader.GetValue<int>("Offset", offsetNode.Value.Value);
extractedFiles.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
using (var target = File.OpenWrite(targetPath))
{
// This is a bit dumb memory-wise, but we load the whole thing when running the game anyway
Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath));
progressMessage = "Extracting " + Path.GetFileName(Path.GetFileName(targetPath));
var data = source.ReadBytes(length);
if (type == ExtractionType.Blast)
data = Blast.Decompress(data);
target.Write(data);
}
}
}
}
static void ExtractFromMSCab(string path, MiniYaml actionYaml, List<string> extractedFiles, Action<string> updateMessage)
{
var sourcePath = Path.Combine(path, actionYaml.Value);
using (var source = File.OpenRead(sourcePath))
{
var reader = new MSCabCompression(source);
foreach (var node in actionYaml.Nodes)
{
var targetPath = Platform.ResolvePath(node.Key);
if (File.Exists(targetPath))
{
Log.Write("install", "Skipping installed file " + targetPath);
continue;
}
extractedFiles.Add(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
using (var target = File.OpenWrite(targetPath))
{
// This is a bit dumb memory-wise, but we load the whole thing when running the game anyway
Log.Write("install", "Extracting {0} -> {1}".F(sourcePath, targetPath));
var displayFilename = Path.GetFileName(Path.GetFileName(targetPath));
Action<int> onProgress = percent => updateMessage("Extracting {0} ({1}%)".F(displayFilename, percent));
target.Write(reader.ExtractFile(node.Value.Value, onProgress));
}
}
}
}
bool PathIsDiscMount(string path, ModContent.ModDisc disc)
{
try
{
foreach (var kv in disc.IDFiles)
{
var filePath = Path.Combine(path, kv.Key);
if (!File.Exists(filePath))
return false;
using (var fileStream = File.OpenRead(filePath))
using (var csp = SHA1.Create())
{
var hash = new string(csp.ComputeHash(fileStream).SelectMany(a => a.ToString("x2")).ToArray());
if (hash != kv.Value)
return false;
}
}
}
catch (Exception)
{
return false;
}
return true;
}
void ShowMessage(string title, string message)
{
visible = Mode.Message;
titleLabel.Text = title;
messageLabel.Text = message;
primaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += messageContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (messageContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = messageContainer.Bounds.Height;
}
void ShowProgressbar(string title, Func<string> getMessage)
{
visible = Mode.Progress;
titleLabel.Text = title;
progressBar.IsIndeterminate = () => true;
var font = Game.Renderer.Fonts[progressLabel.Font];
var status = new CachedTransform<string, string>(s => WidgetUtils.TruncateText(s, progressLabel.Bounds.Width, font));
progressLabel.GetText = () => status.Update(getMessage());
primaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += progressContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (progressContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = progressContainer.Bounds.Height;
}
void ShowList(string title, string message, IEnumerable<string> items)
{
visible = Mode.List;
titleLabel.Text = title;
listLabel.Text = message;
listPanel.RemoveChildren();
foreach (var i in items)
{
var item = i;
var labelWidget = (LabelWidget)listTemplate.Clone();
labelWidget.GetText = () => item;
listPanel.AddChild(labelWidget);
}
primaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
secondaryButton.Bounds.Y += listContainer.Bounds.Height - panel.Bounds.Height;
panel.Bounds.Y -= (listContainer.Bounds.Height - panel.Bounds.Height) / 2;
panel.Bounds.Height = listContainer.Bounds.Height;
}
void ShowContinueCancel(Action continueAction)
{
primaryButton.OnClick = continueAction;
primaryButton.Text = "Continue";
primaryButton.Visible = true;
secondaryButton.OnClick = Ui.CloseWindow;
secondaryButton.Text = "Cancel";
secondaryButton.Visible = true;
secondaryButton.Disabled = false;
Game.RunAfterTick(Ui.ResetTooltips);
}
void ShowBackRetry(Action retryAction)
{
primaryButton.OnClick = retryAction;
primaryButton.Text = "Retry";
primaryButton.Visible = true;
secondaryButton.OnClick = Ui.CloseWindow;
secondaryButton.Text = "Back";
secondaryButton.Visible = true;
secondaryButton.Disabled = false;
Game.RunAfterTick(Ui.ResetTooltips);
}
void ShowDisabledCancel()
{
primaryButton.Visible = false;
secondaryButton.Disabled = true;
Game.RunAfterTick(Ui.ResetTooltips);
}
}
}

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic

View File

@@ -0,0 +1,53 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.IO;
using System.Linq;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentDiscTooltipLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public ModContentDiscTooltipLogic(Widget widget, Func<string> getText)
{
var discs = widget.Get<ContainerWidget>("DISCS");
var template = discs.Get<LabelWidget>("DISC_TEMPLATE");
discs.RemoveChildren();
var desc = widget.Get<LabelWidget>("DESCRIPTION");
var font = Game.Renderer.Fonts[template.Font];
var discTitles = getText().Split('\n');
var maxWidth = 0;
var sideMargin = desc.Bounds.X;
var bottomMargin = discs.Bounds.Height;
foreach (var disc in discTitles)
{
var label = (LabelWidget)template.Clone();
var title = disc;
label.GetText = () => title;
label.Bounds.Y = discs.Bounds.Height;
label.Bounds.Width = font.Measure(disc).X;
maxWidth = Math.Max(maxWidth, label.Bounds.Width + label.Bounds.X);
discs.AddChild(label);
discs.Bounds.Height += label.Bounds.Height;
}
widget.Bounds.Width = 2 * sideMargin + maxWidth;
widget.Bounds.Height = discs.Bounds.Y + bottomMargin + discs.Bounds.Height;
}
}
}

View File

@@ -0,0 +1,127 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.IO;
using System.Linq;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentLogic : ChromeLogic
{
readonly ModContent content;
readonly ScrollPanelWidget scrollPanel;
readonly Widget template;
bool discAvailable;
[ObjectCreator.UseCtor]
public ModContentLogic(Widget widget, string modId, Action onCancel)
{
var panel = widget.Get("CONTENT_PANEL");
content = ModMetadata.AllMods[modId].ModContent;
scrollPanel = panel.Get<ScrollPanelWidget>("PACKAGES");
template = scrollPanel.Get<ContainerWidget>("PACKAGE_TEMPLATE");
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines = !string.IsNullOrEmpty(content.HeaderMessage) ? content.HeaderMessage.Replace("\\n", "\n").Split('\n') : new string[0];
var headerHeight = 0;
foreach (var l in headerLines)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
headerHeight += headerTemplate.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
panel.Bounds.Y -= headerHeight / 2;
scrollPanel.Bounds.Y += headerHeight;
var discButton = panel.Get<ButtonWidget>("CHECK_DISC_BUTTON");
discButton.Bounds.Y += headerHeight;
discButton.IsVisible = () => discAvailable;
discButton.OnClick = () => Ui.OpenWindow("DISC_INSTALL_PANEL", new WidgetArgs
{
{ "afterInstall", () => { } },
{ "content", content }
});
var backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.Bounds.Y += headerHeight;
backButton.OnClick = () => { Ui.CloseWindow(); onCancel(); };
PopulateContentList();
Game.RunAfterTick(Ui.ResetTooltips);
}
public override void BecameVisible()
{
PopulateContentList();
}
void PopulateContentList()
{
scrollPanel.RemoveChildren();
foreach (var p in content.Packages)
{
var container = template.Clone();
var titleWidget = container.Get<LabelWidget>("TITLE");
var title = p.Value.Title;
titleWidget.GetText = () => title;
var requiredWidget = container.Get<LabelWidget>("REQUIRED");
requiredWidget.IsVisible = () => p.Value.Required;
var discWidget = container.Get<ImageWidget>("DISC");
var discs = p.Value.Discs.Select(s => content.Discs[s].Title).Distinct();
var discList = discs.JoinWith("\n");
var discAvailable = discs.Any();
discWidget.GetTooltipText = () => discList;
discWidget.IsVisible = () => discAvailable;
var installed = p.Value.IsInstalled();
var downloadButton = container.Get<ButtonWidget>("DOWNLOAD");
var downloadEnabled = !installed && p.Value.Download != null;
downloadButton.IsVisible = () => downloadEnabled;
if (downloadEnabled)
{
var download = content.Downloads[p.Value.Download];
var widgetArgs = new WidgetArgs
{
{ "download", download },
{ "onSuccess", () => { } }
};
downloadButton.OnClick = () => Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", widgetArgs);
}
var installedWidget = container.Get<LabelWidget>("INSTALLED");
installedWidget.IsVisible = () => installed;
var requiresDiscWidget = container.Get<LabelWidget>("REQUIRES_DISC");
requiresDiscWidget.IsVisible = () => !installed && !downloadEnabled;
if (!discAvailable)
requiresDiscWidget.GetText = () => "Manual Install";
scrollPanel.AddChild(container);
}
discAvailable = content.Packages.Values.Any(p => p.Discs.Any() && !p.IsInstalled());
}
}
}

View File

@@ -0,0 +1,72 @@
#region Copyright & License Information
/*
* Copyright 2007-2016 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ModContentPromptLogic : ChromeLogic
{
[ObjectCreator.UseCtor]
public ModContentPromptLogic(Widget widget, string modId, Action continueLoading)
{
var panel = widget.Get("CONTENT_PROMPT_PANEL");
var mod = ModMetadata.AllMods[modId];
var content = ModMetadata.AllMods[modId].ModContent;
var headerTemplate = panel.Get<LabelWidget>("HEADER_TEMPLATE");
var headerLines = !string.IsNullOrEmpty(content.InstallPromptMessage) ? content.InstallPromptMessage.Replace("\\n", "\n").Split('\n') : new string[0];
var headerHeight = 0;
foreach (var l in headerLines)
{
var line = (LabelWidget)headerTemplate.Clone();
line.GetText = () => l;
line.Bounds.Y += headerHeight;
panel.AddChild(line);
headerHeight += headerTemplate.Bounds.Height;
}
panel.Bounds.Height += headerHeight;
panel.Bounds.Y -= headerHeight / 2;
var advancedButton = panel.Get<ButtonWidget>("ADVANCED_BUTTON");
advancedButton.Bounds.Y += headerHeight;
advancedButton.OnClick = () =>
{
Ui.OpenWindow("CONTENT_PANEL", new WidgetArgs
{
{ "modId", modId },
{ "onCancel", Ui.CloseWindow }
});
};
var quickButton = panel.Get<ButtonWidget>("QUICK_BUTTON");
quickButton.IsVisible = () => !string.IsNullOrEmpty(content.QuickDownload);
quickButton.Bounds.Y += headerHeight;
quickButton.OnClick = () =>
{
Ui.OpenWindow("PACKAGE_DOWNLOAD_PANEL", new WidgetArgs
{
{ "download", content.Downloads[content.QuickDownload] },
{ "onSuccess", continueLoading }
});
};
var backButton = panel.Get<ButtonWidget>("BACK_BUTTON");
backButton.Bounds.Y += headerHeight;
backButton.OnClick = Ui.CloseWindow;
Game.RunAfterTick(Ui.ResetTooltips);
}
}
}

View File

@@ -27,8 +27,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly ModMetadata[] allMods;
readonly Dictionary<string, Sprite> previews = new Dictionary<string, Sprite>();
readonly Dictionary<string, Sprite> logos = new Dictionary<string, Sprite>();
readonly Cache<ModMetadata, bool> modInstallStatus;
readonly Cache<string, bool> modPrerequisitesFulfilled;
readonly Widget modChooserPanel;
readonly ButtonWidget loadButton;
readonly SheetBuilder sheetBuilder;
@@ -40,14 +38,24 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[ObjectCreator.UseCtor]
public ModBrowserLogic(Widget widget, ModData modData)
{
modInstallStatus = new Cache<ModMetadata, bool>(IsModInstalled);
modPrerequisitesFulfilled = new Cache<string, bool>(Game.IsModInstalled);
modChooserPanel = widget;
loadButton = modChooserPanel.Get<ButtonWidget>("LOAD_BUTTON");
loadButton.OnClick = () => LoadMod(selectedMod);
loadButton.IsDisabled = () => selectedMod.Id == modData.Manifest.Mod.Id;
var contentButton = modChooserPanel.Get<ButtonWidget>("CONFIGURE_BUTTON");
contentButton.IsDisabled = () => selectedMod.ModContent == null;
contentButton.OnClick = () =>
{
var widgetArgs = new WidgetArgs
{
{ "modId", selectedMod.Id },
{ "onCancel", () => { } }
};
Ui.OpenWindow("CONTENT_PANEL", widgetArgs);
};
modChooserPanel.Get<ButtonWidget>("QUIT_BUTTON").OnClick = Game.Exit;
modList = modChooserPanel.Get("MOD_LIST");
@@ -160,14 +168,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var selectedIndex = Array.IndexOf(allMods, mod);
if (selectedIndex - modOffset > 4)
modOffset = selectedIndex - 4;
loadButton.Text = !modPrerequisitesFulfilled[mod.Id] ? "Install mod" :
modInstallStatus[mod] ? "Load Mod" : "Install Assets";
}
void LoadMod(ModMetadata mod)
{
if (!modPrerequisitesFulfilled[mod.Id])
if (!Game.IsModInstalled(mod.Id))
{
var widgetArgs = new WidgetArgs
{
@@ -178,17 +183,31 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return;
}
if (!modInstallStatus[mod])
if (!IsModInstalled(mod))
{
var widgetArgs = new WidgetArgs
if (mod.ModContent != null)
{
{ "continueLoading", () =>
Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) },
{ "mirrorListUrl", mod.Content.PackageMirrorList },
{ "modId", mod.Id }
};
var widgetArgs = new WidgetArgs
{
{ "continueLoading", () =>
Game.RunAfterTick(() => Game.InitializeMod(mod.Id, new Arguments())) },
{ "modId", mod.Id }
};
Ui.OpenWindow("INSTALL_PANEL", widgetArgs);
Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs);
}
else
{
var widgetArgs = new WidgetArgs
{
{ "continueLoading", () =>
Game.RunAfterTick(() => Game.InitializeMod(Game.Settings.Game.Mod, new Arguments())) },
{ "mirrorListUrl", mod.Content.PackageMirrorList },
{ "modId", mod.Id }
};
Ui.OpenWindow("INSTALL_PANEL", widgetArgs);
}
return;
}