Merge pull request #5353 from pchote/map-validation

Disable maps with invalid rules
This commit is contained in:
Matthias Mailänder
2014-05-22 09:55:06 +02:00
13 changed files with 180 additions and 34 deletions

View File

@@ -21,12 +21,13 @@ namespace OpenRA
{ {
public readonly ModMetadata Mod; public readonly ModMetadata Mod;
public readonly string[] public readonly string[]
Folders, MapFolders, Rules, ServerTraits, Folders, Rules, ServerTraits,
Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout, Sequences, VoxelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
Weapons, Voices, Notifications, Music, Movies, Translations, TileSets, Weapons, Voices, Notifications, Music, Movies, Translations, TileSets,
ChromeMetrics, PackageContents, LuaScripts, MapCompatibility, Missions; ChromeMetrics, PackageContents, LuaScripts, MapCompatibility, Missions;
public readonly Dictionary<string, string> Packages; public readonly IReadOnlyDictionary<string, string> Packages;
public readonly IReadOnlyDictionary<string, string> MapFolders;
public readonly MiniYaml LoadScreen; public readonly MiniYaml LoadScreen;
public readonly MiniYaml LobbyDefaults; public readonly MiniYaml LobbyDefaults;
public readonly Dictionary<string, Pair<string, int>> Fonts; public readonly Dictionary<string, Pair<string, int>> Fonts;
@@ -43,8 +44,8 @@ namespace OpenRA
// TODO: Use fieldloader // TODO: Use fieldloader
Folders = YamlList(yaml, "Folders"); Folders = YamlList(yaml, "Folders");
MapFolders = YamlList(yaml, "MapFolders"); MapFolders = YamlDictionary(yaml, "MapFolders");
Packages = yaml["Packages"].NodesDict.ToDictionary(x => x.Key, x => x.Value.Value); Packages = YamlDictionary(yaml, "Packages");
Rules = YamlList(yaml, "Rules"); Rules = YamlList(yaml, "Rules");
ServerTraits = YamlList(yaml, "ServerTraits"); ServerTraits = YamlList(yaml, "ServerTraits");
Sequences = YamlList(yaml, "Sequences"); Sequences = YamlList(yaml, "Sequences");
@@ -95,5 +96,14 @@ namespace OpenRA
return yaml[key].NodesDict.Keys.ToArray(); return yaml[key].NodesDict.Keys.ToArray();
} }
static IReadOnlyDictionary<string, string> YamlDictionary(Dictionary<string, MiniYaml> yaml, string key)
{
if (!yaml.ContainsKey(key))
return new ReadOnlyDictionary<string, string>();
var inner = yaml[key].NodesDict.ToDictionary(x => x.Key, x => x.Value.Value);
return new ReadOnlyDictionary<string, string>(inner);
}
} }
} }

View File

