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:
@@ -39,6 +39,9 @@ namespace OpenRA.Network
|
||||
public string ServerError = null;
|
||||
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 LocalFrameNumber;
|
||||
|
||||
|
||||
@@ -383,6 +383,12 @@ namespace OpenRA.Network
|
||||
break;
|
||||
}
|
||||
|
||||
case "SyncMapPool":
|
||||
{
|
||||
orderManager.ServerMapPool = FieldLoader.GetValue<HashSet<string>>("SyncMapPool", order.TargetString);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (world == null)
|
||||
|
||||
@@ -1396,6 +1396,9 @@ namespace OpenRA.Mods.Common.Server
|
||||
{
|
||||
lock (server.LobbyInfo)
|
||||
{
|
||||
if (server.MapPool != null)
|
||||
server.SendOrderTo(conn, "SyncMapPool", FieldSaver.FormatValue(server.MapPool));
|
||||
|
||||
var client = server.GetClient(conn);
|
||||
|
||||
// Validate whether color is allowed and get an alternative if it isn't
|
||||
|
||||
@@ -236,7 +236,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
var onSelect = new Action<string>(uid =>
|
||||
{
|
||||
// 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;
|
||||
|
||||
orderManager.IssueOrder(Order.Command("map " + uid));
|
||||
@@ -250,6 +251,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
|
||||
{
|
||||
{ "initialMap", modData.MapCache.PickLastModifiedMap(MapVisibility.Lobby) ?? map.Uid },
|
||||
{ "remoteMapPool", orderManager.ServerMapPool },
|
||||
{ "initialTab", MapClassification.System },
|
||||
{ "onExit", modData.MapCache.UpdateMaps },
|
||||
{ "onSelect", Game.IsHost ? onSelect : null },
|
||||
|
||||
@@ -204,6 +204,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
|
||||
{
|
||||
{ "initialMap", null },
|
||||
{ "remoteMapPool", null },
|
||||
{ "initialTab", MapClassification.User },
|
||||
{ "onExit", () => SwitchMenu(MenuType.MapEditor) },
|
||||
{ "onSelect", onSelect },
|
||||
|
||||
@@ -42,6 +42,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
[TranslationReference]
|
||||
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")]
|
||||
const string MapDeletionFailed = "notification-map-deletion-failed";
|
||||
|
||||
@@ -80,8 +86,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
readonly Widget widget;
|
||||
readonly DropDownButtonWidget gameModeDropdown;
|
||||
readonly ModData modData;
|
||||
readonly HashSet<string> remoteMapPool;
|
||||
readonly ScrollItemWidget itemTemplate;
|
||||
|
||||
MapClassification currentTab;
|
||||
bool disposed;
|
||||
int remoteSearching = 0;
|
||||
int remoteUnavailable = 0;
|
||||
|
||||
readonly Dictionary<MapClassification, ScrollPanelWidget> scrollpanels = new();
|
||||
|
||||
@@ -97,12 +108,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
Func<MapPreview, long> orderByFunc;
|
||||
|
||||
[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)
|
||||
{
|
||||
this.widget = widget;
|
||||
this.modData = modData;
|
||||
this.onSelect = onSelect;
|
||||
this.remoteMapPool = remoteMapPool;
|
||||
|
||||
allMaps = TranslationProvider.GetString(AllMaps);
|
||||
|
||||
@@ -121,10 +133,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER");
|
||||
|
||||
var itemTemplate = widget.Get<ScrollItemWidget>("MAP_TEMPLATE");
|
||||
itemTemplate = widget.Get<ScrollItemWidget>("MAP_TEMPLATE");
|
||||
widget.RemoveChild(itemTemplate);
|
||||
|
||||
SetupOrderByDropdown(itemTemplate);
|
||||
SetupOrderByDropdown();
|
||||
|
||||
var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
|
||||
if (mapFilterInput != null)
|
||||
@@ -137,7 +149,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
else
|
||||
{
|
||||
mapFilter = mapFilterInput.Text = null;
|
||||
EnumerateMaps(currentTab, itemTemplate);
|
||||
EnumerateMaps(currentTab);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -146,7 +158,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
mapFilterInput.OnTextEdited = () =>
|
||||
{
|
||||
mapFilter = mapFilterInput.Text;
|
||||
EnumerateMaps(currentTab, itemTemplate);
|
||||
EnumerateMaps(currentTab);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -167,12 +179,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
deleteMapButton.IsVisible = () => currentTab == MapClassification.User;
|
||||
deleteMapButton.OnClick = () =>
|
||||
{
|
||||
DeleteOneMap(selectedUid, (string newUid) =>
|
||||
DeleteOneMap(selectedUid, newUid =>
|
||||
{
|
||||
RefreshMaps(currentTab, filter);
|
||||
EnumerateMaps(currentTab, itemTemplate);
|
||||
EnumerateMaps(currentTab);
|
||||
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) =>
|
||||
{
|
||||
RefreshMaps(currentTab, filter);
|
||||
EnumerateMaps(currentTab, itemTemplate);
|
||||
SwitchTab(modData.MapCache[newUid].Class, itemTemplate);
|
||||
EnumerateMaps(currentTab);
|
||||
SwitchTab(modData.MapCache[newUid].Class);
|
||||
});
|
||||
};
|
||||
|
||||
SetupMapTab(MapClassification.User, filter, "USER_MAPS_TAB_BUTTON", "USER_MAPS_TAB", itemTemplate);
|
||||
SetupMapTab(MapClassification.System, filter, "SYSTEM_MAPS_TAB_BUTTON", "SYSTEM_MAPS_TAB", itemTemplate);
|
||||
var remoteMapLabel = widget.Get<LabelWidget>("REMOTE_MAP_LABEL");
|
||||
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(),
|
||||
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));
|
||||
}
|
||||
|
||||
SwitchTab(currentTab, itemTemplate);
|
||||
EnumerateMaps(currentTab);
|
||||
}
|
||||
|
||||
void SwitchTab(MapClassification tab, ScrollItemWidget itemTemplate)
|
||||
void SwitchTab(MapClassification tab)
|
||||
{
|
||||
currentTab = tab;
|
||||
EnumerateMaps(tab, itemTemplate);
|
||||
EnumerateMaps(tab);
|
||||
}
|
||||
|
||||
void RefreshMaps(MapClassification tab, MapVisibility filter)
|
||||
{
|
||||
tabMaps[tab] = modData.MapCache.Where(m => m.Status == MapStatus.Available &&
|
||||
m.Class == tab && (m.Visibility & filter) != 0).ToArray();
|
||||
if (tab != MapClassification.Remote)
|
||||
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);
|
||||
tabContainer.IsVisible = () => currentTab == tab;
|
||||
@@ -228,13 +303,21 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
|
||||
var tabButton = widget.Get<ButtonWidget>(tabButtonName);
|
||||
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);
|
||||
}
|
||||
|
||||
void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown, ScrollItemWidget itemTemplate)
|
||||
void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown)
|
||||
{
|
||||
if (gameModeDropdown != null)
|
||||
{
|
||||
@@ -263,7 +346,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(template,
|
||||
() => category == ii.Category,
|
||||
() => { category = ii.Category; EnumerateMaps(tab, itemTemplate); });
|
||||
() => { category = ii.Category; EnumerateMaps(tab); });
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => ShowItem(ii);
|
||||
return item;
|
||||
}
|
||||
@@ -282,7 +365,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
}
|
||||
}
|
||||
|
||||
void SetupOrderByDropdown(ScrollItemWidget itemTemplate)
|
||||
void SetupOrderByDropdown()
|
||||
{
|
||||
var orderByDropdown = widget.GetOrNull<DropDownButtonWidget>("ORDERBY");
|
||||
if (orderByDropdown == null)
|
||||
@@ -304,7 +387,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(template,
|
||||
() => orderByFunc == orderByDict[o],
|
||||
() => { orderByFunc = orderByDict[o]; EnumerateMaps(currentTab, itemTemplate); });
|
||||
() => { orderByFunc = orderByDict[o]; EnumerateMaps(currentTab); });
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => o;
|
||||
|
||||
return item;
|
||||
@@ -317,7 +400,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
orderByDict.FirstOrDefault(m => m.Value == orderByFunc).Key;
|
||||
}
|
||||
|
||||
void EnumerateMaps(MapClassification tab, ScrollItemWidget template)
|
||||
void EnumerateMaps(MapClassification tab)
|
||||
{
|
||||
if (!int.TryParse(mapFilter, out var playerCountFilter))
|
||||
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);
|
||||
item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanels[tab].RenderBounds);
|
||||
|
||||
@@ -400,7 +483,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
if (tab == currentTab)
|
||||
{
|
||||
visibleMaps = maps.Select(m => m.Uid).ToArray();
|
||||
SetupGameModeDropdown(currentTab, gameModeDropdown, template);
|
||||
SetupGameModeDropdown(currentTab, gameModeDropdown);
|
||||
}
|
||||
|
||||
if (visibleMaps.Contains(selectedUid))
|
||||
@@ -455,5 +538,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
confirmText: DeleteAllMapsAccept,
|
||||
onCancel: () => { });
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
disposed = true;
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
|
||||
{
|
||||
{ "initialMap", map.Uid },
|
||||
{ "remoteMapPool", null },
|
||||
{ "initialTab", MapClassification.System },
|
||||
{ "onExit", () => modData.MapCache.UpdateMaps() },
|
||||
{ "onSelect", (Action<string>)(uid => map = modData.MapCache[uid]) },
|
||||
|
||||
@@ -23,6 +23,12 @@ Container@MAPCHOOSER_PANEL:
|
||||
Height: 31
|
||||
Width: 135
|
||||
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:
|
||||
X: 155
|
||||
Y: 15
|
||||
@@ -43,6 +49,14 @@ Container@MAPCHOOSER_PANEL:
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
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:
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
@@ -134,6 +148,13 @@ Container@MAPCHOOSER_PANEL:
|
||||
X: PARENT_RIGHT - WIDTH
|
||||
Width: 200
|
||||
Height: 25
|
||||
Label@REMOTE_MAP_LABEL:
|
||||
X: 140
|
||||
Y: 539
|
||||
Width: PARENT_RIGHT - 430
|
||||
Height: 35
|
||||
Align: Center
|
||||
Font: Bold
|
||||
Button@BUTTON_CANCEL:
|
||||
Key: escape
|
||||
Y: PARENT_BOTTOM - 1
|
||||
|
||||
@@ -499,6 +499,7 @@ label-update-notice-b = Download the latest version from www.openra.net
|
||||
## mapchooser.yaml
|
||||
label-mapchooser-panel-title = Select Map
|
||||
button-bg-system-maps-tab = Official Maps
|
||||
button-bg-remote-maps-tab = Server Maps
|
||||
button-bg-user-maps-tab = Custom Maps
|
||||
label-filter-order-controls-desc = Filter:
|
||||
label-filter-order-controls-desc-joiner = in
|
||||
|
||||
@@ -19,6 +19,13 @@ Background@MAPCHOOSER_PANEL:
|
||||
Width: 140
|
||||
Text: button-mapchooser-panel-system-maps-tab
|
||||
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:
|
||||
X: 160
|
||||
Y: 48
|
||||
@@ -39,6 +46,13 @@ Background@MAPCHOOSER_PANEL:
|
||||
ScrollPanel@MAP_LIST:
|
||||
Width: PARENT_RIGHT
|
||||
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:
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
@@ -150,6 +164,13 @@ Background@MAPCHOOSER_PANEL:
|
||||
Height: 25
|
||||
Text: button-mapchooser-panel-delete-all-maps
|
||||
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:
|
||||
X: PARENT_RIGHT - 270
|
||||
Y: PARENT_BOTTOM - 45
|
||||
|
||||
@@ -350,6 +350,7 @@ label-update-notice-b = Download the latest version from www.openra.net
|
||||
## map-chooser.yaml
|
||||
label-mapchooser-panel-title = Choose Map
|
||||
button-mapchooser-panel-system-maps-tab = Official Maps
|
||||
button-mapchooser-panel-remote-maps-tab = Server Maps
|
||||
button-mapchooser-panel-user-maps-tab = Custom Maps
|
||||
label-filter-order-controls-desc = Filter:
|
||||
label-filter-order-controls-desc-joiner = in
|
||||
|
||||
@@ -500,6 +500,16 @@ label-map-size-huge = (Huge)
|
||||
label-map-size-large = (Large)
|
||||
label-map-size-medium = (Medium)
|
||||
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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user