diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 9fc1b3a892..5207b1327d 100755 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -27,6 +27,9 @@ namespace OpenRA // 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 public class RemoteMapData { @@ -58,6 +61,8 @@ namespace OpenRA public MapStatus Status { get; private set; } public MapClassification Class { get; private set; } + public MapRuleStatus RuleStatus { get; private set; } + Download download; public long DownloadBytes { get; private set; } public int DownloadPercentage { get; private set; } @@ -227,5 +232,22 @@ namespace OpenRA download.Cancel(); 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; + } + } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index aa17d24a48..65c7522428 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using System.Threading; using OpenRA.Graphics; using OpenRA.Network; using OpenRA.Traits; @@ -170,7 +171,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (slotsButton != null) { 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().Select(t => t.Name); slotsButton.OnMouseDown = _ => @@ -264,7 +265,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic optionsBin.IsVisible = () => panel == PanelType.Options; var optionsButton = lobby.Get("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.OnClick = () => panel = (panel == PanelType.Options) ? PanelType.Players : PanelType.Options; @@ -278,7 +279,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var startGameButton = lobby.GetOrNull("START_GAME_BUTTON"); 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); startGameButton.OnClick = () => { @@ -554,16 +555,31 @@ namespace OpenRA.Mods.RA.Widgets.Logic return; Map = Game.modData.MapCache[uid]; - if (Map.Status == MapStatus.Available) { - // Tell the server that we have the map - orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))); + // 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; - // Restore default starting cash if the last map set it to something invalid - var pri = modRules.Actors["player"].Traits.Get(); - if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) - orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash))); + if (map.RuleStatus != MapRuleStatus.Invalid) + { + // Tell the server that we have the map + orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))); + + // Restore default starting cash if the last map set it to something invalid + var pri = modRules.Actors["player"].Traits.Get(); + if (!Map.Map.Options.StartingCash.HasValue && !pri.SelectableCash.Contains(orderManager.LobbyInfo.GlobalSettings.StartingCash)) + orderManager.IssueOrder(Order.Command("startingcash {0}".F(pri.DefaultCash))); + } + }); + }).Start(); } else if (Game.Settings.Game.AllowDownloading) Game.modData.MapCache.QueryRemoteMapDetails(new [] { uid }); diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyMapPreviewLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyMapPreviewLogic.cs index a7562a511d..42181fceeb 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyMapPreviewLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyMapPreviewLogic.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var available = widget.GetOrNull("MAP_AVAILABLE"); 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("MAP_PREVIEW"); preview.Preview = () => lobby.Map; @@ -46,6 +46,25 @@ namespace OpenRA.Mods.RA.Widgets.Logic 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("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("MAP_TITLE"); + if (title != null) + title.GetText = () => lobby.Map.Title; + + var type = invalid.GetOrNull("MAP_TYPE"); + if (type != null) + type.GetText = () => lobby.Map.Type; + } + var download = widget.GetOrNull("MAP_DOWNLOADABLE"); if (download != null) { @@ -76,7 +95,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var progress = widget.GetOrNull("MAP_PROGRESS"); 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("MAP_PREVIEW"); preview.Preview = () => lobby.Map; diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index cba373dcce..6a36afcdca 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -430,7 +430,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var status = parent.Get("STATUS_CHECKBOX"); status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null; 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; status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state))); diff --git a/mods/cnc/chrome/lobby-mappreview.yaml b/mods/cnc/chrome/lobby-mappreview.yaml index e23c093db7..a6de873ac6 100644 --- a/mods/cnc/chrome/lobby-mappreview.yaml +++ b/mods/cnc/chrome/lobby-mappreview.yaml @@ -38,6 +38,41 @@ Container@LOBBY_MAP_PREVIEW: Height: 25 Font: Tiny 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: Width: PARENT_RIGHT Height: PARENT_BOTTOM diff --git a/mods/ra/chrome/lobby-mappreview.yaml b/mods/ra/chrome/lobby-mappreview.yaml index dcffed4b27..7990a72bef 100644 --- a/mods/ra/chrome/lobby-mappreview.yaml +++ b/mods/ra/chrome/lobby-mappreview.yaml @@ -38,6 +38,41 @@ Container@LOBBY_MAP_PREVIEW: Height: 25 Font: Tiny 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: Width: PARENT_RIGHT Height: PARENT_BOTTOM