Merge pull request #8207 from obrakmann/map-chooser-overhaul

Split maps into system and custom maps in the map chooser
This commit is contained in:
Pavel Penev
2015-05-24 04:12:23 +03:00
6 changed files with 380 additions and 140 deletions

View File

@@ -274,5 +274,11 @@ namespace OpenRA
Map.PreloadRules(); Map.PreloadRules();
RuleStatus = Map.InvalidCustomRules ? MapRuleStatus.Invalid : MapRuleStatus.Cached; RuleStatus = Map.InvalidCustomRules ? MapRuleStatus.Invalid : MapRuleStatus.Cached;
} }
public void Invalidate()
{
Status = MapStatus.Unavailable;
RuleStatus = MapRuleStatus.Unknown;
}
} }
} }

View File

@@ -155,7 +155,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON"); var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON");
if (mapButton != null) if (mapButton != null)
{ {
mapButton.IsDisabled = configurationDisabled; mapButton.IsDisabled = () => gameStarting || panel == PanelType.Kick || panel == PanelType.ForceStart ||
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
mapButton.OnClick = () => mapButton.OnClick = () =>
{ {
var onSelect = new Action<string>(uid => var onSelect = new Action<string>(uid =>
@@ -172,8 +173,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{ {
{ "initialMap", Map.Uid }, { "initialMap", Map.Uid },
{ "initialTab", MapClassification.System },
{ "onExit", DoNothing }, { "onExit", DoNothing },
{ "onSelect", onSelect }, { "onSelect", Game.IsHost ? onSelect : null },
{ "filter", MapVisibility.Lobby }, { "filter", MapVisibility.Lobby },
}); });
}; };

View File

@@ -170,11 +170,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var loadMapButton = widget.Get<ButtonWidget>("LOAD_MAP_BUTTON"); var loadMapButton = widget.Get<ButtonWidget>("LOAD_MAP_BUTTON");
loadMapButton.OnClick = () => loadMapButton.OnClick = () =>
{ {
var initialMap = Game.ModData.MapCache.FirstOrDefault();
menuType = MenuType.None; menuType = MenuType.None;
Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs() Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{ {
{ "initialMap", initialMap != null ? initialMap.Uid : null }, { "initialMap", null },
{ "initialTab", MapClassification.User },
{ "onExit", () => menuType = MenuType.MapEditor }, { "onExit", () => menuType = MenuType.MapEditor },
{ "onSelect", onSelect }, { "onSelect", onSelect },
{ "filter", MapVisibility.Lobby | MapVisibility.Shellmap | MapVisibility.MissionSelector }, { "filter", MapVisibility.Lobby | MapVisibility.Shellmap | MapVisibility.MissionSelector },

View File

@@ -10,7 +10,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using OpenRA;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -18,80 +20,63 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
public class MapChooserLogic public class MapChooserLogic
{ {
string selectedUid; readonly Widget widget;
readonly DropDownButtonWidget gameModeDropdown;
// May be a subset of available maps if a mode filter is active MapClassification currentTab;
Dictionary<MapClassification, ScrollPanelWidget> scrollpanels = new Dictionary<MapClassification, ScrollPanelWidget>();
Dictionary<MapClassification, MapPreview[]> tabMaps = new Dictionary<MapClassification, MapPreview[]>();
string[] visibleMaps; string[] visibleMaps;
ScrollPanelWidget scrollpanel; string selectedUid;
ScrollItemWidget itemTemplate; Action<string> onSelect;
string mapFilter;
string gameMode; string gameMode;
string mapFilter;
[ObjectCreator.UseCtor] [ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, string initialMap, Action onExit, Action<string> onSelect, MapVisibility filter) internal MapChooserLogic(Widget widget, string initialMap, MapClassification initialTab, Action onExit, Action<string> onSelect, MapVisibility filter)
{ {
selectedUid = WidgetUtils.ChooseInitialMap(initialMap); this.widget = widget;
this.onSelect = onSelect;
var approving = new Action(() => { Ui.CloseWindow(); onSelect(selectedUid); }); var approving = new Action(() => { Ui.CloseWindow(); onSelect(selectedUid); });
var canceling = new Action(() => { Ui.CloseWindow(); onExit(); }); var canceling = new Action(() => { Ui.CloseWindow(); onExit(); });
widget.Get<ButtonWidget>("BUTTON_OK").OnClick = approving; var okButton = widget.Get<ButtonWidget>("BUTTON_OK");
okButton.Disabled = this.onSelect == null;
okButton.OnClick = approving;
widget.Get<ButtonWidget>("BUTTON_CANCEL").OnClick = canceling; widget.Get<ButtonWidget>("BUTTON_CANCEL").OnClick = canceling;
scrollpanel = widget.Get<ScrollPanelWidget>("MAP_LIST"); gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER");
scrollpanel.Layout = new GridLayout(scrollpanel);
itemTemplate = scrollpanel.Get<ScrollItemWidget>("MAP_TEMPLATE"); var itemTemplate = widget.Get<ScrollItemWidget>("MAP_TEMPLATE");
widget.RemoveChild(itemTemplate);
var gameModeDropdown = widget.GetOrNull<DropDownButtonWidget>("GAMEMODE_FILTER"); var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (gameModeDropdown != null) if (mapFilterInput != null)
{ {
var selectableMaps = Game.ModData.MapCache.Where(m => m.Status == MapStatus.Available && (m.Map.Visibility & filter) != 0); mapFilterInput.TakeKeyboardFocus();
var gameModes = selectableMaps mapFilterInput.OnEscKey = () =>
.GroupBy(m => m.Type)
.Select(g => Pair.New(g.Key, g.Count())).ToList();
// 'all game types' extra item
gameModes.Insert(0, Pair.New(null as string, selectableMaps.Count()));
Func<Pair<string, int>, string> showItem =
x => "{0} ({1})".F(x.First ?? "All Game Types", x.Second);
Func<Pair<string, int>, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, template) =>
{ {
var item = ScrollItemWidget.Setup(template, if (mapFilterInput.Text.Length == 0)
() => gameMode == ii.First,
() => { gameMode = ii.First; EnumerateMaps(onSelect, filter); });
item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii);
return item;
};
gameModeDropdown.OnClick = () =>
gameModeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, gameModes, setupItem);
gameModeDropdown.GetText = () => showItem(gameModes.First(m => m.First == gameMode));
}
var mapfilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (mapfilterInput != null)
{
mapfilterInput.TakeKeyboardFocus();
mapfilterInput.OnEscKey = () =>
{
if (mapfilterInput.Text.Length == 0)
canceling(); canceling();
else else
{ {
mapFilter = mapfilterInput.Text = null; mapFilter = mapFilterInput.Text = null;
EnumerateMaps(onSelect, filter); EnumerateMaps(currentTab, itemTemplate);
} }
return true; return true;
}; };
mapfilterInput.OnEnterKey = () => { approving(); return true; }; mapFilterInput.OnEnterKey = () => { approving(); return true; };
mapfilterInput.OnTextEdited = () => mapFilterInput.OnTextEdited = () =>
{ mapFilter = mapfilterInput.Text; EnumerateMaps(onSelect, filter); }; {
mapFilter = mapFilterInput.Text;
EnumerateMaps(currentTab, itemTemplate);
};
} }
var randomMapButton = widget.GetOrNull<ButtonWidget>("RANDOMMAP_BUTTON"); var randomMapButton = widget.GetOrNull<ButtonWidget>("RANDOMMAP_BUTTON");
@@ -101,18 +86,121 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
var uid = visibleMaps.Random(Game.CosmeticRandom); var uid = visibleMaps.Random(Game.CosmeticRandom);
selectedUid = uid; selectedUid = uid;
scrollpanel.ScrollToItem(uid, smooth: true); scrollpanels[currentTab].ScrollToItem(uid, smooth: true);
}; };
randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Length == 0; randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Length == 0;
} }
EnumerateMaps(onSelect, filter); var deleteMapButton = widget.Get<ButtonWidget>("DELETE_MAP_BUTTON");
deleteMapButton.IsDisabled = () => Game.ModData.MapCache[selectedUid].Class != MapClassification.User;
deleteMapButton.IsVisible = () => currentTab == MapClassification.User;
deleteMapButton.OnClick = () =>
{
DeleteOneMap(selectedUid, (string newUid) =>
{
RefreshMaps(currentTab, filter);
EnumerateMaps(currentTab, itemTemplate);
if (!tabMaps[currentTab].Any())
SwitchTab(Game.ModData.MapCache[newUid].Class, itemTemplate);
});
};
var deleteAllMapsButton = widget.Get<ButtonWidget>("DELETE_ALL_MAPS_BUTTON");
deleteAllMapsButton.IsVisible = () => currentTab == MapClassification.User;
deleteAllMapsButton.OnClick = () =>
{
DeleteAllMaps(visibleMaps, (string newUid) =>
{
RefreshMaps(currentTab, filter);
EnumerateMaps(currentTab, itemTemplate);
SwitchTab(Game.ModData.MapCache[newUid].Class, itemTemplate);
});
};
SetupMapTab(MapClassification.User, filter, "USER_MAPS_TAB_BUTTON", "USER_MAPS_TAB", itemTemplate);
SetupMapTab(MapClassification.System, filter, "SYSTEM_MAPS_TAB_BUTTON", "SYSTEM_MAPS_TAB", itemTemplate);
if (initialMap == null && tabMaps.Keys.Contains(initialTab) && tabMaps[initialTab].Any())
{
selectedUid = WidgetUtils.ChooseInitialMap(tabMaps[initialTab].Select(mp => mp.Uid).First());
currentTab = initialTab;
}
else
{
selectedUid = WidgetUtils.ChooseInitialMap(initialMap);
currentTab = tabMaps.Keys.FirstOrDefault(k => tabMaps[k].Select(mp => mp.Uid).Contains(selectedUid));
} }
void EnumerateMaps(Action<string> onSelect, MapVisibility filter) SwitchTab(currentTab, itemTemplate);
}
void SwitchTab(MapClassification tab, ScrollItemWidget itemTemplate)
{ {
var maps = Game.ModData.MapCache currentTab = tab;
.Where(m => m.Status == MapStatus.Available && (m.Map.Visibility & filter) != 0) EnumerateMaps(tab, itemTemplate);
}
void RefreshMaps(MapClassification tab, MapVisibility filter)
{
tabMaps[tab] = Game.ModData.MapCache.Where(m => m.Status == MapStatus.Available &&
m.Class == tab && (m.Map.Visibility & filter) != 0).ToArray();
}
void SetupMapTab(MapClassification tab, MapVisibility filter, string tabButtonName, string tabContainerName, ScrollItemWidget itemTemplate)
{
var tabContainer = widget.Get<ContainerWidget>(tabContainerName);
tabContainer.IsVisible = () => currentTab == tab;
var tabScrollpanel = tabContainer.Get<ScrollPanelWidget>("MAP_LIST");
tabScrollpanel.Layout = new GridLayout(tabScrollpanel);
scrollpanels.Add(tab, tabScrollpanel);
var tabButton = widget.Get<ButtonWidget>(tabButtonName);
tabButton.IsHighlighted = () => currentTab == tab;
tabButton.IsVisible = () => tabMaps[tab].Any();
tabButton.OnClick = () => SwitchTab(tab, itemTemplate);
RefreshMaps(tab, filter);
}
void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown, ScrollItemWidget itemTemplate)
{
if (gameModeDropdown != null)
{
var gameModes = tabMaps[tab]
.GroupBy(m => m.Type)
.Select(g => Pair.New(g.Key, g.Count())).ToList();
// 'all game types' extra item
gameModes.Insert(0, Pair.New(null as string, tabMaps[tab].Count()));
Func<Pair<string, int>, string> showItem = x => "{0} ({1})".F(x.First ?? "All Game Types", x.Second);
Func<Pair<string, int>, ScrollItemWidget, ScrollItemWidget> setupItem = (ii, template) =>
{
var item = ScrollItemWidget.Setup(template,
() => gameMode == ii.First,
() => { gameMode = ii.First; EnumerateMaps(tab, itemTemplate); });
item.Get<LabelWidget>("LABEL").GetText = () => showItem(ii);
return item;
};
gameModeDropdown.OnClick = () =>
gameModeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 210, gameModes, setupItem);
gameModeDropdown.GetText = () =>
{
var item = gameModes.FirstOrDefault(m => m.First == gameMode);
if (item == default(Pair<string, int>))
item.First = "No matches";
return showItem(item);
};
}
}
void EnumerateMaps(MapClassification tab, ScrollItemWidget template)
{
var maps = tabMaps[tab]
.Where(m => gameMode == null || m.Type == gameMode) .Where(m => gameMode == null || m.Type == gameMode)
.Where(m => mapFilter == null || .Where(m => mapFilter == null ||
m.Title.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0 || m.Title.IndexOf(mapFilter, StringComparison.OrdinalIgnoreCase) >= 0 ||
@@ -120,7 +208,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
.OrderBy(m => m.PlayerCount) .OrderBy(m => m.PlayerCount)
.ThenBy(m => m.Title); .ThenBy(m => m.Title);
scrollpanel.RemoveChildren(); scrollpanels[tab].RemoveChildren();
foreach (var loop in maps) foreach (var loop in maps)
{ {
var preview = loop; var preview = loop;
@@ -128,9 +216,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
// Access the minimap to trigger async generation of the minimap. // Access the minimap to trigger async generation of the minimap.
preview.GetMinimap(); preview.GetMinimap();
var item = ScrollItemWidget.Setup(preview.Uid, itemTemplate, () => selectedUid == preview.Uid, Action dblClick = () =>
() => selectedUid = preview.Uid, () => { Ui.CloseWindow(); onSelect(preview.Uid); }); {
item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanel.RenderBounds); if (onSelect != null)
{
Ui.CloseWindow();
onSelect(preview.Uid);
}
};
var item = ScrollItemWidget.Setup(preview.Uid, template, () => selectedUid == preview.Uid,
() => selectedUid = preview.Uid, dblClick);
item.IsVisible = () => item.RenderBounds.IntersectsWith(scrollpanels[tab].RenderBounds);
var titleLabel = item.Get<LabelWidget>("TITLE"); var titleLabel = item.Get<LabelWidget>("TITLE");
titleLabel.GetText = () => preview.Title; titleLabel.GetText = () => preview.Title;
@@ -158,12 +255,67 @@ namespace OpenRA.Mods.Common.Widgets.Logic
sizeWidget.GetText = () => size; sizeWidget.GetText = () => size;
} }
scrollpanel.AddChild(item); scrollpanels[tab].AddChild(item);
} }
if (tab == currentTab)
{
visibleMaps = maps.Select(m => m.Uid).ToArray(); visibleMaps = maps.Select(m => m.Uid).ToArray();
SetupGameModeDropdown(currentTab, gameModeDropdown, template);
}
if (visibleMaps.Contains(selectedUid)) if (visibleMaps.Contains(selectedUid))
scrollpanel.ScrollToItem(selectedUid); scrollpanels[tab].ScrollToItem(selectedUid);
}
string DeleteMap(string map)
{
var path = Game.ModData.MapCache[map].Map.Path;
try
{
File.Delete(path);
Game.ModData.MapCache[map].Invalidate();
if (selectedUid == map)
selectedUid = WidgetUtils.ChooseInitialMap(tabMaps[currentTab].Select(mp => mp.Uid).FirstOrDefault());
}
catch (Exception ex)
{
Game.Debug("Failed to delete map file '{0}'. See the logs for details.", path);
Log.Write("debug", ex.ToString());
}
return selectedUid;
}
void DeleteOneMap(string map, Action<string> after)
{
ConfirmationDialogs.PromptConfirmAction(
"Delete map",
"Delete the map '{0}'?".F(Game.ModData.MapCache[map].Title),
() =>
{
var newUid = DeleteMap(map);
if (after != null)
after(newUid);
},
null,
"Delete");
}
void DeleteAllMaps(string[] maps, Action<string> after)
{
ConfirmationDialogs.PromptConfirmAction(
"Delete maps",
"Delete all maps on this page?",
() =>
{
maps.Do(m => DeleteMap(m));
if (after != null)
after(WidgetUtils.ChooseInitialMap(null));
},
null,
"Delete");
} }
} }
} }

