Reimplement ingame map downloading.

This commit is contained in:
Paul Chote
2014-03-13 12:43:42 +13:00
parent 6b199d3376
commit c3ba27ef6c
7 changed files with 563 additions and 67 deletions

View File

@@ -136,7 +136,7 @@ namespace OpenRA.GameRules
public bool TeamHealthColors = false;
public bool AllowDownloading = true;
public string[] MapRepositories = { "http://resource.openra.net/map/", "http://resource.ihptru.net:8080/map/" };
public string MapRepository = "http://resource.openra.net/map/";
}
public class KeySettings

View File

@@ -14,10 +14,11 @@ using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Widgets;
namespace OpenRA
{
@@ -57,6 +58,43 @@ namespace OpenRA
}
}
public void QueryRemoteMapDetails(IEnumerable<string> uids)
{
var maps = uids.Distinct()
.Select(uid => previews[uid])
.Where(p => p.Status == MapStatus.Unavailable)
.ToDictionary(p => p.Uid, p => p);
if (!maps.Any())
return;
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Searching, null);
var url = Game.Settings.Game.MapRepository + "hash/" + string.Join(",", maps.Keys.ToArray()) + "/yaml";
Action<DownloadDataCompletedEventArgs, bool> onInfoComplete = (i, cancelled) =>
{
if (cancelled || i.Error != null)
{
Log.Write("debug", "Remote map query failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
return;
}
var data = Encoding.UTF8.GetString(i.Result);
var yaml = MiniYaml.FromString(data);
foreach (var kv in yaml)
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value);
};
new Download(url, _ => { }, onInfoComplete);
}
public static IEnumerable<string> FindMapsIn(string dir)
{
string[] noMaps = { };

View File

@@ -11,9 +11,11 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using OpenRA.FileFormats;
using OpenRA.Graphics;
@@ -21,10 +23,26 @@ using OpenRA.Widgets;
namespace OpenRA
{
public enum MapStatus { Available, Unavailable }
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError }
// Fields names must match the with the remote API
public class RemoteMapData
{
public readonly string title;
public readonly string author;
public readonly string map_type;
public readonly int players;
public readonly Rectangle bounds;
public readonly int[] spawnpoints = {};
public readonly string minimap;
public readonly bool downloading;
public readonly bool requires_upgrade;
}
public class MapPreview
{
static readonly List<CPos> NoSpawns = new List<CPos>();
MapCache cache;
public readonly string Uid;
public string Title { get; private set; }
@@ -37,6 +55,10 @@ namespace OpenRA
public Map Map { get; private set; }
public MapStatus Status { get; private set; }
Download download;
public long DownloadBytes { get; private set; }
public int DownloadPercentage { get; private set; }
Sprite minimap;
bool generatingMinimap;
public Sprite Minimap
@@ -62,7 +84,6 @@ namespace OpenRA
}
}
MapCache cache;
public MapPreview(string uid, MapCache cache)
{
this.cache = cache;
@@ -89,5 +110,116 @@ namespace OpenRA
CustomPreview = m.CustomPreview;
Status = MapStatus.Available;
}
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml)
{
// Update on the main thread to ensure consistency
Game.RunAfterTick(() =>
{
if (status == MapStatus.DownloadAvailable)
{
try
{
var r = FieldLoader.Load<RemoteMapData>(yaml);
// Map is not useable by the current version
if (!r.downloading || r.requires_upgrade)
{
Status = MapStatus.Unavailable;
return;
}
Title = r.title;
Type = r.map_type;
Author = r.author;
PlayerCount = r.players;
Bounds = r.bounds;
var spawns = new List<CPos>();
for (var j = 0; j < r.spawnpoints.Length; j += 2)
spawns.Add(new CPos(r.spawnpoints[j], r.spawnpoints[j+1]));
SpawnPoints = spawns;
CustomPreview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap)));
}
catch (Exception) {}
if (CustomPreview != null)
cache.CacheMinimap(this);
}
Status = status;
});
}
public void Install()
{
if (Status != MapStatus.DownloadAvailable || !Game.Settings.Game.AllowDownloading)
return;
Status = MapStatus.Downloading;
var baseMapPath = new[] { Platform.SupportDir, "maps", Game.modData.Manifest.Mod.Id }.Aggregate(Path.Combine);
// Create the map directory if it doesn't exist
if (!Directory.Exists(baseMapPath))
Directory.CreateDirectory(baseMapPath);
new Thread(() =>
{
// Request the filename from the server
// Run in a worker thread to avoid network delays
var mapUrl = Game.Settings.Game.MapRepository + Uid;
try
{
var request = WebRequest.Create(mapUrl);
request.Method = "HEAD";
var res = request.GetResponse();
// Map not found
if (res.Headers["Content-Disposition"] == null)
{
Status = MapStatus.DownloadError;
return;
}
var mapPath = Path.Combine(baseMapPath, res.Headers["Content-Disposition"].Replace("attachment; filename = ", ""));
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; };
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) =>
{
download = null;
if (cancelled || i.Error != null)
{
Log.Write("debug", "Remote map download failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
Log.Write("debug", "URL was: {0}", mapUrl);
Status = MapStatus.DownloadError;
return;
}
Log.Write("debug", "Downloaded map to '{0}'", mapPath);
Game.RunAfterTick(() => UpdateFromMap(new Map(mapPath)));
};
download = new Download(mapUrl, mapPath, onDownloadProgress, onDownloadComplete);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Status = MapStatus.DownloadError;
}
}).Start();
}
public void CancelInstall()
{
if (download == null)
return;
download.Cancel();
download = null;
}
}
}

