Show the server map pool in the client map chooser.

Maps that aren't installed are queried from the resource center.
This commit is contained in:
Paul Chote
2023-10-31 19:20:00 +00:00
committed by Gustas
parent 72646fc7ff
commit 2e5ef7f059
12 changed files with 188 additions and 29 deletions

View File

@@ -39,6 +39,9 @@ namespace OpenRA.Network
public string ServerError = null; public string ServerError = null;
public bool AuthenticationFailed = false; public bool AuthenticationFailed = false;
// The default null means "no map restriction" while an empty set means "all maps restricted"
public HashSet<string> ServerMapPool = null;
public int NetFrameNumber { get; private set; } public int NetFrameNumber { get; private set; }
public int LocalFrameNumber; public int LocalFrameNumber;

View File

@@ -383,6 +383,12 @@ namespace OpenRA.Network
break; break;
} }
case "SyncMapPool":
{
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
break;
}
default: default:
{ {
if (world == null) if (world == null)

View File

@@ -1396,6 +1396,9 @@ namespace OpenRA.Mods.Common.Server
{ {
lock (server.LobbyInfo) lock (server.LobbyInfo)
{ {
if (server.MapPool != null)
server.SendOrderTo(conn, "SyncMapPool", FieldSaver.FormatValue(server.MapPool));
var client = server.GetClient(conn); var client = server.GetClient(conn);
// Validate whether color is allowed and get an alternative if it isn't // Validate whether color is allowed and get an alternative if it isn't

View File

@@ -236,7 +236,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var onSelect = new Action<string>(uid => var onSelect = new Action<string>(uid =>
{ {
// Don't select the same map again, and handle map becoming unavailable // Don't select the same map again, and handle map becoming unavailable
if (uid == map.Uid || modData.MapCache[uid].Status != MapStatus.Available) var status = modData.MapCache[uid].Status;
if (uid == map.Uid || (status != MapStatus.Available && status != MapStatus.DownloadAvailable))
return; return;
orderManager.IssueOrder(Order.Command("map " + uid)); orderManager.IssueOrder(Order.Command("map " + uid));
@@ -250,6 +251,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{ {
{ "initialMap", modData.MapCache.PickLastModifiedMap(MapVisibility.Lobby) ?? map.Uid }, { "initialMap", modData.MapCache.PickLastModifiedMap(MapVisibility.Lobby) ?? map.Uid },
{ "remoteMapPool", orderManager.ServerMapPool },
{ "initialTab", MapClassification.System }, { "initialTab", MapClassification.System },
{ "onExit", modData.MapCache.UpdateMaps }, { "onExit", modData.MapCache.UpdateMaps },
{ "onSelect", Game.IsHost ? onSelect : null }, { "onSelect", Game.IsHost ? onSelect : null },

View File

@@ -204,6 +204,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{ {
{ "initialMap", null }, { "initialMap", null },
{ "remoteMapPool", null },
{ "initialTab", MapClassification.User }, { "initialTab", MapClassification.User },
{ "onExit", () => SwitchMenu(MenuType.MapEditor) }, { "onExit", () => SwitchMenu(MenuType.MapEditor) },
{ "onSelect", onSelect }, { "onSelect", onSelect },

View File

@@ -42,6 +42,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[TranslationReference] [TranslationReference]
const string MapSizeSmall = "label-map-size-small"; const string MapSizeSmall = "label-map-size-small";
[TranslationReference("count")]
const string MapSearchingCount = "label-map-searching-count";
[TranslationReference("count")]
const string MapUnavailableCount = "label-map-unavailable-count";
[TranslationReference("map")] [TranslationReference("map")]
const string MapDeletionFailed = "notification-map-deletion-failed"; const string MapDeletionFailed = "notification-map-deletion-failed";
@@ -80,8 +86,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly Widget widget; readonly Widget widget;
readonly DropDownButtonWidget gameModeDropdown; readonly DropDownButtonWidget gameModeDropdown;
readonly ModData modData; readonly ModData modData;
readonly HashSet<string> remoteMapPool;
readonly ScrollItemWidget itemTemplate;
MapClassification currentTab; MapClassification currentTab;
bool disposed;
int remoteSearching = 0;
int remoteUnavailable = 0;
readonly Dictionary<MapClassification, ScrollPanelWidget> scrollpanels = new(); readonly Dictionary<MapClassification, ScrollPanelWidget> scrollpanels = new();
@@ -97,12 +108,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Func<MapPreview, long> orderByFunc; Func<MapPreview, long> orderByFunc;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, ModData modData, string initialMap, internal MapChooserLogic(Widget widget, ModData modData, string initialMap, HashSet<string> remoteMapPool,
MapClassification initialTab, Action onExit, Action<string> onSelect, MapVisibility filter) MapClassification initialTab, Action onExit, Action<string> onSelect, MapVisibility filter)
{ {
this.widget = widget; this.widget = widget;
this.modData = modData; this.modData = modData;
this.onSelect = onSelect; this.onSelect = onSelect;
this.remoteMapPool = remoteMapPool;
allMaps = TranslationProvider.GetString(AllMaps); allMaps = TranslationProvider.GetString(AllMaps);
@@ -121,10 +133,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER"); gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER");
var itemTemplate = widget.Get<ScrollItemWidget>("MAP_TEMPLATE"); itemTemplate = widget.Get<ScrollItemWidget>("MAP_TEMPLATE");
widget.RemoveChild(itemTemplate); widget.RemoveChild(itemTemplate);
SetupOrderByDropdown(itemTemplate); SetupOrderByDropdown();
var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT"); var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (mapFilterInput != null) if (mapFilterInput != null)
@@ -137,7 +149,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
else else
{ {
mapFilter = mapFilterInput.Text = null; mapFilter = mapFilterInput.Text = null;
EnumerateMaps(currentTab, itemTemplate); EnumerateMaps(currentTab);
} }
return true; return true;
@@ -146,7 +158,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
mapFilterInput.OnTextEdited = () => mapFilterInput.OnTextEdited = () =>
{ {
mapFilter = mapFilterInput.Text; mapFilter = mapFilterInput.Text;
EnumerateMaps(currentTab, itemTemplate); EnumerateMaps(currentTab);
}; };
} }
@@ -167,12 +179,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
deleteMapButton.IsVisible = () => currentTab == MapClassification.User; deleteMapButton.IsVisible = () => currentTab == MapClassification.User;
deleteMapButton.OnClick = () => deleteMapButton.OnClick = () =>
{ {
DeleteOneMap(selectedUid, (string newUid) => DeleteOneMap(selectedUid, newUid =>
{ {
RefreshMaps(currentTab, filter); RefreshMaps(currentTab, filter);
EnumerateMaps(currentTab, itemTemplate); EnumerateMaps(currentTab);
if (tabMaps[currentTab].Length == 0) if (tabMaps[currentTab].Length == 0)
SwitchTab(modData.MapCache[newUid].Class, itemTemplate); SwitchTab(modData.MapCache[newUid].Class);
}); });
}; };
@@ -183,15 +195,41 @@ namespace OpenRA.Mods.Common.Widgets.Logic
DeleteAllMaps(visibleMaps, (string newUid) => DeleteAllMaps(visibleMaps, (string newUid) =>
{ {
RefreshMaps(currentTab, filter); RefreshMaps(currentTab, filter);
EnumerateMaps(currentTab, itemTemplate); EnumerateMaps(currentTab);
SwitchTab(modData.MapCache[newUid].Class, itemTemplate); SwitchTab(modData.MapCache[newUid].Class);
}); });
}; };
SetupMapTab(MapClassification.User, filter, "USER_MAPS_TAB_BUTTON", "USER_MAPS_TAB", itemTemplate); var remoteMapLabel = widget.Get<LabelWidget>("REMOTE_MAP_LABEL");
SetupMapTab(MapClassification.System, filter, "SYSTEM_MAPS_TAB_BUTTON", "SYSTEM_MAPS_TAB", itemTemplate); var remoteMapText = new CachedTransform<(int Searching, int Unavailable), string>(counts =>
{
if (counts.Searching > 0)
return TranslationProvider.GetString(MapSearchingCount, Translation.Arguments("count", counts.Searching));
if (initialMap == null && tabMaps.TryGetValue(initialTab, out var map) && map.Length > 0) return TranslationProvider.GetString(MapUnavailableCount, Translation.Arguments("count", counts.Unavailable));
});
remoteMapLabel.IsVisible = () => remoteMapPool != null && (remoteSearching > 0 || remoteUnavailable > 0);
remoteMapLabel.GetText = () => remoteMapText.Update((remoteSearching, remoteUnavailable));
// SetupMapTab (through RefreshMap) depends on the map search having already started
if (remoteMapPool != null && Game.Settings.Game.AllowDownloading)
{
var services = modData.Manifest.Get<WebServices>();
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, remoteMapPool);
}
SetupMapTab(MapClassification.User, filter, "USER_MAPS_TAB_BUTTON", "USER_MAPS_TAB");
SetupMapTab(MapClassification.System, filter, "SYSTEM_MAPS_TAB_BUTTON", "SYSTEM_MAPS_TAB");
SetupMapTab(MapClassification.Remote, filter, "REMOTE_MAPS_TAB_BUTTON", "REMOTE_MAPS_TAB");
// System and user map tabs are hidden when the server forces a restricted pool
if (remoteMapPool != null)
{
currentTab = MapClassification.Remote;
selectedUid = initialMap;
}
else if (initialMap == null && tabMaps.TryGetValue(initialTab, out var map) && map.Length > 0)
{ {
selectedUid = Game.ModData.MapCache.ChooseInitialMap(map.Select(mp => mp.Uid).First(), selectedUid = Game.ModData.MapCache.ChooseInitialMap(map.Select(mp => mp.Uid).First(),
Game.CosmeticRandom); Game.CosmeticRandom);
@@ -203,22 +241,59 @@ namespace OpenRA.Mods.Common.Widgets.Logic
currentTab = tabMaps.Keys.FirstOrDefault(k => tabMaps[k].Select(mp => mp.Uid).Contains(selectedUid)); currentTab = tabMaps.Keys.FirstOrDefault(k => tabMaps[k].Select(mp => mp.Uid).Contains(selectedUid));
} }
SwitchTab(currentTab, itemTemplate); EnumerateMaps(currentTab);
} }
void SwitchTab(MapClassification tab, ScrollItemWidget itemTemplate) void SwitchTab(MapClassification tab)
{ {
currentTab = tab; currentTab = tab;
EnumerateMaps(tab, itemTemplate); EnumerateMaps(tab);
} }
void RefreshMaps(MapClassification tab, MapVisibility filter) void RefreshMaps(MapClassification tab, MapVisibility filter)
{ {
tabMaps[tab] = modData.MapCache.Where(m => m.Status == MapStatus.Available && if (tab != MapClassification.Remote)
m.Class == tab && (m.Visibility & filter) != 0).ToArray(); tabMaps[tab] = modData.MapCache.Where(m => m.Status == MapStatus.Available &&
m.Class == tab && (m.Visibility & filter) != 0).ToArray();
else if (remoteMapPool != null)
{
var loaded = new List<MapPreview>();
remoteSearching = 0;
remoteUnavailable = 0;
foreach (var uid in remoteMapPool)
{
var preview = modData.MapCache[uid];
var status = preview.Status;
if (status == MapStatus.Searching)
remoteSearching++;
else if (status == MapStatus.Unavailable)
remoteUnavailable++;
else
loaded.Add(preview);
}
tabMaps[tab] = loaded.ToArray();
if (remoteSearching > 0)
{
Game.RunAfterDelay(1000, () =>
{
if (disposed)
return;
var missingBefore = remoteSearching + remoteUnavailable;
RefreshMaps(MapClassification.Remote, filter);
var missingAfter = remoteSearching + remoteUnavailable;
if (currentTab == MapClassification.Remote && missingBefore != missingAfter)
EnumerateMaps(MapClassification.Remote);
});
}
}
else
tabMaps[tab] = Array.Empty<MapPreview>();
} }
void SetupMapTab(MapClassification tab, MapVisibility filter, string tabButtonName, string tabContainerName, ScrollItemWidget itemTemplate) void SetupMapTab(MapClassification tab, MapVisibility filter, string tabButtonName, string tabContainerName)
{ {
var tabContainer = widget.Get<ContainerWidget>(tabContainerName); var tabContainer = widget.Get<ContainerWidget>(tabContainerName);
tabContainer.IsVisible = () => currentTab == tab; tabContainer.IsVisible = () => currentTab == tab;
@@ -228,13 +303,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var tabButton = widget.Get<ButtonWidget>(tabButtonName); var tabButton = widget.Get<ButtonWidget>(tabButtonName);
tabButton.IsHighlighted = () => currentTab == tab; tabButton.IsHighlighted = () => currentTab == tab;
tabButton.IsVisible = () => tabMaps[tab].Length > 0;
tabButton.OnClick = () => SwitchTab(tab, itemTemplate); if (remoteMapPool != null)
{
var isRemoteTab = tab == MapClassification.Remote;
tabButton.IsVisible = () => isRemoteTab;
}
else
tabButton.IsVisible = () => tabMaps[tab].Length > 0;
tabButton.OnClick = () => SwitchTab(tab);
RefreshMaps(tab, filter); RefreshMaps(tab, filter);
} }
void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown, ScrollItemWidget itemTemplate) void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown)
{ {
if (gameModeDropdown != null) if (gameModeDropdown != null)
{ {
@@ -263,7 +346,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
var item = ScrollItemWidget.Setup(template, var item = ScrollItemWidget.Setup(template,
() => category == ii.Category, () => category == ii.Category,
() => { category = ii.Category; EnumerateMaps(tab, itemTemplate); }); () => { category = ii.Category; EnumerateMaps(tab); });
item.Get<LabelWidget>("LABEL").GetText = () => ShowItem(ii); item.Get<LabelWidget>("LABEL").GetText = () => ShowItem(ii);
return item; return item;
} }
@@ -282,7 +365,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
} }
} }
void SetupOrderByDropdown(ScrollItemWidget itemTemplate) void SetupOrderByDropdown()
{ {
var orderByDropdown = widget.GetOrNull<DropDownButtonWidget>("ORDERBY"); var orderByDropdown = widget.GetOrNull<DropDownButtonWidget>("ORDERBY");
if (orderByDropdown == null) if (orderByDropdown == null)
@@ -304,7 +387,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
var item = ScrollItemWidget.Setup(template, var item = ScrollItemWidget.Setup(template,
() => orderByFunc == orderByDict[o], () => orderByFunc == orderByDict[o],
() => { orderByFunc = orderByDict[o]; EnumerateMaps(currentTab, itemTemplate); }); () => { orderByFunc = orderByDict[o]; EnumerateMaps(currentTab); });
item.Get<LabelWidget>("LABEL").GetText = () => o; item.Get<LabelWidget>("LABEL").GetText = () => o;
return item; return item;
@@ -317,7 +400,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
orderByDict.FirstOrDefault(m => m.Value == orderByFunc).Key; orderByDict.FirstOrDefault(m => m.Value == orderByFunc).Key;
} }
void EnumerateMaps(MapClassification tab, ScrollItemWidget template) void EnumerateMaps(MapClassification tab)
{ {
if (!int.TryParse(mapFilter, out var playerCountFilter)) if (!int.TryParse(mapFilter, out var playerCountFilter))
playerCountFilter = -1; playerCountFilter = -1;
@@ -353,7 +436,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
} }
} }
var item = ScrollItemWidget.Setup(preview.Uid, template, () => selectedUid == preview.Uid, var item = ScrollItemWidget.Setup(preview.Uid, itemTemplate, () => selectedUid == preview.Uid,
() => selectedUid = preview.Uid, DblClick); () => selectedUid = preview.Uid, DblClick);
item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanels[tab].RenderBounds); item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanels[tab].RenderBounds);
@@ -400,7 +483,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (tab == currentTab) if (tab == currentTab)
{ {
visibleMaps = maps.Select(m => m.Uid).ToArray(); visibleMaps = maps.Select(m => m.Uid).ToArray();
SetupGameModeDropdown(currentTab, gameModeDropdown, template); SetupGameModeDropdown(currentTab, gameModeDropdown);
} }
if (visibleMaps.Contains(selectedUid)) if (visibleMaps.Contains(selectedUid))
@@ -455,5 +538,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
confirmText: DeleteAllMapsAccept, confirmText: DeleteAllMapsAccept,
onCancel: () => { }); onCancel: () => { });
} }
protected override void Dispose(bool disposing)
{
disposed = true;
base.Dispose(disposing);
}
} }
} }