View File

@@ -43,12 +43,38 @@ Container@MAPCHOOSER_PANEL:
Y: 10 Y: 10
Width: 200 Width: 200
Height: 25 Height: 25
ScrollPanel@MAP_LIST: Button@SYSTEM_MAPS_TAB_BUTTON:
X: 15
Y: 10
Height: 35
Width: 140
Text: Official Maps
Button@USER_MAPS_TAB_BUTTON:
X: 15
Y: 10
Height: 35
Width: 140
Text: Custom Maps
Container@SYSTEM_MAPS_TAB:
X: 15 X: 15
Y: 45 Y: 45
Width: PARENT_RIGHT - 30 Width: PARENT_RIGHT - 30
Height: 440 Height: 440
Children: Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
Container@USER_MAPS_TAB:
X: 15
Y: 45
Width: PARENT_RIGHT - 30
Height: 440
Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
ScrollItem@MAP_TEMPLATE: ScrollItem@MAP_TEMPLATE:
Width: 183 Width: 183
Height: 232 Height: 232
@@ -100,6 +126,18 @@ Container@MAPCHOOSER_PANEL:
Width: 140 Width: 140
Height: 35 Height: 35
Text: Random Text: Random
Button@DELETE_MAP_BUTTON:
X: PARENT_RIGHT - 300 - WIDTH
Y: 499
Width: 140
Height: 35
Text: Delete Map
Button@DELETE_ALL_MAPS_BUTTON:
X: PARENT_RIGHT - 450 - WIDTH
Y: 499
Width: 140
Height: 35
Text: Delete All Maps
Button@BUTTON_OK: Button@BUTTON_OK:
Key: return Key: return
X: PARENT_RIGHT - WIDTH X: PARENT_RIGHT - WIDTH

