From 43a3d42d3111ce2a27c68d0c1b32577c44d2c1e9 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 9 Mar 2016 18:27:20 +0000 Subject: [PATCH 1/5] Change LoadMapSettings to accept a Ruleset. --- .../ServerTraits/LobbyCommands.cs | 18 +++++++++--------- .../ServerTraits/LobbySettingsNotification.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 39ac629eb5..9eeff1dd89 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -1013,28 +1013,28 @@ namespace OpenRA.Mods.Common.Server }; } - public static void LoadMapSettings(Session.Global gs, Map map) + public static void LoadMapSettings(Session.Global gs, Ruleset rules) { - var devMode = map.Rules.Actors["player"].TraitInfo(); + var devMode = rules.Actors["player"].TraitInfo(); gs.AllowCheats = devMode.Enabled; - var crateSpawner = map.Rules.Actors["world"].TraitInfoOrDefault(); + var crateSpawner = rules.Actors["world"].TraitInfoOrDefault(); gs.Crates = crateSpawner != null && crateSpawner.Enabled; - var shroud = map.Rules.Actors["player"].TraitInfo(); + var shroud = rules.Actors["player"].TraitInfo(); gs.Fog = shroud.FogEnabled; gs.Shroud = !shroud.ExploredMapEnabled; - var resources = map.Rules.Actors["player"].TraitInfo(); + var resources = rules.Actors["player"].TraitInfo(); gs.StartingCash = resources.DefaultCash; - var startingUnits = map.Rules.Actors["world"].TraitInfoOrDefault(); + var startingUnits = rules.Actors["world"].TraitInfoOrDefault(); gs.StartingUnitsClass = startingUnits == null ? "none" : startingUnits.StartingUnitsClass; - var mapBuildRadius = map.Rules.Actors["world"].TraitInfoOrDefault(); + var mapBuildRadius = rules.Actors["world"].TraitInfoOrDefault(); gs.AllyBuildRadius = mapBuildRadius != null && mapBuildRadius.AllyBuildRadiusEnabled; - var mapOptions = map.Rules.Actors["world"].TraitInfo(); + var mapOptions = rules.Actors["world"].TraitInfo(); gs.ShortGame = mapOptions.ShortGameEnabled; gs.TechLevel = mapOptions.TechLevel; gs.Difficulty = mapOptions.Difficulty ?? mapOptions.Difficulties.FirstOrDefault(); @@ -1050,7 +1050,7 @@ namespace OpenRA.Mods.Common.Server .Where(s => s != null) .ToDictionary(s => s.PlayerReference, s => s); - LoadMapSettings(server.LobbyInfo.GlobalSettings, server.Map); + LoadMapSettings(server.LobbyInfo.GlobalSettings, server.Map.Rules); } static HSLColor SanitizePlayerColor(S server, HSLColor askedColor, int playerIndex, Connection connectionToEcho = null) diff --git a/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs b/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs index 963ffcd99c..c6a34d23ba 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbySettingsNotification.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.Server return; var defaults = new Session.Global(); - LobbyCommands.LoadMapSettings(defaults, server.Map); + LobbyCommands.LoadMapSettings(defaults, server.Map.Rules); if (server.LobbyInfo.GlobalSettings.AllowCheats != defaults.AllowCheats) server.SendOrderTo(conn, "Message", "Allow Cheats: {0}".F(server.LobbyInfo.GlobalSettings.AllowCheats)); From be5eee0227ef81f25453e4572e8bce0ae12bdc1f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 9 Mar 2016 19:17:14 +0000 Subject: [PATCH 2/5] Allow rules to be constructed from a MapPreview. --- OpenRA.Game/GameRules/Ruleset.cs | 20 ++++---- OpenRA.Game/Map/Map.cs | 3 +- OpenRA.Game/Map/MapCache.cs | 4 +- OpenRA.Game/Map/MapPreview.cs | 88 ++++++++++++++++++++++++++++++-- 4 files changed, 98 insertions(+), 17 deletions(-) diff --git a/OpenRA.Game/GameRules/Ruleset.cs b/OpenRA.Game/GameRules/Ruleset.cs index 0f7e299076..ff1be28daf 100644 --- a/OpenRA.Game/GameRules/Ruleset.cs +++ b/OpenRA.Game/GameRules/Ruleset.cs @@ -148,7 +148,9 @@ namespace OpenRA return new Ruleset(dr.Actors, dr.Weapons, dr.Voices, dr.Notifications, dr.Music, ts, sequences); } - public static Ruleset LoadFromMap(ModData modData, Map map) + public static Ruleset Load(ModData modData, IReadOnlyFileSystem fileSystem, string tileSet, + MiniYaml mapRules, MiniYaml mapWeapons, MiniYaml mapVoices, MiniYaml mapNotifications, + MiniYaml mapMusic, MiniYaml mapSequences) { var m = modData.Manifest; var dr = modData.DefaultRules; @@ -156,27 +158,27 @@ namespace OpenRA Ruleset ruleset = null; Action f = () => { - var actors = MergeOrDefault("Manifest,Rules", map, m.Rules, map.RuleDefinitions, dr.Actors, + var actors = MergeOrDefault("Rules", fileSystem, m.Rules, mapRules, dr.Actors, k => new ActorInfo(modData.ObjectCreator, k.Key.ToLowerInvariant(), k.Value)); - var weapons = MergeOrDefault("Manifest,Weapons", map, m.Weapons, map.WeaponDefinitions, dr.Weapons, + var weapons = MergeOrDefault("Weapons", fileSystem, m.Weapons, mapWeapons, dr.Weapons, k => new WeaponInfo(k.Key.ToLowerInvariant(), k.Value)); - var voices = MergeOrDefault("Manifest,Voices", map, m.Voices, map.VoiceDefinitions, dr.Voices, + var voices = MergeOrDefault("Voices", fileSystem, m.Voices, mapVoices, dr.Voices, k => new SoundInfo(k.Value)); - var notifications = MergeOrDefault("Manifest,Notifications", map, m.Notifications, map.NotificationDefinitions, dr.Notifications, + var notifications = MergeOrDefault("Notifications", fileSystem, m.Notifications, mapNotifications, dr.Notifications, k => new SoundInfo(k.Value)); - var music = MergeOrDefault("Manifest,Music", map, m.Music, map.NotificationDefinitions, dr.Music, + var music = MergeOrDefault("Music", fileSystem, m.Music, mapMusic, dr.Music, k => new MusicInfo(k.Key, k.Value)); // TODO: Add support for merging custom tileset modifications - var ts = modData.DefaultTileSets[map.Tileset]; + var ts = modData.DefaultTileSets[tileSet]; // TODO: Top-level dictionary should be moved into the Ruleset instead of in its own object - var sequences = map.SequenceDefinitions == null ? modData.DefaultSequences[map.Tileset] : - new SequenceProvider(map, modData, ts, map.SequenceDefinitions); + var sequences = mapSequences == null ? modData.DefaultSequences[tileSet] : + new SequenceProvider(fileSystem, modData, ts, mapSequences); // TODO: Add support for custom voxel sequences ruleset = new Ruleset(actors, weapons, voices, notifications, music, ts, sequences); diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 6fd195351e..f0b1293c1d 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -296,7 +296,8 @@ namespace OpenRA { try { - return Ruleset.LoadFromMap(modData, this); + return Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions, + VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions); } catch (Exception e) { diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index a2301bdb1a..a05bd954f3 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -25,7 +25,7 @@ namespace OpenRA { public sealed class MapCache : IEnumerable, IDisposable { - public static readonly MapPreview UnknownMap = new MapPreview(null, MapGridType.Rectangular, null); + public static readonly MapPreview UnknownMap = new MapPreview(null, null, MapGridType.Rectangular, null); public readonly IReadOnlyDictionary MapLocations; readonly Cache previews; @@ -41,7 +41,7 @@ namespace OpenRA this.modData = modData; var gridType = Exts.Lazy(() => modData.Manifest.Get().Type); - previews = new Cache(uid => new MapPreview(uid, gridType.Value, this)); + previews = new Cache(uid => new MapPreview(modData, uid, gridType.Value, this)); sheetBuilder = new SheetBuilder(SheetType.BGRA); // Enumerate map directories diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index cbc0e79612..f6142301d5 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -53,10 +53,11 @@ namespace OpenRA public readonly bool downloading; } - public class MapPreview : IDisposable + public class MapPreview : IDisposable, IReadOnlyFileSystem { static readonly CPos[] NoSpawns = new CPos[] { }; MapCache cache; + ModData modData; public readonly string Uid; public IReadOnlyPackage Package { get; private set; } @@ -65,6 +66,8 @@ namespace OpenRA public string Title { get; private set; } public string Type { 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; } @@ -75,6 +78,10 @@ namespace OpenRA public MapVisibility Visibility { get; private set; } public bool SuitableForInitialMap { get; private set; } + Lazy rules; + public Ruleset Rules { get { return rules != null ? rules.Value : null; } } + public bool InvalidCustomRules { get; private set; } + Download download; public long DownloadBytes { get; private set; } public int DownloadPercentage { get; private set; } @@ -101,9 +108,11 @@ namespace OpenRA generatingMinimap = false; } - public MapPreview(string uid, MapGridType gridType, MapCache cache) + public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache) { this.cache = cache; + this.modData = modData; + Uid = uid; Title = "Unknown Map"; Type = "Unknown"; @@ -145,6 +154,8 @@ namespace OpenRA Title = temp.Value; if (yaml.TryGetValue("Type", out temp)) Type = temp.Value; + if (yaml.TryGetValue("Tileset", out temp)) + TileSet = temp.Value; if (yaml.TryGetValue("Author", out temp)) Author = temp.Value; if (yaml.TryGetValue("Bounds", out temp)) @@ -188,9 +199,9 @@ namespace OpenRA MiniYaml playerDefinitions; if (yaml.TryGetValue("Players", out playerDefinitions)) { - var players = new MapPlayers(playerDefinitions.Nodes).Players; - PlayerCount = players.Count(x => x.Value.Playable); - SuitableForInitialMap = EvaluateUserFriendliness(players); + Players = new MapPlayers(playerDefinitions.Nodes); + PlayerCount = Players.Players.Count(x => x.Value.Playable); + SuitableForInitialMap = EvaluateUserFriendliness(Players.Players); } } catch (Exception) @@ -198,11 +209,41 @@ namespace OpenRA Status = MapStatus.Unavailable; } + rules = Exts.Lazy(() => + { + try + { + var ruleDefinitions = LoadRuleSection(yaml, "Rules"); + var weaponDefinitions = LoadRuleSection(yaml, "Weapons"); + var voiceDefinitions = LoadRuleSection(yaml, "Voices"); + var musicDefinitions = LoadRuleSection(yaml, "Music"); + var notificationDefinitions = LoadRuleSection(yaml, "Notifications"); + var sequenceDefinitions = LoadRuleSection(yaml, "Sequences"); + return Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions, + voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions); + } + catch + { + InvalidCustomRules = true; + } + + return Ruleset.LoadDefaultsForTileSet(modData, TileSet); + }); + if (p.Contains("map.png")) using (var dataStream = p.GetStream("map.png")) Preview = new Bitmap(dataStream); } + MiniYaml LoadRuleSection(Dictionary yaml, string section) + { + MiniYaml node; + if (!yaml.TryGetValue(section, out node)) + return null; + + return node; + } + bool EvaluateUserFriendliness(Dictionary players) { if (Status != MapStatus.Available || !Visibility.HasFlag(MapVisibility.Lobby)) @@ -367,5 +408,42 @@ namespace OpenRA if (deleteFromPackage != null) deleteFromPackage.Delete(Package.Name); } + + Stream IReadOnlyFileSystem.Open(string filename) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|") && Package.Contains(filename)) + return Package.GetStream(filename); + + return modData.DefaultFileSystem.Open(filename); + } + + bool IReadOnlyFileSystem.TryGetPackageContaining(string path, out IReadOnlyPackage package, out string filename) + { + // Packages aren't supported inside maps + return modData.DefaultFileSystem.TryGetPackageContaining(path, out package, out filename); + } + + bool IReadOnlyFileSystem.TryOpen(string filename, out Stream s) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|")) + { + s = Package.GetStream(filename); + if (s != null) + return true; + } + + return modData.DefaultFileSystem.TryOpen(filename, out s); + } + + bool IReadOnlyFileSystem.Exists(string filename) + { + // Explicit package paths never refer to a map + if (!filename.Contains("|") && Package.Contains(filename)) + return true; + + return modData.DefaultFileSystem.Exists(filename); + } } } From 6c319d53ddb53b0b6e740083665c399497659319 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 9 Mar 2016 19:17:31 +0000 Subject: [PATCH 3/5] Remove an obsolete comment from Map. --- OpenRA.Game/Map/Map.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index f0b1293c1d..74042958d4 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -1192,7 +1192,6 @@ namespace OpenRA return FindTilesInAnnulus(center, 0, maxRange, allowOutsideBounds); } - // Placeholders for future implementation public Stream Open(string filename) { // Explicit package paths never refer to a map From 1ba7a5e59eb928b686346e33a3dbeb8fa5ff87f2 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 9 Mar 2016 19:17:45 +0000 Subject: [PATCH 4/5] Use MapPreview on the server. --- OpenRA.Game/Server/Server.cs | 9 +++---- .../ServerTraits/LobbyCommands.cs | 25 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index d46e7b2522..a51f487910 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -51,8 +51,7 @@ namespace OpenRA.Server public List TempBans = new List(); // Managed by LobbyCommands - public Map Map; - public MapPlayers MapPlayers; + public MapPreview Map; readonly int randomSeed; readonly TcpListener listener; @@ -323,7 +322,7 @@ namespace OpenRA.Server } if (client.Slot != null) - SyncClientToPlayerReference(client, MapPlayers.Players[client.Slot]); + SyncClientToPlayerReference(client, Map.Players.Players[client.Slot]); else client.Color = HSLColor.FromRGB(255, 255, 255); @@ -392,12 +391,12 @@ namespace OpenRA.Server SendOrderTo(newConn, "Message", motd); } - if (Map.RuleDefinitions != null && !LobbyInfo.IsSinglePlayer) + if (Map.Rules != ModData.DefaultRules && !LobbyInfo.IsSinglePlayer) SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); if (Settings.DisableSinglePlayer) SendOrderTo(newConn, "Message", "Singleplayer games have been disabled on this server."); - else if (MapPlayers.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots)) + else if (Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots)) SendOrderTo(newConn, "Message", "Bots have been disabled on this map."); if (handshake.Mod == "{DEV_VERSION}") diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index 9eeff1dd89..427e923674 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Server client.SpawnPoint = 0; client.Slot = s; - S.SyncClientToPlayerReference(client, server.MapPlayers.Players[s]); + S.SyncClientToPlayerReference(client, server.Map.Players.Players[s]); if (!slot.LockColor) client.PreferredColor = client.Color = SanitizePlayerColor(server, client.Color, client.Index, conn); @@ -311,7 +311,7 @@ namespace OpenRA.Mods.Common.Server var tileset = server.Map.Rules.TileSet; var terrainColors = tileset.TerrainInfo.Where(ti => ti.RestrictPlayerColor).Select(ti => ti.Color); var playerColors = server.LobbyInfo.Clients.Select(c => c.Color.RGB) - .Concat(server.MapPlayers.Players.Values.Select(p => p.Color.RGB)); + .Concat(server.Map.Players.Players.Values.Select(p => p.Color.RGB)); bot.Color = bot.PreferredColor = validator.RandomValidColor(server.Random, terrainColors, playerColors); server.LobbyInfo.Clients.Add(bot); @@ -323,7 +323,7 @@ namespace OpenRA.Mods.Common.Server bot.Bot = botType; } - S.SyncClientToPlayerReference(bot, server.MapPlayers.Players[parts[0]]); + S.SyncClientToPlayerReference(bot, server.Map.Players.Players[parts[0]]); server.SyncLobbyClients(); server.SyncLobbySlots(); return true; @@ -370,9 +370,9 @@ namespace OpenRA.Mods.Common.Server if (c.Slot != null) { // Remove Bot from slot if slot forbids bots - if (c.Bot != null && !server.MapPlayers.Players[c.Slot].AllowBots) + if (c.Bot != null && !server.Map.Players.Players[c.Slot].AllowBots) server.LobbyInfo.Clients.Remove(c); - S.SyncClientToPlayerReference(c, server.MapPlayers.Players[c.Slot]); + S.SyncClientToPlayerReference(c, server.Map.Players.Players[c.Slot]); } else if (c.Bot != null) server.LobbyInfo.Clients.Remove(c); @@ -387,12 +387,12 @@ namespace OpenRA.Mods.Common.Server server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title)); - if (server.Map.RuleDefinitions != null) + if (server.Map.Rules.Actors != server.ModData.DefaultRules.Actors) server.SendMessage("This map contains custom rules. Game experience may change."); if (server.Settings.DisableSinglePlayer) server.SendMessage("Singleplayer games have been disabled on this server."); - else if (server.MapPlayers.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."); return true; @@ -875,7 +875,7 @@ namespace OpenRA.Mods.Common.Server int spawnPoint; if (!Exts.TryParseIntegerInvariant(parts[1], out spawnPoint) - || spawnPoint < 0 || spawnPoint > server.Map.SpawnPoints.Value.Length) + || spawnPoint < 0 || spawnPoint > server.Map.SpawnPoints.Length) { Log.Write("server", "Invalid spawn point: {0}", parts[1]); return true; @@ -1042,10 +1042,9 @@ namespace OpenRA.Mods.Common.Server static void LoadMap(S server) { - server.Map = new Map(server.ModData, server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map].Package); + server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map]; - server.MapPlayers = new MapPlayers(server.Map.PlayerDefinitions); - server.LobbyInfo.Slots = server.MapPlayers.Players + server.LobbyInfo.Slots = server.Map.Players.Players .Select(p => MakeSlotFromPlayerReference(p.Value)) .Where(s => s != null) .ToDictionary(s => s.PlayerReference, s => s); @@ -1067,7 +1066,7 @@ namespace OpenRA.Mods.Common.Server var tileset = server.Map.Rules.TileSet; var terrainColors = tileset.TerrainInfo.Where(ti => ti.RestrictPlayerColor).Select(ti => ti.Color).ToList(); var playerColors = server.LobbyInfo.Clients.Where(c => c.Index != playerIndex).Select(c => c.Color.RGB) - .Concat(server.MapPlayers.Players.Values.Select(p => p.Color.RGB)).ToList(); + .Concat(server.Map.Players.Players.Values.Select(p => p.Color.RGB)).ToList(); return validator.MakeValid(askColor.RGB, server.Random, terrainColors, playerColors, onError); } @@ -1086,7 +1085,7 @@ namespace OpenRA.Mods.Common.Server if (slot == null) return null; - return server.MapPlayers.Players[slot.PlayerReference]; + return server.Map.Players.Players[slot.PlayerReference]; } } } From 324ef2e073284cd9384c57fb57cb2ee3c40c1780 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 9 Mar 2016 19:27:04 +0000 Subject: [PATCH 5/5] Remove Map usage from MissionBrowerLogic. --- .../Widgets/Logic/MissionBrowserLogic.cs | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs index 484fc132f6..efece12fb8 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MissionBrowserLogic.cs @@ -43,11 +43,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ScrollPanelWidget missionList; readonly ScrollItemWidget headerTemplate; readonly ScrollItemWidget template; - readonly Cache mapCache; - - MapPreview selectedMapPreview; - Map selectedMap; + MapPreview selectedMap; PlayingVideo playingVideo; string difficulty; @@ -56,7 +53,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public MissionBrowserLogic(Widget widget, ModData modData, World world, Action onStart, Action onExit) { - mapCache = new Cache(p => new Map(modData, p.Package)); this.modData = modData; this.onStart = onStart; @@ -67,12 +63,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic var title = widget.GetOrNull("MISSIONBROWSER_TITLE"); if (title != null) - title.GetText = () => playingVideo != PlayingVideo.None ? selectedMapPreview.Title : title.Text; + title.GetText = () => playingVideo != PlayingVideo.None ? selectedMap.Title : title.Text; - widget.Get("MISSION_INFO").IsVisible = () => selectedMapPreview != null; + widget.Get("MISSION_INFO").IsVisible = () => selectedMap != null; var previewWidget = widget.Get("MISSION_PREVIEW"); - previewWidget.Preview = () => selectedMapPreview; + previewWidget.Preview = () => selectedMap; previewWidget.IsVisible = () => playingVideo == PlayingVideo.None; videoPlayer = widget.Get("MISSION_VIDEO"); @@ -132,11 +128,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (allPreviews.Any()) SelectMap(allPreviews.First()); - // Preload map and preview data to reduce jank + // Preload map preview and rules to reduce jank new Thread(() => { foreach (var p in allPreviews) - modData.MapCache[mapCache[p].Uid].GetMinimap(); + { + p.GetMinimap(); + var unused = p.Rules; + } }).Start(); var startButton = widget.Get("STARTGAME_BUTTON"); @@ -163,7 +162,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var preview = p; var item = ScrollItemWidget.Setup(template, - () => selectedMapPreview != null && selectedMapPreview.Uid == preview.Uid, + () => selectedMap != null && selectedMap.Uid == preview.Uid, () => SelectMap(preview), StartMissionClicked); @@ -174,8 +173,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic void SelectMap(MapPreview preview) { - selectedMap = mapCache[preview]; - selectedMapPreview = preview; + selectedMap = preview; // Cache the rules on a background thread to avoid jank var difficultyDisabled = true; @@ -189,17 +187,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic var infoVideoVisible = false; var infoVideoDisabled = true; - var map = selectedMap; new Thread(() => { - map.PreloadRules(); - var mapOptions = map.Rules.Actors["world"].TraitInfo(); + var mapOptions = preview.Rules.Actors["world"].TraitInfo(); difficulty = mapOptions.Difficulty ?? mapOptions.Difficulties.FirstOrDefault(); difficulties = mapOptions.Difficulties; difficultyDisabled = mapOptions.DifficultyLocked || mapOptions.Difficulties.Length <= 1; - var missionData = map.Rules.Actors["world"].TraitInfoOrDefault(); + var missionData = preview.Rules.Actors["world"].TraitInfoOrDefault(); if (missionData != null) { briefingVideo = missionData.BriefingVideo; @@ -214,7 +210,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var height = descriptionFont.Measure(briefing).Y; Game.RunAfterTick(() => { - if (map == selectedMap) + if (preview == selectedMap) { description.Text = briefing; description.Bounds.Height = height; @@ -348,11 +344,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic PlayVideo(fsPlayer, missionData.StartVideo, PlayingVideo.GameStart, () => { StopVideo(fsPlayer); - Game.CreateAndStartLocalServer(selectedMapPreview.Uid, orders, onStart); + Game.CreateAndStartLocalServer(selectedMap.Uid, orders, onStart); }); } else - Game.CreateAndStartLocalServer(selectedMapPreview.Uid, orders, onStart); + Game.CreateAndStartLocalServer(selectedMap.Uid, orders, onStart); } class DropDownOption