Merge pull request #10992 from pchote/server-maps
Allow Dedicated Servers to query map info from the Resource Center.
This commit is contained in:
@@ -113,7 +113,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void QueryRemoteMapDetails(IEnumerable<string> uids)
|
public void QueryRemoteMapDetails(IEnumerable<string> uids, Action<MapPreview> mapDetailsReceived = null, Action queryFailed = null)
|
||||||
{
|
{
|
||||||
var maps = uids.Distinct()
|
var maps = uids.Distinct()
|
||||||
.Select(uid => previews[uid])
|
.Select(uid => previews[uid])
|
||||||
@@ -137,6 +137,9 @@ namespace OpenRA
|
|||||||
foreach (var p in maps.Values)
|
foreach (var p in maps.Values)
|
||||||
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
|
||||||
|
|
||||||
|
if (queryFailed != null)
|
||||||
|
queryFailed();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,11 +148,13 @@ namespace OpenRA
|
|||||||
{
|
{
|
||||||
var yaml = MiniYaml.FromString(data);
|
var yaml = MiniYaml.FromString(data);
|
||||||
foreach (var kv in yaml)
|
foreach (var kv in yaml)
|
||||||
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value);
|
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
|
Log.Write("debug", "Can't parse remote map search data:\n{0}", data);
|
||||||
|
if (queryFailed != null)
|
||||||
|
queryFailed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using System.Drawing;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using OpenRA.FileSystem;
|
using OpenRA.FileSystem;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
@@ -51,10 +52,72 @@ namespace OpenRA
|
|||||||
public readonly MapGridType map_grid_type;
|
public readonly MapGridType map_grid_type;
|
||||||
public readonly string minimap;
|
public readonly string minimap;
|
||||||
public readonly bool downloading;
|
public readonly bool downloading;
|
||||||
|
public readonly string tileset;
|
||||||
|
public readonly string rules;
|
||||||
|
public readonly string players_block;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MapPreview : IDisposable, IReadOnlyFileSystem
|
public class MapPreview : IDisposable, IReadOnlyFileSystem
|
||||||
{
|
{
|
||||||
|
/// <summary>Wrapper that enables map data to be replaced in an atomic fashion</summary>
|
||||||
|
class InnerData
|
||||||
|
{
|
||||||
|
public string Title;
|
||||||
|
public string[] Categories;
|
||||||
|
public string Author;
|
||||||
|
public string TileSet;
|
||||||
|
public MapPlayers Players;
|
||||||
|
public int PlayerCount;
|
||||||
|
public CPos[] SpawnPoints;
|
||||||
|
public MapGridType GridType;
|
||||||
|
public Rectangle Bounds;
|
||||||
|
public Bitmap Preview;
|
||||||
|
public MapStatus Status;
|
||||||
|
public MapClassification Class;
|
||||||
|
public MapVisibility Visibility;
|
||||||
|
public bool SuitableForInitialMap;
|
||||||
|
|
||||||
|
Lazy<Ruleset> rules;
|
||||||
|
public Ruleset Rules { get { return rules != null ? rules.Value : null; } }
|
||||||
|
public bool InvalidCustomRules { get; private set; }
|
||||||
|
public bool RulesLoaded { get; private set; }
|
||||||
|
|
||||||
|
public void SetRulesetGenerator(ModData modData, Func<Ruleset> generator)
|
||||||
|
{
|
||||||
|
InvalidCustomRules = false;
|
||||||
|
RulesLoaded = false;
|
||||||
|
|
||||||
|
// Note: multiple threads may try to access the value at the same time
|
||||||
|
// We rely on the thread-safety guarantees given by Lazy<T> to prevent race conitions.
|
||||||
|
// If you're thinking about replacing this, then you must be careful to keep this safe.
|
||||||
|
rules = Exts.Lazy(() =>
|
||||||
|
{
|
||||||
|
if (generator == null)
|
||||||
|
return Ruleset.LoadDefaultsForTileSet(modData, TileSet);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return generator();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Write("debug", "Failed to load rules for `{0}` with error :{1}", Title, e.Message);
|
||||||
|
InvalidCustomRules = true;
|
||||||
|
return Ruleset.LoadDefaultsForTileSet(modData, TileSet);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RulesLoaded = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public InnerData Clone()
|
||||||
|
{
|
||||||
|
return (InnerData)MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static readonly CPos[] NoSpawns = new CPos[] { };
|
static readonly CPos[] NoSpawns = new CPos[] { };
|
||||||
MapCache cache;
|
MapCache cache;
|
||||||
ModData modData;
|
ModData modData;
|
||||||
@@ -63,25 +126,26 @@ namespace OpenRA
|
|||||||
public IReadOnlyPackage Package { get; private set; }
|
public IReadOnlyPackage Package { get; private set; }
|
||||||
IReadOnlyPackage parentPackage;
|
IReadOnlyPackage parentPackage;
|
||||||
|
|
||||||
public string Title { get; private set; }
|
volatile InnerData innerData;
|
||||||
public string[] Categories { get; private set; }
|
|
||||||
public string Author { get; private set; }
|
|
||||||
public string TileSet { get; private set; }
|
|
||||||
public MapPlayers Players { get; private set; }
|
|
||||||
public int PlayerCount { get; private set; }
|
|
||||||
public CPos[] SpawnPoints { get; private set; }
|
|
||||||
public MapGridType GridType { get; private set; }
|
|
||||||
public Rectangle Bounds { get; private set; }
|
|
||||||
public Bitmap Preview { get; private set; }
|
|
||||||
public MapStatus Status { get; private set; }
|
|
||||||
public MapClassification Class { get; private set; }
|
|
||||||
public MapVisibility Visibility { get; private set; }
|
|
||||||
public bool SuitableForInitialMap { get; private set; }
|
|
||||||
|
|
||||||
Lazy<Ruleset> rules;
|
public string Title { get { return innerData.Title; } }
|
||||||
public Ruleset Rules { get { return rules != null ? rules.Value : null; } }
|
public string[] Categories { get { return innerData.Categories; } }
|
||||||
public bool InvalidCustomRules { get; private set; }
|
public string Author { get { return innerData.Author; } }
|
||||||
public bool RulesLoaded { get; private set; }
|
public string TileSet { get { return innerData.TileSet; } }
|
||||||
|
public MapPlayers Players { get { return innerData.Players; } }
|
||||||
|
public int PlayerCount { get { return innerData.PlayerCount; } }
|
||||||
|
public CPos[] SpawnPoints { get { return innerData.SpawnPoints; } }
|
||||||
|
public MapGridType GridType { get { return innerData.GridType; } }
|
||||||
|
public Rectangle Bounds { get { return innerData.Bounds; } }
|
||||||
|
public Bitmap Preview { get { return innerData.Preview; } }
|
||||||
|
public MapStatus Status { get { return innerData.Status; } }
|
||||||
|
public MapClassification Class { get { return innerData.Class; } }
|
||||||
|
public MapVisibility Visibility { get { return innerData.Visibility; } }
|
||||||
|
public bool SuitableForInitialMap { get { return innerData.SuitableForInitialMap; } }
|
||||||
|
|
||||||
|
public Ruleset Rules { get { return innerData.Rules; } }
|
||||||
|
public bool InvalidCustomRules { get { return innerData.InvalidCustomRules; } }
|
||||||
|
public bool RulesLoaded { get { return innerData.RulesLoaded; } }
|
||||||
|
|
||||||
Download download;
|
Download download;
|
||||||
public long DownloadBytes { get; private set; }
|
public long DownloadBytes { get; private set; }
|
||||||
@@ -115,16 +179,23 @@ namespace OpenRA
|
|||||||
this.modData = modData;
|
this.modData = modData;
|
||||||
|
|
||||||
Uid = uid;
|
Uid = uid;
|
||||||
Title = "Unknown Map";
|
innerData = new InnerData
|
||||||
Categories = new[] { "Unknown" };
|
{
|
||||||
Author = "Unknown Author";
|
Title = "Unknown Map",
|
||||||
PlayerCount = 0;
|
Categories = new[] { "Unknown" },
|
||||||
Bounds = Rectangle.Empty;
|
Author = "Unknown Author",
|
||||||
SpawnPoints = NoSpawns;
|
TileSet = "unknown",
|
||||||
GridType = gridType;
|
Players = null,
|
||||||
Status = MapStatus.Unavailable;
|
PlayerCount = 0,
|
||||||
Class = MapClassification.Unknown;
|
SpawnPoints = NoSpawns,
|
||||||
Visibility = MapVisibility.Lobby;
|
GridType = gridType,
|
||||||
|
Bounds = Rectangle.Empty,
|
||||||
|
Preview = null,
|
||||||
|
Status = MapStatus.Unavailable,
|
||||||
|
Class = MapClassification.Unknown,
|
||||||
|
Visibility = MapVisibility.Lobby,
|
||||||
|
SuitableForInitialMap = false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType)
|
public void UpdateFromMap(IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType)
|
||||||
@@ -140,8 +211,10 @@ namespace OpenRA
|
|||||||
|
|
||||||
Package = p;
|
Package = p;
|
||||||
parentPackage = parent;
|
parentPackage = parent;
|
||||||
GridType = gridType;
|
|
||||||
Class = classification;
|
var newData = innerData.Clone();
|
||||||
|
newData.GridType = gridType;
|
||||||
|
newData.Class = classification;
|
||||||
|
|
||||||
MiniYaml temp;
|
MiniYaml temp;
|
||||||
if (yaml.TryGetValue("MapFormat", out temp))
|
if (yaml.TryGetValue("MapFormat", out temp))
|
||||||
@@ -152,23 +225,29 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (yaml.TryGetValue("Title", out temp))
|
if (yaml.TryGetValue("Title", out temp))
|
||||||
Title = temp.Value;
|
newData.Title = temp.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Categories", out temp))
|
if (yaml.TryGetValue("Categories", out temp))
|
||||||
Categories = FieldLoader.GetValue<string[]>("Categories", temp.Value);
|
newData.Categories = FieldLoader.GetValue<string[]>("Categories", temp.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("Tileset", out temp))
|
if (yaml.TryGetValue("Tileset", out temp))
|
||||||
TileSet = temp.Value;
|
newData.TileSet = temp.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Author", out temp))
|
if (yaml.TryGetValue("Author", out temp))
|
||||||
Author = temp.Value;
|
newData.Author = temp.Value;
|
||||||
|
|
||||||
if (yaml.TryGetValue("Bounds", out temp))
|
if (yaml.TryGetValue("Bounds", out temp))
|
||||||
Bounds = FieldLoader.GetValue<Rectangle>("Bounds", temp.Value);
|
newData.Bounds = FieldLoader.GetValue<Rectangle>("Bounds", temp.Value);
|
||||||
|
|
||||||
if (yaml.TryGetValue("Visibility", out temp))
|
if (yaml.TryGetValue("Visibility", out temp))
|
||||||
Visibility = FieldLoader.GetValue<MapVisibility>("Visibility", temp.Value);
|
newData.Visibility = FieldLoader.GetValue<MapVisibility>("Visibility", temp.Value);
|
||||||
|
|
||||||
string requiresMod = string.Empty;
|
string requiresMod = string.Empty;
|
||||||
if (yaml.TryGetValue("RequiresMod", out temp))
|
if (yaml.TryGetValue("RequiresMod", out temp))
|
||||||
requiresMod = temp.Value;
|
requiresMod = temp.Value;
|
||||||
|
|
||||||
Status = mapCompatibility == null || mapCompatibility.Contains(requiresMod) ? MapStatus.Available : MapStatus.Unavailable;
|
newData.Status = mapCompatibility == null || mapCompatibility.Contains(requiresMod) ?
|
||||||
|
MapStatus.Available : MapStatus.Unavailable;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -183,15 +262,15 @@ namespace OpenRA
|
|||||||
spawns.Add(s.InitDict.Get<LocationInit>().Value(null));
|
spawns.Add(s.InitDict.Get<LocationInit>().Value(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
SpawnPoints = spawns.ToArray();
|
newData.SpawnPoints = spawns.ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
SpawnPoints = new CPos[0];
|
newData.SpawnPoints = new CPos[0];
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
SpawnPoints = new CPos[0];
|
newData.SpawnPoints = new CPos[0];
|
||||||
Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -200,22 +279,17 @@ namespace OpenRA
|
|||||||
MiniYaml playerDefinitions;
|
MiniYaml playerDefinitions;
|
||||||
if (yaml.TryGetValue("Players", out playerDefinitions))
|
if (yaml.TryGetValue("Players", out playerDefinitions))
|
||||||
{
|
{
|
||||||
Players = new MapPlayers(playerDefinitions.Nodes);
|
newData.Players = new MapPlayers(playerDefinitions.Nodes);
|
||||||
PlayerCount = Players.Players.Count(x => x.Value.Playable);
|
newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable);
|
||||||
SuitableForInitialMap = EvaluateUserFriendliness(Players.Players);
|
newData.SuitableForInitialMap = EvaluateUserFriendliness(newData.Players.Players);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: multiple threads may try to access the value at the same time
|
newData.SetRulesetGenerator(modData, () =>
|
||||||
// We rely on the thread-safety guarantees given by Lazy<T> to prevent race conitions.
|
|
||||||
// If you're thinking about replacing this, then you must be careful to keep this safe.
|
|
||||||
rules = Exts.Lazy(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var ruleDefinitions = LoadRuleSection(yaml, "Rules");
|
var ruleDefinitions = LoadRuleSection(yaml, "Rules");
|
||||||
var weaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
var weaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
||||||
@@ -225,22 +299,14 @@ namespace OpenRA
|
|||||||
var sequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
var sequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||||
return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
||||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions);
|
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions);
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Log.Write("debug", "Failed to load rules for `{0}` with error :{1}", Title, e.Message);
|
|
||||||
InvalidCustomRules = true;
|
|
||||||
return Ruleset.LoadDefaultsForTileSet(modData, TileSet);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
RulesLoaded = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (p.Contains("map.png"))
|
if (p.Contains("map.png"))
|
||||||
using (var dataStream = p.GetStream("map.png"))
|
using (var dataStream = p.GetStream("map.png"))
|
||||||
Preview = new Bitmap(dataStream);
|
newData.Preview = new Bitmap(dataStream);
|
||||||
|
|
||||||
|
// Assign the new data atomically
|
||||||
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
|
MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
|
||||||
@@ -272,11 +338,12 @@ namespace OpenRA
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml)
|
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
||||||
{
|
|
||||||
// Update on the main thread to ensure consistency
|
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
{
|
{
|
||||||
|
var newData = innerData.Clone();
|
||||||
|
newData.Status = status;
|
||||||
|
newData.Class = MapClassification.Remote;
|
||||||
|
|
||||||
if (status == MapStatus.DownloadAvailable)
|
if (status == MapStatus.DownloadAvailable)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -286,33 +353,55 @@ namespace OpenRA
|
|||||||
// Map download has been disabled server side
|
// Map download has been disabled server side
|
||||||
if (!r.downloading)
|
if (!r.downloading)
|
||||||
{
|
{
|
||||||
Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Title = r.title;
|
newData.Title = r.title;
|
||||||
Categories = r.categories;
|
newData.Categories = r.categories;
|
||||||
Author = r.author;
|
newData.Author = r.author;
|
||||||
PlayerCount = r.players;
|
newData.PlayerCount = r.players;
|
||||||
Bounds = r.bounds;
|
newData.Bounds = r.bounds;
|
||||||
|
newData.TileSet = r.tileset;
|
||||||
|
|
||||||
var spawns = new CPos[r.spawnpoints.Length / 2];
|
var spawns = new CPos[r.spawnpoints.Length / 2];
|
||||||
for (var j = 0; j < r.spawnpoints.Length; j += 2)
|
for (var j = 0; j < r.spawnpoints.Length; j += 2)
|
||||||
spawns[j / 2] = new CPos(r.spawnpoints[j], r.spawnpoints[j + 1]);
|
spawns[j / 2] = new CPos(r.spawnpoints[j], r.spawnpoints[j + 1]);
|
||||||
SpawnPoints = spawns;
|
newData.SpawnPoints = spawns;
|
||||||
GridType = r.map_grid_type;
|
newData.GridType = r.map_grid_type;
|
||||||
|
newData.Preview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap)));
|
||||||
|
|
||||||
Preview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap)));
|
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
||||||
|
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
||||||
|
|
||||||
|
newData.SetRulesetGenerator(modData, () =>
|
||||||
|
{
|
||||||
|
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
||||||
|
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
||||||
|
var ruleDefinitions = LoadRuleSection(rulesYaml, "Rules");
|
||||||
|
var weaponDefinitions = LoadRuleSection(rulesYaml, "Weapons");
|
||||||
|
var voiceDefinitions = LoadRuleSection(rulesYaml, "Voices");
|
||||||
|
var musicDefinitions = LoadRuleSection(rulesYaml, "Music");
|
||||||
|
var notificationDefinitions = LoadRuleSection(rulesYaml, "Notifications");
|
||||||
|
var sequenceDefinitions = LoadRuleSection(rulesYaml, "Sequences");
|
||||||
|
return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
||||||
|
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
|
|
||||||
if (Preview != null)
|
// Commit updated data before running the callbacks
|
||||||
|
innerData = newData;
|
||||||
|
|
||||||
|
if (innerData.Preview != null)
|
||||||
cache.CacheMinimap(this);
|
cache.CacheMinimap(this);
|
||||||
|
|
||||||
|
if (parseMetadata != null)
|
||||||
|
parseMetadata(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = status;
|
// Update the status and class unconditionally
|
||||||
Class = MapClassification.Remote;
|
innerData = newData;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Install(Action onSuccess)
|
public void Install(Action onSuccess)
|
||||||
@@ -320,12 +409,12 @@ namespace OpenRA
|
|||||||
if (Status != MapStatus.DownloadAvailable || !Game.Settings.Game.AllowDownloading)
|
if (Status != MapStatus.DownloadAvailable || !Game.Settings.Game.AllowDownloading)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Status = MapStatus.Downloading;
|
innerData.Status = MapStatus.Downloading;
|
||||||
var installLocation = cache.MapLocations.FirstOrDefault(p => p.Value == MapClassification.User);
|
var installLocation = cache.MapLocations.FirstOrDefault(p => p.Value == MapClassification.User);
|
||||||
if (installLocation.Key == null || !(installLocation.Key is IReadWritePackage))
|
if (installLocation.Key == null || !(installLocation.Key is IReadWritePackage))
|
||||||
{
|
{
|
||||||
Log.Write("debug", "Map install directory not found");
|
Log.Write("debug", "Map install directory not found");
|
||||||
Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +435,7 @@ namespace OpenRA
|
|||||||
// Map not found
|
// Map not found
|
||||||
if (res.Headers["Content-Disposition"] == null)
|
if (res.Headers["Content-Disposition"] == null)
|
||||||
{
|
{
|
||||||
Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +452,7 @@ namespace OpenRA
|
|||||||
Log.Write("debug", "Remote map download failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
|
Log.Write("debug", "Remote map download failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
|
||||||
Log.Write("debug", "URL was: {0}", mapUrl);
|
Log.Write("debug", "URL was: {0}", mapUrl);
|
||||||
|
|
||||||
Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +471,7 @@ namespace OpenRA
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.Message);
|
Console.WriteLine(e.Message);
|
||||||
Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
}
|
}
|
||||||
}).Start();
|
}).Start();
|
||||||
}
|
}
|
||||||
@@ -398,7 +487,7 @@ namespace OpenRA
|
|||||||
|
|
||||||
public void Invalidate()
|
public void Invalidate()
|
||||||
{
|
{
|
||||||
Status = MapStatus.Unavailable;
|
innerData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ namespace OpenRA
|
|||||||
[Desc("Disallow games where only one player plays with bots.")]
|
[Desc("Disallow games where only one player plays with bots.")]
|
||||||
public bool DisableSinglePlayer = false;
|
public bool DisableSinglePlayer = false;
|
||||||
|
|
||||||
|
[Desc("Query map information from the Resource Center if they are not available locally.")]
|
||||||
|
public bool QueryMapRepository = true;
|
||||||
|
|
||||||
public string TimestampFormat = "s";
|
public string TimestampFormat = "s";
|
||||||
|
|
||||||
public ServerSettings Clone()
|
public ServerSettings Clone()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Mods.Common.Traits;
|
using OpenRA.Mods.Common.Traits;
|
||||||
using OpenRA.Network;
|
using OpenRA.Network;
|
||||||
@@ -338,16 +339,24 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.ModData.MapCache[s].Status != MapStatus.Available)
|
var lastMap = server.LobbyInfo.GlobalSettings.Map;
|
||||||
|
Action<MapPreview> selectMap = map =>
|
||||||
{
|
{
|
||||||
server.SendOrderTo(conn, "Message", "Map was not found on server.");
|
// Make sure the map hasn't changed in the meantime
|
||||||
return true;
|
if (server.LobbyInfo.GlobalSettings.Map != lastMap)
|
||||||
}
|
return;
|
||||||
|
|
||||||
server.LobbyInfo.GlobalSettings.Map = s;
|
server.LobbyInfo.GlobalSettings.Map = map.Uid;
|
||||||
|
|
||||||
var oldSlots = server.LobbyInfo.Slots.Keys.ToArray();
|
var oldSlots = server.LobbyInfo.Slots.Keys.ToArray();
|
||||||
LoadMap(server);
|
server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map];
|
||||||
|
|
||||||
|
server.LobbyInfo.Slots = server.Map.Players.Players
|
||||||
|
.Select(p => MakeSlotFromPlayerReference(p.Value))
|
||||||
|
.Where(ss => ss != null)
|
||||||
|
.ToDictionary(ss => ss.PlayerReference, ss => ss);
|
||||||
|
|
||||||
|
LoadMapSettings(server.LobbyInfo.GlobalSettings, server.Map.Rules);
|
||||||
|
|
||||||
// Reset client states
|
// Reset client states
|
||||||
foreach (var c in server.LobbyInfo.Clients)
|
foreach (var c in server.LobbyInfo.Clients)
|
||||||
@@ -380,7 +389,7 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
server.LobbyInfo.Clients.Remove(c);
|
server.LobbyInfo.Clients.Remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if color is allowed and get an alternative it isn't
|
// Validate if color is allowed and get an alternative if it isn't
|
||||||
foreach (var c in server.LobbyInfo.Clients)
|
foreach (var c in server.LobbyInfo.Clients)
|
||||||
if (c.Slot == null || (c.Slot != null && !server.LobbyInfo.Slots[c.Slot].LockColor))
|
if (c.Slot == null || (c.Slot != null && !server.LobbyInfo.Slots[c.Slot].LockColor))
|
||||||
c.Color = c.PreferredColor = SanitizePlayerColor(server, c.Color, c.Index, conn);
|
c.Color = c.PreferredColor = SanitizePlayerColor(server, c.Color, c.Index, conn);
|
||||||
@@ -396,6 +405,21 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
server.SendMessage("Singleplayer games have been disabled on this server.");
|
server.SendMessage("Singleplayer games have been disabled on this server.");
|
||||||
else if (server.Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots))
|
else if (server.Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots))
|
||||||
server.SendMessage("Bots have been disabled on this map.");
|
server.SendMessage("Bots have been disabled on this map.");
|
||||||
|
};
|
||||||
|
|
||||||
|
Action queryFailed = () =>
|
||||||
|
server.SendOrderTo(conn, "Message", "Map was not found on server.");
|
||||||
|
|
||||||
|
var m = server.ModData.MapCache[s];
|
||||||
|
if (m.Status == MapStatus.Available || m.Status == MapStatus.DownloadAvailable)
|
||||||
|
selectMap(m);
|
||||||
|
else if (server.Settings.QueryMapRepository)
|
||||||
|
{
|
||||||
|
server.SendOrderTo(conn, "Message", "Searching for map on the Resource Center...");
|
||||||
|
server.ModData.MapCache.QueryRemoteMapDetails(new[] { s }, selectMap, queryFailed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
queryFailed();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -996,7 +1020,18 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
|
|
||||||
public void ServerStarted(S server)
|
public void ServerStarted(S server)
|
||||||
{
|
{
|
||||||
LoadMap(server);
|
// Remote maps are not supported for the initial map
|
||||||
|
var uid = server.LobbyInfo.GlobalSettings.Map;
|
||||||
|
server.Map = server.ModData.MapCache[uid];
|
||||||
|
if (server.Map.Status != MapStatus.Available)
|
||||||
|
throw new Exception("Map {0} not found".F(uid));
|
||||||
|
|
||||||
|
server.LobbyInfo.Slots = server.Map.Players.Players
|
||||||
|
.Select(p => MakeSlotFromPlayerReference(p.Value))
|
||||||
|
.Where(s => s != null)
|
||||||
|
.ToDictionary(s => s.PlayerReference, s => s);
|
||||||
|
|
||||||
|
LoadMapSettings(server.LobbyInfo.GlobalSettings, server.Map.Rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Session.Slot MakeSlotFromPlayerReference(PlayerReference pr)
|
static Session.Slot MakeSlotFromPlayerReference(PlayerReference pr)
|
||||||
@@ -1045,18 +1080,6 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
gs.Difficulty = mapOptions.Difficulty ?? mapOptions.Difficulties.FirstOrDefault();
|
gs.Difficulty = mapOptions.Difficulty ?? mapOptions.Difficulties.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadMap(S server)
|
|
||||||
{
|
|
||||||
server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map];
|
|
||||||
|
|
||||||
server.LobbyInfo.Slots = server.Map.Players.Players
|
|
||||||
.Select(p => MakeSlotFromPlayerReference(p.Value))
|
|
||||||
.Where(s => s != null)
|
|
||||||
.ToDictionary(s => s.PlayerReference, s => s);
|
|
||||||
|
|
||||||
LoadMapSettings(server.LobbyInfo.GlobalSettings, server.Map.Rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
static HSLColor SanitizePlayerColor(S server, HSLColor askedColor, int playerIndex, Connection connectionToEcho = null)
|
static HSLColor SanitizePlayerColor(S server, HSLColor askedColor, int playerIndex, Connection connectionToEcho = null)
|
||||||
{
|
{
|
||||||
var validator = server.ModData.Manifest.Get<ColorValidator>();
|
var validator = server.ModData.Manifest.Get<ColorValidator>();
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var startGameButton = lobby.GetOrNull<ButtonWidget>("START_GAME_BUTTON");
|
var startGameButton = lobby.GetOrNull<ButtonWidget>("START_GAME_BUTTON");
|
||||||
if (startGameButton != null)
|
if (startGameButton != null)
|
||||||
{
|
{
|
||||||
startGameButton.IsDisabled = () => configurationDisabled() ||
|
startGameButton.IsDisabled = () => configurationDisabled() || Map.Status != MapStatus.Available ||
|
||||||
orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) ||
|
orderManager.LobbyInfo.Slots.Any(sl => sl.Value.Required && orderManager.LobbyInfo.ClientInSlot(sl.Key) == null) ||
|
||||||
(orderManager.LobbyInfo.GlobalSettings.DisableSingleplayer && orderManager.LobbyInfo.IsSinglePlayer);
|
(orderManager.LobbyInfo.GlobalSettings.DisableSingleplayer && orderManager.LobbyInfo.IsSinglePlayer);
|
||||||
|
|
||||||
@@ -464,8 +464,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
};
|
};
|
||||||
|
|
||||||
startingUnits.IsDisabled = () => configurationDisabled() || startUnitsLocked.Update(Map);
|
startingUnits.IsDisabled = () => configurationDisabled() || startUnitsLocked.Update(Map);
|
||||||
startingUnits.GetText = () => Map.Status != MapStatus.Available ||
|
startingUnits.GetText = () => !Map.RulesLoaded || startUnitsLocked.Update(Map) ?
|
||||||
!Map.RulesLoaded || startUnitsLocked.Update(Map) ? "Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass);
|
"Not Available" : className(orderManager.LobbyInfo.GlobalSettings.StartingUnitsClass);
|
||||||
startingUnits.OnMouseDown = _ =>
|
startingUnits.OnMouseDown = _ =>
|
||||||
{
|
{
|
||||||
var classes = startUnitsInfos.Update(Map).Select(a => a.Class).Distinct();
|
var classes = startUnitsInfos.Update(Map).Select(a => a.Class).Distinct();
|
||||||
@@ -496,8 +496,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
map => map.Rules.Actors["player"].TraitInfo<PlayerResourcesInfo>());
|
map => map.Rules.Actors["player"].TraitInfo<PlayerResourcesInfo>());
|
||||||
|
|
||||||
startingCash.IsDisabled = () => configurationDisabled() || playerResources.Update(Map).DefaultCashLocked;
|
startingCash.IsDisabled = () => configurationDisabled() || playerResources.Update(Map).DefaultCashLocked;
|
||||||
startingCash.GetText = () => Map.Status != MapStatus.Available ||
|
startingCash.GetText = () => !Map.RulesLoaded || playerResources.Update(Map).DefaultCashLocked ?
|
||||||
!Map.RulesLoaded || playerResources.Update(Map).DefaultCashLocked ? "Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash);
|
"Not Available" : "${0}".F(orderManager.LobbyInfo.GlobalSettings.StartingCash);
|
||||||
startingCash.OnMouseDown = _ =>
|
startingCash.OnMouseDown = _ =>
|
||||||
{
|
{
|
||||||
var options = playerResources.Update(Map).SelectableCash.Select(c => new DropDownOption
|
var options = playerResources.Update(Map).SelectableCash.Select(c => new DropDownOption
|
||||||
@@ -533,8 +533,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
techLevelDescription.IsVisible = techLevel.IsVisible;
|
techLevelDescription.IsVisible = techLevel.IsVisible;
|
||||||
|
|
||||||
techLevel.IsDisabled = () => configurationDisabled() || mapOptions.Update(Map).TechLevelLocked;
|
techLevel.IsDisabled = () => configurationDisabled() || mapOptions.Update(Map).TechLevelLocked;
|
||||||
techLevel.GetText = () => Map.Status != MapStatus.Available ||
|
techLevel.GetText = () => !Map.RulesLoaded || mapOptions.Update(Map).TechLevelLocked ?
|
||||||
!Map.RulesLoaded || mapOptions.Update(Map).TechLevelLocked ? "Not Available" : "{0}".F(orderManager.LobbyInfo.GlobalSettings.TechLevel);
|
"Not Available" : "{0}".F(orderManager.LobbyInfo.GlobalSettings.TechLevel);
|
||||||
techLevel.OnMouseDown = _ =>
|
techLevel.OnMouseDown = _ =>
|
||||||
{
|
{
|
||||||
var options = techLevels.Update(Map).Select(c => new DropDownOption
|
var options = techLevels.Update(Map).Select(c => new DropDownOption
|
||||||
@@ -776,6 +776,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LoadMapPreviewRules(MapPreview map)
|
||||||
|
{
|
||||||
|
new Task(() =>
|
||||||
|
{
|
||||||
|
// Force map rules to be loaded on this background thread
|
||||||
|
var unused = map.Rules;
|
||||||
|
}).Start();
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateCurrentMap()
|
void UpdateCurrentMap()
|
||||||
{
|
{
|
||||||
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
|
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
|
||||||
@@ -814,8 +823,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
});
|
});
|
||||||
}).Start();
|
}).Start();
|
||||||
}
|
}
|
||||||
|
else if (Map.Status == MapStatus.DownloadAvailable)
|
||||||
|
LoadMapPreviewRules(Map);
|
||||||
else if (Game.Settings.Game.AllowDownloading)
|
else if (Game.Settings.Game.AllowDownloading)
|
||||||
modData.MapCache.QueryRemoteMapDetails(new[] { uid });
|
modData.MapCache.QueryRemoteMapDetails(new[] { uid }, LoadMapPreviewRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePlayerList()
|
void UpdatePlayerList()
|
||||||
|
|||||||
Reference in New Issue
Block a user