@@ -42,16 +42,19 @@ namespace OpenRA
public void LoadMaps() public void LoadMaps()
{ {
var paths = modData.Manifest.MapFolders.SelectMany(f => FindMapsIn(f)); // Expand the dictionary (dir path, dir type) to a dictionary of (map path, dir type)
foreach (var path in paths) var mapPaths = modData.Manifest.MapFolders.SelectMany(kv =>
FindMapsIn(kv.Key).ToDictionary(p => p, p => string.IsNullOrEmpty(kv.Value) ? MapClassification.Unknown : Enum<MapClassification>.Parse(kv.Value)));
foreach (var path in mapPaths)
{ {
try try
{ {
using (new Support.PerfTimer(path)) using (new Support.PerfTimer(path.Key))
{ {
var map = new Map(path, modData.Manifest.Mod.Id); var map = new Map(path.Key, modData.Manifest.Mod.Id);
if (modData.Manifest.MapCompatibility.Contains(map.RequiresMod)) if (modData.Manifest.MapCompatibility.Contains(map.RequiresMod))
previews[map.Uid].UpdateFromMap(map); previews[map.Uid].UpdateFromMap(map, path.Value);
} }
} }
catch (Exception e) catch (Exception e)

View File

@@ -24,6 +24,12 @@ namespace OpenRA
{ {
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError } public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError }
// Used for grouping maps in the UI
public enum MapClassification { Unknown, System, User, Remote }
// Used for verifying map availability in the lobby
public enum MapRuleStatus { Unknown, Cached, Invalid }
// Fields names must match the with the remote API // Fields names must match the with the remote API
public class RemoteMapData public class RemoteMapData
{ {
@@ -53,6 +59,9 @@ namespace OpenRA
public Bitmap CustomPreview { get; private set; } public Bitmap CustomPreview { get; private set; }
public Map Map { get; private set; } public Map Map { get; private set; }
public MapStatus Status { get; private set; } public MapStatus Status { get; private set; }
public MapClassification Class { get; private set; }
public MapRuleStatus RuleStatus { get; private set; }
Download download; Download download;
public long DownloadBytes { get; private set; } public long DownloadBytes { get; private set; }
@@ -94,9 +103,10 @@ namespace OpenRA
Bounds = Rectangle.Empty; Bounds = Rectangle.Empty;
SpawnPoints = NoSpawns; SpawnPoints = NoSpawns;
Status = MapStatus.Unavailable; Status = MapStatus.Unavailable;
Class = MapClassification.Unknown;
} }
public void UpdateFromMap(Map m) public void UpdateFromMap(Map m, MapClassification classification)
{ {
Map = m; Map = m;
Title = m.Title; Title = m.Title;
@@ -108,6 +118,7 @@ namespace OpenRA
SpawnPoints = m.GetSpawnPoints().ToList(); SpawnPoints = m.GetSpawnPoints().ToList();
CustomPreview = m.CustomPreview; CustomPreview = m.CustomPreview;
Status = MapStatus.Available; Status = MapStatus.Available;
Class = classification;
} }
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml) public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml)
@@ -146,8 +157,9 @@ namespace OpenRA
if (CustomPreview != null) if (CustomPreview != null)
cache.CacheMinimap(this); cache.CacheMinimap(this);
} }
Status = status;
Status = status;
Class = MapClassification.Remote;
}); });
} }
@@ -199,7 +211,7 @@ namespace OpenRA
} }
Log.Write("debug", "Downloaded map to '{0}'", mapPath); Log.Write("debug", "Downloaded map to '{0}'", mapPath);
Game.RunAfterTick(() => UpdateFromMap(new Map(mapPath))); Game.RunAfterTick(() => UpdateFromMap(new Map(mapPath), MapClassification.User));
}; };
download = new Download(mapUrl, mapPath, onDownloadProgress, onDownloadComplete); download = new Download(mapUrl, mapPath, onDownloadProgress, onDownloadComplete);
@@ -220,5 +232,22 @@ namespace OpenRA
download.Cancel(); download.Cancel();
download = null; download = null;
} }
public void CacheRules()
{
if (RuleStatus != MapRuleStatus.Unknown)
return;
try
{
Map.PreloadRules();
RuleStatus = MapRuleStatus.Cached;
}
catch (Exception e)
{
Log.Write("debug", "Map {0} failed validation with an exception:\n{1}", Uid, e.Message);
RuleStatus = MapRuleStatus.Invalid;
}
}
} }
} }

View File