View File

@@ -554,6 +554,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic
if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash))
orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash)));
}
else if (Game.Settings.Game.AllowDownloading)
Game.modData.MapCache.QueryRemoteMapDetails(new [] { uid });
}
void UpdatePlayerList()

View File

@@ -23,22 +23,126 @@ namespace OpenRA.Mods.RA.Widgets.Logic
[ObjectCreator.UseCtor]
internal LobbyMapPreviewLogic(Widget widget, OrderManager orderManager, LobbyLogic lobby)
{
var mapPreview = widget.Get<MapPreviewWidget>("MAP_PREVIEW");
mapPreview.Preview = () => lobby.Map;
mapPreview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, mapPreview, lobby.Map, mi);
mapPreview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, lobby.Map);
var available = widget.GetOrNull("MAP_AVAILABLE");
if (available != null)
{
available.IsVisible = () => lobby.Map.Status == MapStatus.Available;
var mapTitle = widget.GetOrNull<LabelWidget>("MAP_TITLE");
if (mapTitle != null)
mapTitle.GetText = () => lobby.Map.Title;
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, lobby.Map);
var mapType = widget.GetOrNull<LabelWidget>("MAP_TYPE");
if (mapType != null)
mapType.GetText = () => lobby.Map.Type;
var title = available.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var mapAuthor = widget.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (mapAuthor != null)
mapAuthor.GetText = () => "Created by {0}".F(lobby.Map.Author);
var type = available.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var author = available.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (author != null)
author.GetText = () => "Created by {0}".F(lobby.Map.Author);
}
var download = widget.GetOrNull("MAP_DOWNLOADABLE");
if (download != null)
{
download.IsVisible = () => lobby.Map.Status == MapStatus.DownloadAvailable;
var preview = download.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, lobby.Map);
var title = download.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = download.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var author = download.GetOrNull<LabelWidget>("MAP_AUTHOR");
if (author != null)
author.GetText = () => "Created by {0}".F(lobby.Map.Author);
var install = download.GetOrNull<ButtonWidget>("MAP_INSTALL");
if (install != null)
install.OnClick = () => lobby.Map.Install();
}
var progress = widget.GetOrNull("MAP_PROGRESS");
if (progress != null)
{
progress.IsVisible = () => lobby.Map.Status != MapStatus.Available && lobby.Map.Status != MapStatus.DownloadAvailable;
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager, lobby.Map);
var title = progress.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = progress.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING");
if (statusSearching != null)
statusSearching.IsVisible = () => lobby.Map.Status == MapStatus.Searching;
var statusUnavailable = progress.GetOrNull("MAP_STATUS_UNAVAILABLE");
if (statusUnavailable != null)
statusUnavailable.IsVisible = () => lobby.Map.Status == MapStatus.Unavailable;
var statusError = progress.GetOrNull("MAP_STATUS_ERROR");
if (statusError != null)
statusError.IsVisible = () => lobby.Map.Status == MapStatus.DownloadError;
var statusDownloading = progress.GetOrNull<LabelWidget>("MAP_STATUS_DOWNLOADING");
if (statusDownloading != null)
{
statusDownloading.IsVisible = () => lobby.Map.Status == MapStatus.Downloading;
statusDownloading.GetText = () =>
{
if (lobby.Map.DownloadBytes == 0)
return "Connecting...";
// Server does not provide the total file length
if (lobby.Map.DownloadPercentage == 0)
return "Downloading {0} kB".F(lobby.Map.DownloadBytes / 1024);
return "Downloading {0} kB ({1}%)".F(lobby.Map.DownloadBytes / 1024, lobby.Map.DownloadPercentage);
};
}
var retry = progress.GetOrNull<ButtonWidget>("MAP_RETRY");
if (retry != null)
{
retry.IsVisible = () => lobby.Map.Status == MapStatus.DownloadError || lobby.Map.Status == MapStatus.Unavailable;
retry.OnClick = () =>
{
if (lobby.Map.Status == MapStatus.DownloadError)
lobby.Map.Install();
else if (lobby.Map.Status == MapStatus.Unavailable)
Game.modData.MapCache.QueryRemoteMapDetails(new [] { lobby.Map.Uid });
};
retry.GetText = () => lobby.Map.Status == MapStatus.DownloadError ? "Retry Install" : "Retry Search";
}
var progressbar = progress.GetOrNull<ProgressBarWidget>("MAP_PROGRESSBAR");
if (progressbar != null)
{
progressbar.IsIndeterminate = () => lobby.Map.DownloadPercentage == 0;
progressbar.GetPercentage = () => lobby.Map.DownloadPercentage;
progressbar.IsVisible = () => !retry.IsVisible();
}
}
}
}
}

