diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 022b72f56c..94cf43a2da 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -153,7 +153,7 @@ namespace OpenRA public class Map : IReadOnlyFileSystem { - public const int SupportedMapFormat = 10; + public const int SupportedMapFormat = 11; /// Defines the order of the fields in map.yaml static readonly MapField[] YamlFields = @@ -166,7 +166,7 @@ namespace OpenRA new MapField("MapSize"), new MapField("Bounds"), new MapField("Visibility"), - new MapField("Type"), + new MapField("Categories"), new MapField("LockPreview", required: false, ignoreIfValue: "False"), new MapField("Players", "PlayerDefinitions"), new MapField("Actors", "ActorDefinitions"), @@ -192,7 +192,7 @@ namespace OpenRA public bool LockPreview; public Rectangle Bounds; public MapVisibility Visibility = MapVisibility.Lobby; - public string Type = "Conquest"; + public string[] Categories = { "Conquest" }; public int2 MapSize { get; private set; } diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index d56c880548..ad6867002b 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -44,7 +44,7 @@ namespace OpenRA { public readonly string title; public readonly string author; - public readonly string map_type; + public readonly string[] categories; public readonly int players; public readonly Rectangle bounds; public readonly int[] spawnpoints = { }; @@ -64,7 +64,7 @@ namespace OpenRA IReadOnlyPackage parentPackage; public string Title { get; private set; } - public string Type { get; private set; } + public string[] Categories { get; private set; } public string Author { get; private set; } public string TileSet { get; private set; } public MapPlayers Players { get; private set; } @@ -116,7 +116,7 @@ namespace OpenRA Uid = uid; Title = "Unknown Map"; - Type = "Unknown"; + Categories = new[] { "Unknown" }; Author = "Unknown Author"; PlayerCount = 0; Bounds = Rectangle.Empty; @@ -153,8 +153,8 @@ namespace OpenRA if (yaml.TryGetValue("Title", out temp)) Title = temp.Value; - if (yaml.TryGetValue("Type", out temp)) - Type = temp.Value; + if (yaml.TryGetValue("Categories", out temp)) + Categories = FieldLoader.GetValue("Categories", temp.Value); if (yaml.TryGetValue("Tileset", out temp)) TileSet = temp.Value; if (yaml.TryGetValue("Author", out temp)) @@ -257,7 +257,7 @@ namespace OpenRA return false; // Other map types may have confusing settings or gameplay - if (Type != "Conquest") + if (!Categories.Contains("Conquest")) return false; // Maps with bots disabled confuse new players @@ -290,7 +290,7 @@ namespace OpenRA } Title = r.title; - Type = r.map_type; + Categories = r.categories; Author = r.author; PlayerCount = r.players; Bounds = r.bounds; diff --git a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs index f9b2eb381a..28a3d8b48f 100644 --- a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs +++ b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs @@ -10,6 +10,7 @@ #endregion using System; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.Common.Lint @@ -28,8 +29,8 @@ namespace OpenRA.Mods.Common.Lint if (map.Title == null) emitError("Map does not define a valid title."); - if (map.Type == null) - emitError("Map does not define a valid type."); + if (!map.Categories.Any()) + emitError("Map does not define any categories."); } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index d036ba4ed2..c6359d8970 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -994,6 +994,14 @@ namespace OpenRA.Mods.Common.UtilityCommands yaml.Nodes.Add(new MiniYamlNode("LockPreview", new MiniYaml("True"))); } + // Format 10 -> 11 replaced the single map type field with a list of categories + if (mapFormat < 11) + { + var type = yaml.Nodes.First(n => n.Key == "Type"); + yaml.Nodes.Add(new MiniYamlNode("Categories", type.Value)); + yaml.Nodes.Remove(type); + } + if (mapFormat < Map.SupportedMapFormat) { yaml.Nodes.First(n => n.Key == "MapFormat").Value = new MiniYaml(Map.SupportedMapFormat.ToString()); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs index c2aea8c6e5..4421369540 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -96,12 +96,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic var titleText = widget.Get("TITLE"); var titleTextNoTabs = widget.GetOrNull("TITLE_NO_TABS"); + var mapTitle = world.Map.Title; + var firstCategory = world.Map.Categories.FirstOrDefault(); + if (firstCategory != null) + mapTitle = firstCategory + ": " + mapTitle; + titleText.IsVisible = () => numTabs > 1 || (numTabs == 1 && titleTextNoTabs == null); - titleText.GetText = () => string.Concat(world.Map.Type, ": ", world.Map.Title); + titleText.GetText = () => mapTitle; if (titleTextNoTabs != null) { titleTextNoTabs.IsVisible = () => numTabs == 1; - titleTextNoTabs.GetText = () => string.Concat(world.Map.Type, ": ", world.Map.Title); + titleTextNoTabs.GetText = () => mapTitle; } var bg = widget.Get("BACKGROUND"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyMapPreviewLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyMapPreviewLogic.cs index 0adcd35579..95d7c1c985 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyMapPreviewLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Lobby/LobbyMapPreviewLogic.cs @@ -9,6 +9,7 @@ */ #endregion +using System.Linq; using OpenRA.Network; using OpenRA.Widgets; @@ -43,7 +44,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic var typeLabel = available.GetOrNull("MAP_TYPE"); if (typeLabel != null) - typeLabel.GetText = () => lobby.Map.Type; + { + var type = new CachedTransform(m => lobby.Map.Categories.FirstOrDefault() ?? ""); + typeLabel.GetText = () => type.Update(lobby.Map); + } var authorLabel = available.GetOrNull("MAP_AUTHOR"); if (authorLabel != null) @@ -65,13 +69,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi); preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map); - var title = invalid.GetOrNull("MAP_TITLE"); - if (title != null) - title.GetText = () => lobby.Map.Title; + var titleLabel = invalid.GetOrNull("MAP_TITLE"); + if (titleLabel != null) + titleLabel.GetText = () => lobby.Map.Title; - var type = invalid.GetOrNull("MAP_TYPE"); - if (type != null) - type.GetText = () => lobby.Map.Type; + var typeLabel = invalid.GetOrNull("MAP_TYPE"); + if (typeLabel != null) + { + var type = new CachedTransform(m => lobby.Map.Categories.FirstOrDefault() ?? ""); + typeLabel.GetText = () => type.Update(lobby.Map); + } } var download = widget.GetOrNull("MAP_DOWNLOADABLE"); @@ -84,17 +91,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi); preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map); - var title = download.GetOrNull("MAP_TITLE"); - if (title != null) - title.GetText = () => lobby.Map.Title; + var titleLabel = download.GetOrNull("MAP_TITLE"); + if (titleLabel != null) + titleLabel.GetText = () => lobby.Map.Title; - var type = download.GetOrNull("MAP_TYPE"); - if (type != null) - type.GetText = () => lobby.Map.Type; + var typeLabel = download.GetOrNull("MAP_TYPE"); + if (typeLabel != null) + { + var type = new CachedTransform(m => lobby.Map.Categories.FirstOrDefault() ?? ""); + typeLabel.GetText = () => type.Update(lobby.Map); + } - var author = download.GetOrNull("MAP_AUTHOR"); - if (author != null) - author.GetText = () => "Created by {0}".F(lobby.Map.Author); + var authorLabel = download.GetOrNull("MAP_AUTHOR"); + if (authorLabel != null) + authorLabel.GetText = () => "Created by {0}".F(lobby.Map.Author); var install = download.GetOrNull("MAP_INSTALL"); if (install != null) @@ -116,13 +126,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi); preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map); - var title = progress.GetOrNull("MAP_TITLE"); - if (title != null) - title.GetText = () => lobby.Map.Title; + var titleLabel = progress.GetOrNull("MAP_TITLE"); + if (titleLabel != null) + titleLabel.GetText = () => lobby.Map.Title; - var type = progress.GetOrNull("MAP_TYPE"); - if (type != null) - type.GetText = () => lobby.Map.Type; + var typeLabel = progress.GetOrNull("MAP_TYPE"); + if (typeLabel != null) + if (typeLabel != null) + { + var type = new CachedTransform(m => lobby.Map.Categories.FirstOrDefault() ?? ""); + typeLabel.GetText = () => type.Update(lobby.Map); + } var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING"); if (statusSearching != null) diff --git a/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs index a1ce1dd4a8..9d84fef4be 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic string selectedUid; Action onSelect; - string gameMode; + string category; string mapFilter; [ObjectCreator.UseCtor] @@ -170,30 +170,43 @@ namespace OpenRA.Mods.Common.Widgets.Logic { if (gameModeDropdown != null) { - var gameModes = tabMaps[tab] - .GroupBy(m => m.Type) - .Select(g => Pair.New(g.Key, g.Count())).ToList(); + var categoryDict = new Dictionary(); + foreach (var map in tabMaps[tab]) + { + foreach (var category in map.Categories) + { + var count = 0; + categoryDict.TryGetValue(category, out count); + categoryDict[category] = count + 1; + } + } + + // Order categories alphabetically + var categories = categoryDict + .Select(kv => Pair.New(kv.Key, kv.Value)) + .OrderBy(p => p.First) + .ToList(); // 'all game types' extra item - gameModes.Insert(0, Pair.New(null as string, tabMaps[tab].Count())); + categories.Insert(0, Pair.New(null as string, tabMaps[tab].Count())); - Func, string> showItem = x => "{0} ({1})".F(x.First ?? "All Game Types", x.Second); + Func, string> showItem = x => "{0} ({1})".F(x.First ?? "All Maps", x.Second); Func, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, template) => { var item = ScrollItemWidget.Setup(template, - () => gameMode == ii.First, - () => { gameMode = ii.First; EnumerateMaps(tab, itemTemplate); }); + () => category == ii.First, + () => { category = ii.First; EnumerateMaps(tab, itemTemplate); }); item.Get("LABEL").GetText = () => showItem(ii); return item; }; gameModeDropdown.OnClick = () => - gameModeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, gameModes, setupItem); + gameModeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, categories, setupItem); gameModeDropdown.GetText = () => { - var item = gameModes.FirstOrDefault(m => m.First == gameMode); + var item = categories.FirstOrDefault(m => m.First == category); if (item == default(Pair)) item.First = "No matches"; @@ -209,7 +222,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic playerCountFilter = -1; var maps = tabMaps[tab] - .Where(m => gameMode == null || m.Type == gameMode) + .Where(m => category == null || m.Categories.Contains(category)) .Where(m => mapFilter == null || (m.Title != null && m.Title.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0) || (m.Author != null && m.Author.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0) || @@ -251,7 +264,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic var detailsWidget = item.GetOrNull("DETAILS"); if (detailsWidget != null) - detailsWidget.GetText = () => "{0} ({1} players)".F(preview.Type, preview.PlayerCount); + { + var type = preview.Categories.FirstOrDefault(); + var details = ""; + if (type != null) + details = type + " "; + + details += "({0} players)".F(preview.PlayerCount); + detailsWidget.GetText = () => details; + } var authorWidget = item.GetOrNull("AUTHOR"); if (authorWidget != null) diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs index 6a627779a4..003af771f0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs @@ -83,7 +83,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic var type = panel.GetOrNull("MAP_TYPE"); if (type != null) - type.GetText = () => selectedReplay.GameInfo.MapPreview.Type; + { + var mapType = new CachedTransform(m => m.Categories.FirstOrDefault() ?? ""); + type.GetText = () => mapType.Update(selectedReplay.GameInfo.MapPreview); + } panel.Get("DURATION").GetText = () => WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds);