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

View File

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

View File

@@ -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

View File

@@ -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 },

View File

@@ -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 },

View File

@@ -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)
{
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);
}
void SetupMapTab(MapClassification tab, MapVisibility filter, string tabButtonName, string tabContainerName, ScrollItemWidget itemTemplate)
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)
{
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;
if (remoteMapPool != null)
{
var isRemoteTab = tab == MapClassification.Remote;
tabButton.IsVisible = () => isRemoteTab;
}
else
tabButton.IsVisible = () => tabMaps[tab].Length > 0;
tabButton.OnClick = () => SwitchTab(tab, itemTemplate);
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);
}
}
}

View File

@@ -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]) },

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.