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);