View File

@@ -13,12 +13,40 @@ Background@MAPCHOOSER_PANEL:
Height: 20 Height: 20
Text: Choose Map Text: Choose Map
Font: Bold Font: Bold
ScrollPanel@MAP_LIST: Button@SYSTEM_MAPS_TAB_BUTTON:
X: 20
Y: 43
Height: 25
Width: 140
Text: Official Maps
Font: Bold
Button@USER_MAPS_TAB_BUTTON:
X: 160
Y: 43
Height: 25
Width: 140
Text: Custom Maps
Font: Bold
Container@SYSTEM_MAPS_TAB:
X: 20 X: 20
Y: 67 Y: 67
Width: PARENT_RIGHT - 40 Width: PARENT_RIGHT - 40
Height: 481 Height: 481
Children: Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
Container@USER_MAPS_TAB:
X: 20
Y: 67
Width: PARENT_RIGHT - 40
Height: 481
Children:
ScrollPanel@MAP_LIST:
Width: PARENT_RIGHT
Height: PARENT_BOTTOM
Children:
ScrollItem@MAP_TEMPLATE: ScrollItem@MAP_TEMPLATE:
Width: 194 Width: 194
Height: 243 Height: 243
@@ -89,6 +117,20 @@ Background@MAPCHOOSER_PANEL:
Height: 25 Height: 25
Text: Random Map Text: Random Map
Font: Bold Font: Bold
Button@DELETE_MAP_BUTTON:
X: 160
Y: PARENT_BOTTOM - 45
Width: 120
Height: 25
Text: Delete Map
Font: Bold
Button@DELETE_ALL_MAPS_BUTTON:
X: 300
Y: PARENT_BOTTOM - 45
Width: 120
Height: 25
Text: Delete All Maps
Font: Bold
Button@BUTTON_OK: Button@BUTTON_OK:
X: PARENT_RIGHT - 270 X: PARENT_RIGHT - 270
Y: PARENT_BOTTOM - 45 Y: PARENT_BOTTOM - 45