View File

@@ -5,32 +5,141 @@ Container@LOBBY_MAP_PREVIEW:
Width:194
Height:250
Children:
Background@MAP_BG:
Container@MAP_AVAILABLE:
Width:PARENT_RIGHT
Height:194
Background:panel-gray
Height:PARENT_BOTTOM
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:197
Background@MAP_BG:
Width:PARENT_RIGHT
Height:194
Background:panel-gray
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:212
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:225
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Container@MAP_DOWNLOADABLE:
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:212
Height:PARENT_BOTTOM
Children:
Background@MAP_BG:
Width:PARENT_RIGHT
Height:164
Background:panel-gray
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:167
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:184
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Button@MAP_INSTALL:
Y:224
Width:PARENT_RIGHT
Height:25
Text:Install Map
Container@MAP_PROGRESS:
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:225
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Height:PARENT_BOTTOM
Children:
Background@MAP_BG:
Width:PARENT_RIGHT
Height:164
Background:panel-gray
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:167
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_STATUS_SEARCHING:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text: Searching OpenRA Resource Center...
Container@MAP_STATUS_UNAVAILABLE:
Width:PARENT_RIGHT
Children:
Label@a:
Y:184
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:This map was not found on the
Label@b:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:OpenRA Resource Center
Label@MAP_STATUS_ERROR:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:An error occurred during installation
Label@MAP_STATUS_DOWNLOADING:
Y:197
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
ProgressBar@MAP_PROGRESSBAR:
Y:224
Width:PARENT_RIGHT
Height:25
Indeterminate:True
Button@MAP_RETRY:
Y:224
Width:PARENT_RIGHT
Height:25

View File

@@ -5,32 +5,143 @@ Container@LOBBY_MAP_PREVIEW:
Width:214
Height:250
Children:
Background@MAP_BG:
Container@MAP_AVAILABLE:
Width:PARENT_RIGHT
Height:214
Background:dialog3
Height:PARENT_BOTTOM
Children:
MapPreview@MAP_PREVIEW:
X:2
Y:2
Width:210
Height:210
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:215
Background@MAP_BG:
Width:PARENT_RIGHT
Height:214
Background:dialog3
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:232
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:245
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Container@MAP_DOWNLOADABLE:
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:230
Height:PARENT_BOTTOM
Children:
Background@MAP_BG:
Width:PARENT_RIGHT
Height:182
Background:dialog3
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:185
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_TYPE:
Y:202
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Button@MAP_INSTALL:
Y:242
Width:PARENT_RIGHT
Height:25
Font:Bold
Text:Install Map
Container@MAP_PROGRESS:
Width:PARENT_RIGHT
Height:25
Font:TinyBold
Align:Center
Label@MAP_AUTHOR:
Y:243
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Height:PARENT_BOTTOM
Children:
Background@MAP_BG:
Width:PARENT_RIGHT
Height:182
Background:dialog3
Children:
MapPreview@MAP_PREVIEW:
X:1
Y:1
Width:PARENT_RIGHT-2
Height:PARENT_BOTTOM-2
TooltipContainer:TOOLTIP_CONTAINER
Label@MAP_TITLE:
Y:185
Width:PARENT_RIGHT
Height:25
Font:Bold
Align:Center
Label@MAP_STATUS_SEARCHING:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text: Searching OpenRA Resource Center...
Container@MAP_STATUS_UNAVAILABLE:
Width:PARENT_RIGHT
Children:
Label@a:
Y:202
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:This map was not found on the
Label@b:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:OpenRA Resource Center
Label@MAP_STATUS_ERROR:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
Text:An error occurred during installation
Label@MAP_STATUS_DOWNLOADING:
Y:215
Width:PARENT_RIGHT
Height:25
Font:Tiny
Align:Center
ProgressBar@MAP_PROGRESSBAR:
Y:242
Width:PARENT_RIGHT
Height:25
Indeterminate:True
Button@MAP_RETRY:
Y:242
Width:PARENT_RIGHT
Height:25
Font:Bold