@@ -12,6 +12,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.Network; using OpenRA.Network;
using OpenRA.Traits; using OpenRA.Traits;
@@ -170,7 +171,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
if (slotsButton != null) if (slotsButton != null)
{ {
slotsButton.IsDisabled = () => configurationDisabled() || panel != PanelType.Players || slotsButton.IsDisabled = () => configurationDisabled() || panel != PanelType.Players ||
!orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots || !s.LockTeam); Map.RuleStatus != MapRuleStatus.Cached || !orderManager.LobbyInfo.Slots.Values.Any(s => s.AllowBots || !s.LockTeam);
var botNames = modRules.Actors["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name); var botNames = modRules.Actors["player"].Traits.WithInterface<IBotInfo>().Select(t => t.Name);
slotsButton.OnMouseDown = _ => slotsButton.OnMouseDown = _ =>
@@ -264,7 +265,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
optionsBin.IsVisible = () => panel == PanelType.Options; optionsBin.IsVisible = () => panel == PanelType.Options;
var optionsButton = lobby.Get<ButtonWidget>("OPTIONS_BUTTON"); var optionsButton = lobby.Get<ButtonWidget>("OPTIONS_BUTTON");
optionsButton.IsDisabled = () => panel == PanelType.Kick || panel == PanelType.ForceStart; optionsButton.IsDisabled = () => Map.RuleStatus != MapRuleStatus.Cached || panel == PanelType.Kick || panel == PanelType.ForceStart;
optionsButton.GetText = () => panel == PanelType.Options ? "Players" : "Options"; optionsButton.GetText = () => panel == PanelType.Options ? "Players" : "Options";
optionsButton.OnClick = () => panel = (panel == PanelType.Options) ? PanelType.Players : PanelType.Options; optionsButton.OnClick = () => panel = (panel == PanelType.Options) ? PanelType.Players : PanelType.Options;
@@ -278,7 +279,7 @@ namespace OpenRA.Mods.RA.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.RuleStatus != MapRuleStatus.Cached ||
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);
startGameButton.OnClick = () => startGameButton.OnClick = () =>
{ {
@@ -582,8 +583,20 @@ namespace OpenRA.Mods.RA.Widgets.Logic
return; return;
Map = Game.modData.MapCache[uid]; Map = Game.modData.MapCache[uid];
if (Map.Status == MapStatus.Available) if (Map.Status == MapStatus.Available)
{
// Maps need to be validated and pre-loaded before they can be accessed
new Thread(_ =>
{
var map = Map;
map.CacheRules();
Game.RunAfterTick(() =>
{
// Map may have changed in the meantime
if (map != Map)
return;
if (map.RuleStatus != MapRuleStatus.Invalid)
{ {
// Tell the server that we have the map // Tell the server that we have the map
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))); orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
@@ -593,6 +606,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash))
orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash))); orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash)));
} }
});
}).Start();
}
else if (Game.Settings.Game.AllowDownloading) else if (Game.Settings.Game.AllowDownloading)
Game.modData.MapCache.QueryRemoteMapDetails(new [] { uid }); Game.modData.MapCache.QueryRemoteMapDetails(new [] { uid });
} }

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var available = widget.GetOrNull("MAP_AVAILABLE"); var available = widget.GetOrNull("MAP_AVAILABLE");
if (available != null) if (available != null)
{ {
available.IsVisible = () => lobby.Map.Status == MapStatus.Available; available.IsVisible = () => lobby.Map.Status == MapStatus.Available && lobby.Map.RuleStatus == MapRuleStatus.Cached;
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW"); var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map; preview.Preview = () => lobby.Map;
@@ -46,6 +46,25 @@ namespace OpenRA.Mods.RA.Widgets.Logic
author.GetText = () => "Created by {0}".F(lobby.Map.Author); author.GetText = () => "Created by {0}".F(lobby.Map.Author);
} }
var invalid = widget.GetOrNull("MAP_INVALID");
if (invalid != null)
{
invalid.IsVisible = () => lobby.Map.Status == MapStatus.Available && lobby.Map.RuleStatus == MapRuleStatus.Invalid;
var preview = invalid.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map;
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
var title = invalid.GetOrNull<LabelWidget>("MAP_TITLE");
if (title != null)
title.GetText = () => lobby.Map.Title;
var type = invalid.GetOrNull<LabelWidget>("MAP_TYPE");
if (type != null)
type.GetText = () => lobby.Map.Type;
}
var download = widget.GetOrNull("MAP_DOWNLOADABLE"); var download = widget.GetOrNull("MAP_DOWNLOADABLE");
if (download != null) if (download != null)
{ {
@@ -76,7 +95,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var progress = widget.GetOrNull("MAP_PROGRESS"); var progress = widget.GetOrNull("MAP_PROGRESS");
if (progress != null) if (progress != null)
{ {
progress.IsVisible = () => lobby.Map.Status != MapStatus.Available && lobby.Map.Status != MapStatus.DownloadAvailable; progress.IsVisible = () => (lobby.Map.Status != MapStatus.Available || lobby.Map.RuleStatus == MapRuleStatus.Unknown) && lobby.Map.Status != MapStatus.DownloadAvailable;
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW"); var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
preview.Preview = () => lobby.Map; preview.Preview = () => lobby.Map;

View File

@@ -430,7 +430,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
var status = parent.Get<CheckboxWidget>("STATUS_CHECKBOX"); var status = parent.Get<CheckboxWidget>("STATUS_CHECKBOX");
status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null; status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null;
status.IsVisible = () => true; status.IsVisible = () => true;
status.IsDisabled = () => c.Bot != null || map.Status != MapStatus.Available; status.IsDisabled = () => c.Bot != null || map.Status != MapStatus.Available || map.RuleStatus != MapRuleStatus.Cached;
var state = orderManager.LocalClient.IsReady ? Session.ClientState.NotReady : Session.ClientState.Ready; var state = orderManager.LocalClient.IsReady ? Session.ClientState.NotReady : Session.ClientState.Ready;
status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state))); status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state)));