View File

@@ -95,6 +95,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{ {
{ "initialMap", map.Uid }, { "initialMap", map.Uid },
{ "remoteMapPool", null },
{ "initialTab", MapClassification.System }, { "initialTab", MapClassification.System },
{ "onExit", () => modData.MapCache.UpdateMaps() }, { "onExit", () => modData.MapCache.UpdateMaps() },
{ "onSelect", (Action<string>)(uid => map = modData.MapCache[uid]) }, { "onSelect", (Action<string>)(uid => map = modData.MapCache[uid]) },

View File

@@ -23,6 +23,12 @@ Container@MAPCHOOSER_PANEL:
Height: 31 Height: 31
Width: 135 Width: 135
Text: button-bg-system-maps-tab Text: button-bg-system-maps-tab
Button@REMOTE_MAPS_TAB_BUTTON:
X: 15
Y: 15
Height: 31
Width: 135
Text: button-bg-remote-maps-tab
Button@USER_MAPS_TAB_BUTTON: Button@USER_MAPS_TAB_BUTTON:
X: 155 X: 155
Y: 15 Y: 15
@@ -43,6 +49,14 @@ Container@MAPCHOOSER_PANEL:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM
ItemSpacing: 1 ItemSpacing: 1
Container@REMOTE_MAPS_TAB:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
ItemSpacing: 1
Container@USER_MAPS_TAB: Container@USER_MAPS_TAB:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM
@@ -134,6 +148,13 @@ Container@MAPCHOOSER_PANEL:
X: PARENT_RIGHT - WIDTH X: PARENT_RIGHT - WIDTH
Width: 200 Width: 200
Height: 25 Height: 25
Label@REMOTE_MAP_LABEL:
X: 140
Y: 539
Width: PARENT_RIGHT - 430
Height: 35
Align: Center
Font: Bold
Button@BUTTON_CANCEL: Button@BUTTON_CANCEL:
Key: escape Key: escape
Y: PARENT_BOTTOM - 1 Y: PARENT_BOTTOM - 1

View File

@@ -499,6 +499,7 @@ label-update-notice-b = Download the latest version from www.openra.net
## mapchooser.yaml ## mapchooser.yaml
label-mapchooser-panel-title = Select Map label-mapchooser-panel-title = Select Map
button-bg-system-maps-tab = Official Maps button-bg-system-maps-tab = Official Maps
button-bg-remote-maps-tab = Server Maps
button-bg-user-maps-tab = Custom Maps button-bg-user-maps-tab = Custom Maps
label-filter-order-controls-desc = Filter: label-filter-order-controls-desc = Filter:
label-filter-order-controls-desc-joiner = in label-filter-order-controls-desc-joiner = in

View File

@@ -19,6 +19,13 @@ Background@MAPCHOOSER_PANEL:
Width: 140 Width: 140
Text: button-mapchooser-panel-system-maps-tab Text: button-mapchooser-panel-system-maps-tab
Font: Bold Font: Bold
Button@REMOTE_MAPS_TAB_BUTTON:
X: 20
Y: 48
Height: 31
Width: 140
Text: button-mapchooser-panel-remote-maps-tab
Font: Bold
Button@USER_MAPS_TAB_BUTTON: Button@USER_MAPS_TAB_BUTTON:
X: 160 X: 160
Y: 48 Y: 48
@@ -39,6 +46,13 @@ Background@MAPCHOOSER_PANEL:
ScrollPanel@MAP_LIST: ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM
Container@REMOTE_MAPS_TAB:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Container@USER_MAPS_TAB: Container@USER_MAPS_TAB:
Width: PARENT_RIGHT Width: PARENT_RIGHT
Height: PARENT_BOTTOM Height: PARENT_BOTTOM
@@ -150,6 +164,13 @@ Background@MAPCHOOSER_PANEL:
Height: 25 Height: 25
Text: button-mapchooser-panel-delete-all-maps Text: button-mapchooser-panel-delete-all-maps
Font: Bold Font: Bold
Label@REMOTE_MAP_LABEL:
X: 140
Y: PARENT_BOTTOM - HEIGHT - 20
Width: PARENT_RIGHT - 410
Height: 25
Align: Center
Font: Bold
Button@BUTTON_OK: Button@BUTTON_OK:
X: PARENT_RIGHT - 270 X: PARENT_RIGHT - 270
Y: PARENT_BOTTOM - 45 Y: PARENT_BOTTOM - 45

View File

@@ -350,6 +350,7 @@ label-update-notice-b = Download the latest version from www.openra.net
## map-chooser.yaml ## map-chooser.yaml
label-mapchooser-panel-title = Choose Map label-mapchooser-panel-title = Choose Map
button-mapchooser-panel-system-maps-tab = Official Maps button-mapchooser-panel-system-maps-tab = Official Maps
button-mapchooser-panel-remote-maps-tab = Server Maps
button-mapchooser-panel-user-maps-tab = Custom Maps button-mapchooser-panel-user-maps-tab = Custom Maps
label-filter-order-controls-desc = Filter: label-filter-order-controls-desc = Filter:
label-filter-order-controls-desc-joiner = in label-filter-order-controls-desc-joiner = in

View File

@@ -500,6 +500,16 @@ label-map-size-huge = (Huge)
label-map-size-large = (Large) label-map-size-large = (Large)
label-map-size-medium = (Medium) label-map-size-medium = (Medium)
label-map-size-small = (Small) label-map-size-small = (Small)
label-map-searching-count =
{ $count ->
[one] Searching the OpenRA Resource Center for { $count } map...
*[other] Searching the OpenRA Resource Center for { $count } maps...
}
label-map-unavailable-count =
{ $count ->
[one] { $count } map was not found on the OpenRA Resource Center
*[other] { $count } maps were not found on the OpenRA Resource Center
}
notification-map-deletion-failed = Failed to delete map '{ $map }'. See the debug.log file for details. notification-map-deletion-failed = Failed to delete map '{ $map }'. See the debug.log file for details.