View File

@@ -38,6 +38,41 @@ Container@LOBBY_MAP_PREVIEW:
Height: 25 Height: 25
Font: Tiny Font: Tiny
Align: Center Align: Center
Container@MAP_INVALID:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
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_STATUS_A:
Y: 212
Width: PARENT_RIGHT
Height: 25
Font: Tiny
Align: Center
Text: This map is not compatible
Label@MAP_STATUS_B:
Y: 225
Width: PARENT_RIGHT
Height: 25
Font: Tiny
Align: Center
Text: with this version of OpenRA
Container@MAP_DOWNLOADABLE: Container@MAP_DOWNLOADABLE:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM

View File

@@ -13,8 +13,8 @@ Folders:
~^Content/cnc ~^Content/cnc
MapFolders: MapFolders:
./mods/cnc/maps ./mods/cnc/maps: System
~^maps/cnc ~^maps/cnc: User
Packages: Packages:
bluetib.mix bluetib.mix

View File

@@ -17,8 +17,8 @@ Folders:
~^Content/d2k/Music ~^Content/d2k/Music
MapFolders: MapFolders:
./mods/d2k/maps ./mods/d2k/maps: System
~^maps/d2k ~^maps/d2k: User
Packages: Packages:
SOUND.RS SOUND.RS

View File

@@ -47,5 +47,4 @@ Fonts:
TinyBold: TinyBold:
Font:FreeSansBold.ttf Font:FreeSansBold.ttf
Size:10 Size:10
Packages:
LobbyDefaults: LobbyDefaults:

View File

@@ -38,6 +38,41 @@ Container@LOBBY_MAP_PREVIEW:
Height: 25 Height: 25
Font: Tiny Font: Tiny
Align: Center Align: Center
Container@MAP_INVALID:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
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_STATUS_A:
Y:232
Width: PARENT_RIGHT
Height: 25
Font: Tiny
Align: Center
Text: This map is not compatible
Label@MAP_STATUS_B:
Y:245
Width: PARENT_RIGHT
Height: 25
Font: Tiny
Align: Center
Text: with this version of OpenRA
Container@MAP_DOWNLOADABLE: Container@MAP_DOWNLOADABLE:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM

View File

@@ -13,8 +13,8 @@ Folders:
~^Content/ra ~^Content/ra
MapFolders: MapFolders:
./mods/ra/maps ./mods/ra/maps: System
~^maps/ra ~^maps/ra: User
Packages: Packages:
~main.mix ~main.mix

View File

@@ -15,8 +15,8 @@ Folders:
./mods/ra/uibits ./mods/ra/uibits
MapFolders: MapFolders:
./mods/ts/maps ./mods/ts/maps: System
~^maps/ts ~^maps/ts: User
Packages: Packages:
# Tiberian Sun # Tiberian Sun