Add Generate Map tab to the lobby map chooser.

This commit is contained in:
Paul Chote
2025-05-11 20:44:39 +01:00
committed by Gustas Kažukauskas
parent 979483b63c
commit 6ffd786d5b
11 changed files with 875 additions and 8 deletions

View File

@@ -0,0 +1,156 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets
{
public class GeneratedMapPreviewWidget : Widget
{
public readonly bool ShowSpawnPoints = true;
readonly Sprite spawnUnclaimed;
readonly SpriteFont spawnFont;
readonly Color spawnColor, spawnContrastColor;
readonly int2 spawnLabelOffset;
Sheet mapSheet;
Sprite mapSprite;
Rectangle mapRect;
public GeneratedMapPreviewWidget()
{
spawnUnclaimed = ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed");
spawnFont = Game.Renderer.Fonts[ChromeMetrics.Get<string>("SpawnFont")];
spawnColor = ChromeMetrics.Get<Color>("SpawnColor");
spawnContrastColor = ChromeMetrics.Get<Color>("SpawnContrastColor");
spawnLabelOffset = ChromeMetrics.Get<int2>("SpawnLabelOffset");
}
protected GeneratedMapPreviewWidget(GeneratedMapPreviewWidget other)
: base(other)
{
ShowSpawnPoints = other.ShowSpawnPoints;
spawnUnclaimed = ChromeProvider.GetImage("lobby-bits", "spawn-unclaimed");
spawnFont = Game.Renderer.Fonts[ChromeMetrics.Get<string>("SpawnFont")];
spawnColor = ChromeMetrics.Get<Color>("SpawnColor");
spawnContrastColor = ChromeMetrics.Get<Color>("SpawnContrastColor");
spawnLabelOffset = ChromeMetrics.Get<int2>("SpawnLabelOffset");
}
public override GeneratedMapPreviewWidget Clone() { return new GeneratedMapPreviewWidget(this); }
(int2 Pos, string Label, int2 LabelOffset)[] spawns = [];
public void Update(MapPreview map)
{
Update(map.SpawnPoints, map.Bounds, map.GridType, map.Preview);
}
public void Update(Map map)
{
var spawnPoints = map.ActorDefinitions
.Where(d => d.Value.Value == "mpspawn")
.Select(kv => new ActorReference(kv.Value.Value, kv.Value.ToDictionary()).Get<LocationInit>().Value);
Update(spawnPoints, map.Bounds, map.Grid.Type, new Png(map.Package.GetStream("map.png")));
}
void Update(IEnumerable<CPos> spawnPoints, Rectangle bounds, MapGridType gridType, Png preview)
{
if (mapSheet == null || mapSheet.Size.Width < preview.Width || mapSheet.Size.Height < preview.Height)
{
mapSheet?.Dispose();
mapSheet = new Sheet(SheetType.BGRA, new Size(preview.Width, preview.Height).NextPowerOf2());
}
var spriteRect = new Rectangle(0, 0, preview.Width, preview.Height);
mapSprite = new Sprite(mapSheet, spriteRect, TextureChannel.RGBA);
OpenRA.Graphics.Util.FastCopyIntoSprite(mapSprite, preview);
mapSheet.CommitBufferedData();
// Update map rect
var previewScale = Math.Min(RenderBounds.Width * 1f / spriteRect.Width, RenderBounds.Height * 1f / spriteRect.Height);
var w = (int)(previewScale * spriteRect.Width);
var h = (int)(previewScale * spriteRect.Height);
var x = RenderBounds.X + (RenderBounds.Width - w) / 2;
var y = RenderBounds.Y + (RenderBounds.Height - h) / 2;
mapRect = new Rectangle(x, y, w, h);
if (ShowSpawnPoints)
{
var s = new List<(int2, string, int2)>();
foreach (var p in spawnPoints)
{
var pos = ConvertToPreview(p, bounds, gridType, previewScale);
var sprite = spawnUnclaimed;
var offset = sprite.Size.XY.ToInt2() / 2;
WidgetUtils.DrawSprite(sprite, pos - offset);
var number = Convert.ToChar('A' + s.Count).ToString();
var textOffset = spawnFont.Measure(number) / 2 + spawnLabelOffset;
s.Add((pos, number, textOffset));
}
spawns = s.ToArray();
}
}
public void Clear()
{
mapSprite = null;
}
public int2 ConvertToPreview(CPos cell, Rectangle bounds, MapGridType gridType, float previewScale)
{
var point = cell.ToMPos(gridType);
var cellWidth = gridType == MapGridType.RectangularIsometric ? 2 : 1;
var dx = (int)(previewScale * cellWidth * (point.U - bounds.Left));
var dy = (int)(previewScale * (point.V - bounds.Top));
// Odd rows are shifted right by 1px
if ((point.V & 1) == 1)
dx++;
return new int2(mapRect.X + dx, mapRect.Y + dy);
}
public override void Draw()
{
if (mapSprite == null)
return;
WidgetUtils.DrawSprite(mapSprite, mapRect.Location, mapRect.Size);
var offset = spawnUnclaimed.Size.XY.ToInt2() / 2;
foreach (var (pos, label, labelOffset) in spawns)
{
WidgetUtils.DrawSprite(spawnUnclaimed, pos - offset);
spawnFont.DrawTextWithContrast(label, pos - labelOffset, spawnColor, spawnContrastColor, 1);
}
}
public override void Removed()
{
base.Removed();
mapSheet?.Dispose();
mapSheet = null;
}
public bool Loaded => mapSprite != null;
}
}

View File

@@ -98,6 +98,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
MapPreview map;
Session.MapStatus mapStatus;
MapGenerationArgs lastGeneratedMap;
bool chatEnabled;
bool disableTeamChat;
@@ -249,16 +250,30 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Game.Settings.Save();
});
var onSelectGenerated = new Action<MapGenerationArgs>(args =>
{
if (args.Uid == map.Uid)
return;
lastGeneratedMap = args;
orderManager.IssueOrder(Order.FromTargetString("GenerateMap", args.Serialize(), true));
orderManager.IssueOrder(Order.Command("map " + args.Uid));
Game.Settings.Server.Map = args.Uid;
Game.Settings.Save();
});
// Check for updated maps, if the user has edited a map we'll preselect it for them
modData.MapCache.UpdateMaps();
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", modData.MapCache.PickLastModifiedMap(MapVisibility.Lobby) ?? map.Uid },
{ "initialGeneratedMap", lastGeneratedMap },
{ "remoteMapPool", orderManager.ServerMapPool },
{ "initialTab", MapClassification.System },
{ "onExit", modData.MapCache.UpdateMaps },
{ "onSelect", Game.IsHost ? onSelect : null },
{ "onSelectGenerated", Game.IsHost ? onSelectGenerated : null },
{ "filter", MapVisibility.Lobby },
});
};

View File

@@ -211,10 +211,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Game.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", null },
{ "initialGeneratedMap", (MapGenerationArgs)null },
{ "remoteMapPool", null },
{ "initialTab", MapClassification.User },
{ "onExit", () => SwitchMenu(MenuType.MapEditor) },
{ "onSelect", onSelect },
{ "onSelectGenerated", null },
{ "filter", MapVisibility.Lobby | MapVisibility.Shellmap | MapVisibility.MissionSelector },
});
};

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Widgets;
@@ -91,6 +92,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[FluentReference]
const string RemoteMapsTab = "button-mapchooser-remote-maps-tab";
[FluentReference]
const string GeneratedMapsTab = "button-mapchooser-generated-maps-tab";
public static string MapSizeLabel(Size size)
{
var area = size.Width * size.Height;
@@ -124,6 +128,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
string selectedUid;
readonly Action<string> onSelect;
MapGenerationArgs generatedMap;
string category;
string mapFilter;
@@ -131,8 +136,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Func<MapPreview, long> orderByFunc;
[ObjectCreator.UseCtor]
internal MapChooserLogic(Widget widget, ModData modData, string initialMap, HashSet<string> remoteMapPool,
MapClassification initialTab, Action onExit, Action<string> onSelect, MapVisibility filter)
internal MapChooserLogic(Widget widget, ModData modData, string initialMap, MapGenerationArgs initialGeneratedMap, HashSet<string> remoteMapPool,
MapClassification initialTab, Action onExit, Action<string> onSelect, Action<MapGenerationArgs> onSelectGenerated, MapVisibility filter)
{
this.widget = widget;
this.modData = modData;
@@ -145,13 +150,20 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var approving = new Action(() =>
{
Ui.CloseWindow();
onSelect?.Invoke(selectedUid);
if (currentTab == MapClassification.Generated && generatedMap != null)
onSelectGenerated?.Invoke(generatedMap);
else
onSelect?.Invoke(selectedUid);
});
var canceling = new Action(() => { Ui.CloseWindow(); onExit(); });
var okButton = widget.Get<ButtonWidget>("BUTTON_OK");
okButton.Disabled = this.onSelect == null;
if (onSelect != null)
okButton.IsDisabled = () => currentTab == MapClassification.Generated && generatedMap == null;
else
okButton.Disabled = true;
okButton.OnClick = approving;
widget.Get<ButtonWidget>("BUTTON_CANCEL").OnClick = canceling;
@@ -162,6 +174,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SetupOrderByDropdown();
var filterContainer = widget.GetOrNull("FILTER_ORDER_CONTROLS");
if (filterContainer != null)
filterContainer.IsVisible = () => currentTab != MapClassification.Generated;
var mapFilterInput = widget.GetOrNull<TextFieldWidget>("MAPFILTER_INPUT");
if (mapFilterInput != null)
{
@@ -196,6 +212,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
scrollpanels[currentTab].ScrollToItem(uid, smooth: true);
};
randomMapButton.IsDisabled = () => visibleMaps == null || visibleMaps.Length == 0;
randomMapButton.IsVisible = () => currentTab != MapClassification.Generated;
}
var deleteMapButton = widget.Get<ButtonWidget>("DELETE_MAP_BUTTON");
@@ -249,6 +266,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SetupMapPanel(MapClassification.System, "SYSTEM_MAPS_TAB");
SetupMapPanel(MapClassification.Remote, "REMOTE_MAPS_TAB");
var hasGenerator = modData.DefaultRules.Actors[SystemActors.EditorWorld].HasTraitInfo<IEditorMapGeneratorInfo>();
if (onSelectGenerated != null && hasGenerator)
SetupGenerateMapPanel(MapClassification.Generated, "GENERATE_MAP_TAB", initialGeneratedMap);
// System and user map tabs are hidden when the server forces a restricted pool
if (remoteMapPool != null)
{
@@ -260,8 +281,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
tabLabels[MapClassification.System] = SystemMapsTab;
tabLabels[MapClassification.User] = UserMapsTab;
if (onSelectGenerated != null && hasGenerator)
tabLabels[MapClassification.Generated] = GeneratedMapsTab;
if (initialMap == null && tabMaps.TryGetValue(initialTab, out var map) && map.Length > 0)
if (initialMap != null && modData.MapCache[initialMap].Class == MapClassification.Generated && onSelectGenerated != null && hasGenerator)
{
currentTab = MapClassification.Generated;
selectedUid = modData.MapCache.ChooseInitialMap(null, Game.CosmeticRandom);
}
else if (initialMap == null && tabMaps.TryGetValue(initialTab, out var map) && map.Length > 0)
{
var uid = map.Select(mp => mp.Uid).First();
selectedUid = Game.ModData.MapCache.ChooseInitialMap(uid, Game.CosmeticRandom);
@@ -286,7 +314,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void RefreshMaps(MapClassification tab)
{
if (tab != MapClassification.Remote)
if (tab == MapClassification.System || tab == MapClassification.User)
tabMaps[tab] = modData.MapCache.Where(m => m.Status == MapStatus.Available &&
m.Class == tab && (m.Visibility & filter) != 0).ToArray();
else if (remoteMapPool != null)
@@ -358,6 +386,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
RefreshMaps(tab);
}
void SetupGenerateMapPanel(MapClassification tab, string tabContainerName, MapGenerationArgs initialSettings)
{
var tabContainer = widget.Get<ContainerWidget>(tabContainerName);
tabContainer.IsVisible = () => currentTab == tab;
Ui.LoadWidget("MAPCHOOSER_GENERATE_PANEL", tabContainer, new WidgetArgs
{
{ "modData", modData },
{ "initialSettings", initialSettings },
{ "onGenerate", (Action<MapGenerationArgs>)(data => generatedMap = data) }
});
}
void SetupGameModeDropdown(MapClassification tab, DropDownButtonWidget gameModeDropdown)
{
if (gameModeDropdown != null)
@@ -443,6 +483,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void EnumerateMaps(MapClassification tab)
{
if (tab == MapClassification.Generated)
return;
if (!int.TryParse(mapFilter, out var playerCountFilter))
playerCountFilter = -1;
@@ -532,7 +575,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{
modData.MapCache[map].Delete();
if (selectedUid == map)
selectedUid = Game.ModData.MapCache.ChooseInitialMap(tabMaps[currentTab].Select(mp => mp.Uid).FirstOrDefault(),
selectedUid = modData.MapCache.ChooseInitialMap(tabMaps[currentTab].Select(mp => mp.Uid).FirstOrDefault(),
Game.CosmeticRandom);
}
catch (Exception ex)
@@ -569,7 +612,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
foreach (var map in maps)
DeleteMap(map);
after?.Invoke(Game.ModData.MapCache.ChooseInitialMap(null, Game.CosmeticRandom));
after?.Invoke(modData.MapCache.ChooseInitialMap(null, Game.CosmeticRandom));
},
confirmText: DeleteAllMapsAccept,
onCancel: () => { });

View File

@@ -0,0 +1,424 @@
#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using OpenRA.FileSystem;
using OpenRA.Mods.Common.MapGenerator;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class MapGeneratorLogic : ChromeLogic
{
[FluentReference]
const string Tileset = "label-mapchooser-random-map-tileset";
[FluentReference]
const string MapSize = "label-mapchooser-random-map-size";
[FluentReference]
const string RandomMap = "label-mapchooser-random-map-title";
[FluentReference]
const string Generating = "label-mapchooser-random-map-generating";
[FluentReference]
const string GenerationFailed = "label-mapchooser-random-map-error";
[FluentReference("players")]
const string Players = "label-player-count";
[FluentReference("author")]
const string CreatedBy = "label-created-by";
[FluentReference]
const string MapSizeSmall = "label-map-size-small";
[FluentReference]
const string MapSizeMedium = "label-map-size-medium";
[FluentReference]
const string MapSizeLarge = "label-map-size-large";
[FluentReference]
const string MapSizeHuge = "label-map-size-huge";
public static readonly IReadOnlyDictionary<string, int2> MapSizes = new Dictionary<string, int2>()
{
{ MapSizeSmall, new int2(48, 60) },
{ MapSizeMedium, new int2(60, 90) },
{ MapSizeLarge, new int2(90, 120) },
{ MapSizeHuge, new int2(120, 160) },
};
readonly ModData modData;
readonly IEditorMapGeneratorInfo generator;
readonly IMapGeneratorSettings settings;
readonly Action<MapGenerationArgs> onGenerate;
readonly GeneratedMapPreviewWidget preview;
readonly ScrollPanelWidget settingsPanel;
readonly Widget checkboxSettingTemplate;
readonly Widget textSettingTemplate;
readonly Widget dropdownSettingTemplate;
readonly Widget tilesetSetting;
readonly Widget sizeSetting;
ITerrainInfo selectedTerrain;
string selectedSize;
Size size;
volatile bool generating;
volatile bool failed;
[ObjectCreator.UseCtor]
internal MapGeneratorLogic(Widget widget, ModData modData, MapGenerationArgs initialSettings, Action<MapGenerationArgs> onGenerate)
{
this.modData = modData;
this.onGenerate = onGenerate;
generator = modData.DefaultRules.Actors[SystemActors.EditorWorld].TraitInfos<IEditorMapGeneratorInfo>().First();
settings = generator.GetSettings();
preview = widget.Get<GeneratedMapPreviewWidget>("PREVIEW");
widget.Get("ERROR").IsVisible = () => failed;
var title = new CachedTransform<string, string>(id => FluentProvider.GetMessage(id));
var previewTitleLabel = widget.Get<LabelWidget>("TITLE");
previewTitleLabel.GetText = () => title.Update(generating ? Generating : failed ? GenerationFailed : RandomMap);
var previewDetailsLabel = widget.GetOrNull<LabelWidget>("DETAILS");
if (previewDetailsLabel != null)
{
// The default "Conquest" label is hardcoded in Map.cs
var desc = new CachedTransform<int, string>(p => "Conquest " + FluentProvider.GetMessage(Players, "players", p));
var playersOption = settings.Options.FirstOrDefault(o => o.Id == "Players") as MapGeneratorMultiIntegerChoiceOption;
previewDetailsLabel.GetText = () => desc.Update(playersOption?.Value ?? 0);
previewDetailsLabel.IsVisible = () => !failed;
}
var previewAuthorLabel = widget.GetOrNull<LabelWithTooltipWidget>("AUTHOR");
if (previewAuthorLabel != null)
{
var desc = FluentProvider.GetMessage(CreatedBy, "author", FluentProvider.GetMessage(generator.Name));
previewAuthorLabel.GetText = () => desc;
previewAuthorLabel.IsVisible = () => !failed;
}
var previewSizeLabel = widget.GetOrNull<LabelWidget>("SIZE");
if (previewSizeLabel != null)
{
var desc = new CachedTransform<Size, string>(MapChooserLogic.MapSizeLabel);
previewSizeLabel.IsVisible = () => !failed;
previewSizeLabel.GetText = () => desc.Update(size);
}
settingsPanel = widget.Get<ScrollPanelWidget>("SETTINGS_PANEL");
checkboxSettingTemplate = settingsPanel.Get<Widget>("CHECKBOX_TEMPLATE");
textSettingTemplate = settingsPanel.Get<Widget>("TEXT_TEMPLATE");
dropdownSettingTemplate = settingsPanel.Get<Widget>("DROPDOWN_TEMPLATE");
settingsPanel.Layout = new GridLayout(settingsPanel);
// Tileset and map size are handled outside the generator logic so must be created manually
var validTerrainInfos = generator.Tilesets.Select(t => modData.DefaultTerrainInfo[t]).ToList();
var tilesetLabel = FluentProvider.GetMessage(Tileset);
tilesetSetting = dropdownSettingTemplate.Clone();
tilesetSetting.Get<LabelWidget>("LABEL").GetText = () => tilesetLabel;
var label = new CachedTransform<ITerrainInfo, string>(ti => FluentProvider.GetMessage(ti.Name));
var tilesetDropdown = tilesetSetting.Get<DropDownButtonWidget>("DROPDOWN");
tilesetDropdown.GetText = () => label.Update(selectedTerrain);
tilesetDropdown.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(ITerrainInfo terrainInfo, ScrollItemWidget template)
{
bool IsSelected() => terrainInfo == selectedTerrain;
void OnClick()
{
selectedTerrain = terrainInfo;
RefreshSettings();
GenerateMap();
}
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var itemLabel = FluentProvider.GetMessage(terrainInfo.Name);
item.Get<LabelWidget>("LABEL").GetText = () => itemLabel;
return item;
}
tilesetDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", validTerrainInfos.Count * 30, validTerrainInfos, SetupItem);
};
var sizeLabel = FluentProvider.GetMessage(MapSize);
sizeSetting = dropdownSettingTemplate.Clone();
sizeSetting.Get<LabelWidget>("LABEL").GetText = () => sizeLabel;
var sizeDropdown = sizeSetting.Get<DropDownButtonWidget>("DROPDOWN");
var sizeDropdownLabel = new CachedTransform<string, string>(s => FluentProvider.GetMessage(s));
sizeDropdown.GetText = () => sizeDropdownLabel.Update(selectedSize);
sizeDropdown.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(string size, ScrollItemWidget template)
{
bool IsSelected() => size == selectedSize;
void OnClick()
{
selectedSize = size;
RandomizeSize();
GenerateMap();
}
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var label = FluentProvider.GetMessage(size);
item.Get<LabelWidget>("LABEL").GetText = () => label;
return item;
}
sizeDropdown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", MapSizes.Count * 30, MapSizes.Keys, SetupItem);
};
var generateButton = widget.Get<ButtonWidget>("BUTTON_GENERATE");
generateButton.IsDisabled = () => generating;
generateButton.OnClick = () =>
{
settings.Randomize(Game.CosmeticRandom);
RandomizeSize();
GenerateMap();
};
selectedSize = MapSizes.Keys.Skip(1).First();
if (initialSettings != null)
{
selectedTerrain = modData.DefaultTerrainInfo[initialSettings.Tileset];
size = initialSettings.Size;
foreach (var kv in MapSizes)
if (kv.Value.X > size.Width && kv.Value.Y <= size.Width)
selectedSize = kv.Key;
settings.Initialize(initialSettings);
RefreshSettings();
var map = modData.MapCache[initialSettings.Uid];
if (map.Status == MapStatus.Available)
{
preview.Update(map);
onGenerate(initialSettings);
}
else
GenerateMap();
}
else
{
selectedTerrain = validTerrainInfos[0];
settings.Randomize(Game.CosmeticRandom);
RandomizeSize();
RefreshSettings();
GenerateMap();
}
}
void RandomizeSize()
{
var sizeRange = MapSizes[selectedSize];
var width = Game.CosmeticRandom.Next(sizeRange.X, sizeRange.Y);
size = new Size(width + 2, width + 2);
}
void RefreshSettings()
{
settingsPanel.RemoveChildren();
tilesetSetting.Bounds = sizeSetting.Bounds = dropdownSettingTemplate.Bounds;
settingsPanel.AddChild(tilesetSetting);
settingsPanel.AddChild(sizeSetting);
var playerCount = settings.PlayerCount;
foreach (var o in settings.Options)
{
if (o.Id == "Seed")
continue;
Widget settingWidget = null;
switch (o)
{
case MapGeneratorBooleanOption bo:
{
settingWidget = checkboxSettingTemplate.Clone();
var checkboxWidget = settingWidget.Get<CheckboxWidget>("CHECKBOX");
var label = FluentProvider.GetMessage(bo.Label);
checkboxWidget.GetText = () => label;
checkboxWidget.IsChecked = () => bo.Value;
checkboxWidget.OnClick = () =>
{
bo.Value ^= true;
GenerateMap();
};
break;
}
case MapGeneratorIntegerOption io:
{
settingWidget = textSettingTemplate.Clone();
var labelWidget = settingWidget.Get<LabelWidget>("LABEL");
var label = FluentProvider.GetMessage(io.Label);
labelWidget.GetText = () => label;
var textFieldWidget = settingWidget.Get<TextFieldWidget>("INPUT");
textFieldWidget.Type = TextFieldType.Integer;
textFieldWidget.Text = FieldSaver.FormatValue(io.Value);
textFieldWidget.OnTextEdited = () =>
{
var valid = int.TryParse(textFieldWidget.Text, out io.Value);
textFieldWidget.IsValid = () => valid;
};
textFieldWidget.OnEscKey = _ => { textFieldWidget.YieldKeyboardFocus(); return true; };
textFieldWidget.OnEnterKey = _ => { textFieldWidget.YieldKeyboardFocus(); return true; };
textFieldWidget.OnLoseFocus = GenerateMap;
break;
}
case MapGeneratorMultiIntegerChoiceOption mio:
{
settingWidget = dropdownSettingTemplate.Clone();
var labelWidget = settingWidget.Get<LabelWidget>("LABEL");
var label = FluentProvider.GetMessage(mio.Label);
labelWidget.GetText = () => label;
var labelCache = new CachedTransform<int, string>(v => FieldSaver.FormatValue(v));
var dropDownWidget = settingWidget.Get<DropDownButtonWidget>("DROPDOWN");
dropDownWidget.GetText = () => labelCache.Update(mio.Value);
dropDownWidget.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(int choice, ScrollItemWidget template)
{
bool IsSelected() => choice == mio.Value;
void OnClick()
{
mio.Value = choice;
if (o.Id == "Players")
RefreshSettings();
GenerateMap();
}
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var itemLabel = FieldSaver.FormatValue(choice);
item.Get<LabelWidget>("LABEL").GetText = () => itemLabel;
item.GetTooltipText = null;
return item;
}
dropDownWidget.ShowDropDown("LABEL_DROPDOWN_WITH_TOOLTIP_TEMPLATE", mio.Choices.Length * 30, mio.Choices, SetupItem);
};
break;
}
case MapGeneratorMultiChoiceOption mo:
{
var validChoices = mo.ValidChoices(selectedTerrain, playerCount);
if (!validChoices.Contains(mo.Value))
mo.Value = mo.Default?.FirstOrDefault(validChoices.Contains) ?? validChoices.FirstOrDefault();
if (mo.Label != null && validChoices.Count > 0)
{
settingWidget = dropdownSettingTemplate.Clone();
var labelWidget = settingWidget.Get<LabelWidget>("LABEL");
var label = FluentProvider.GetMessage(mo.Label);
labelWidget.GetText = () => label;
var labelCache = new CachedTransform<string, string>(v => FluentProvider.GetMessage(mo.Choices[v].Label + ".label"));
var dropDownWidget = settingWidget.Get<DropDownButtonWidget>("DROPDOWN");
dropDownWidget.GetText = () => labelCache.Update(mo.Value);
dropDownWidget.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(string choice, ScrollItemWidget template)
{
bool IsSelected() => choice == mo.Value;
void OnClick()
{
mo.Value = choice;
GenerateMap();
}
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var itemLabel = FluentProvider.GetMessage(mo.Choices[choice].Label + ".label");
item.Get<LabelWidget>("LABEL").GetText = () => itemLabel;
if (FluentProvider.TryGetMessage(mo.Choices[choice].Label + ".description", out var desc))
item.GetTooltipText = () => desc;
else
item.GetTooltipText = null;
return item;
}
dropDownWidget.ShowDropDown("LABEL_DROPDOWN_WITH_TOOLTIP_TEMPLATE", validChoices.Count * 30, validChoices, SetupItem);
};
}
break;
}
default:
throw new NotImplementedException($"Unhandled MapGeneratorOption type {o.GetType().Name}");
}
if (settingWidget == null)
continue;
settingWidget.IsVisible = () => true;
settingsPanel.AddChild(settingWidget);
}
}
void GenerateMap()
{
generating = true;
onGenerate(null);
failed = false;
preview.Clear();
Task.Run(() =>
{
for (var i = 0; i < 5; i++)
{
try
{
var args = settings.Compile(selectedTerrain, size);
var map = generator.Generate(modData, args);
// Map UID and preview image are generated on save
var package = new ZipFileLoader.ReadWriteZipFile();
map.Save(package);
args.Uid = map.Uid;
Game.RunAfterTick(() =>
{
preview.Update(map);
onGenerate(args);
generating = false;
});
return;
}
catch (Exception)
{
// Ignore the exception
}
}
failed = true;
generating = false;
});
}
}
}

View File

@@ -98,10 +98,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Ui.OpenWindow("MAPCHOOSER_PANEL", new WidgetArgs()
{
{ "initialMap", map.Uid },
{ "initialGeneratedMap", (MapGenerationArgs)null },
{ "remoteMapPool", null },
{ "initialTab", MapClassification.System },
{ "onExit", () => modData.MapCache.UpdateMaps() },
{ "onSelect", (Action<string>)(uid => map = modData.MapCache[uid]) },
{ "onSelectGenerated", null },
{ "filter", MapVisibility.Lobby },
{ "onStart", () => { } }
});

View File

@@ -63,6 +63,9 @@ Container@MAPCHOOSER_PANEL:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
ItemSpacing: 1
Container@GENERATE_MAP_TAB:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT + 30
ScrollItem@MAP_TEMPLATE:
Width: 210
Height: 262
@@ -186,3 +189,103 @@ Container@MAPCHOOSER_PANEL:
Height: 35
Text: button-bg-ok
TooltipContainer@TOOLTIP_CONTAINER:
Container@MAPCHOOSER_GENERATE_PANEL:
Logic: MapGeneratorLogic
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
Children:
Background@MAP_BG:
X: PARENT_WIDTH - WIDTH
Width: 370
Height: 370
Background: panel-gray
Children:
GeneratedMapPreview@PREVIEW:
X: 1
Y: 1
Width: PARENT_WIDTH - 2
Height: PARENT_HEIGHT - 2
Label@TITLE:
X: PARENT_WIDTH - WIDTH
Y: 375
Width: 370
Height: 24
Align: Center
Text: label-mapchooser-random-map-title
Font: Bold
Label@ERROR:
X: PARENT_WIDTH - WIDTH
Y: 405
Width: 370
Height: 24
Align: Center
Text: label-mapchooser-random-map-error-desc
Label@DETAILS:
X: PARENT_WIDTH - WIDTH
Y: 405
Width: 370
Height: 24
Align: Center
LabelWithTooltip@AUTHOR:
X: PARENT_WIDTH - WIDTH
Y: 425
Width: 370
Height: 24
Align: Center
Label@SIZE:
X: PARENT_WIDTH - WIDTH
Y: 445
Width: 370
Height: 24
Align: Center
Button@BUTTON_GENERATE:
X: PARENT_WIDTH - 135 - WIDTH
Y: PARENT_HEIGHT + 14
Width: 140
Height: 35
Text: button-mapchooser-random-map-generate
ScrollPanel@SETTINGS_PANEL:
TopBottomSpacing: 5
Width: 485
Height: PARENT_HEIGHT
Children:
Container@CHECKBOX_TEMPLATE:
Width: 230
Height: 30
Children:
Checkbox@CHECKBOX:
X: 10
Y: 5
Width: PARENT_WIDTH - 20
Height: 20
Container@TEXT_TEMPLATE:
Width: 230
Height: 50
Children:
LabelForInput@LABEL:
X: 10
Width: PARENT_WIDTH - 20
Height: 20
For: INPUT
TextField@INPUT:
X: 10
Y: 20
Width: PARENT_WIDTH - 20
Height: 25
Container@DROPDOWN_TEMPLATE:
Width: 230
Height: 50
Children:
LabelForInput@LABEL:
X: 10
Width: PARENT_WIDTH - 20
Height: 20
For: DROPDOWN
DropDownButton@DROPDOWN:
X: 10
Y: 20
Width: PARENT_WIDTH - 20
Height: 25
PanelRoot: GENERATOR_DROPDOWN_PANEL_ROOT # ensure that tooltips for the options are on top of the dropdown panel
Container@GENERATOR_DROPDOWN_PANEL_ROOT:

View File

@@ -534,6 +534,14 @@ button-bg-delete-map = Delete Map
button-bg-delete-all-maps = Delete All Maps
button-bg-ok = Ok
label-mapchooser-random-map-title = Random Map
label-mapchooser-random-map-generating = Generating...
label-mapchooser-random-map-error = Map Generation Failed
button-mapchooser-random-map-generate = Generate
label-mapchooser-random-map-tileset = Environment:
label-mapchooser-random-map-size = Map Size:
label-mapchooser-random-map-error-desc = Adjust the settings to try again.
## missionbrowser.yaml
button-missionbrowser-panel-mission-info = Mission Info
button-missionbrowser-panel-mission-options = Options

View File

@@ -57,6 +57,9 @@ Background@MAPCHOOSER_PANEL:
ScrollPanel@MAP_LIST:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
Container@GENERATE_MAP_TAB:
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
ScrollItem@MAP_TEMPLATE:
Width: 208
Height: 266
@@ -185,3 +188,105 @@ Background@MAPCHOOSER_PANEL:
Font: Bold
Key: escape
TooltipContainer@TOOLTIP_CONTAINER:
Container@MAPCHOOSER_GENERATE_PANEL:
Logic: MapGeneratorLogic
Width: PARENT_WIDTH
Height: PARENT_HEIGHT
Children:
Background@MAP_BG:
X: PARENT_WIDTH - WIDTH
Width: 370
Height: 370
Background: dialog3
Children:
GeneratedMapPreview@PREVIEW:
X: 1
Y: 1
Width: PARENT_WIDTH - 2
Height: PARENT_HEIGHT - 2
Label@TITLE:
X: PARENT_WIDTH - WIDTH
Y: 375
Width: 370
Height: 24
Align: Center
Text: label-mapchooser-random-map-title
Font: Bold
Label@ERROR:
X: PARENT_WIDTH - WIDTH
Y: 405
Width: 370
Height: 24
Align: Center
Text: label-mapchooser-random-map-error-desc
Label@DETAILS:
X: PARENT_WIDTH - WIDTH
Y: 405
Width: 370
Height: 24
Align: Center
LabelWithTooltip@AUTHOR:
X: PARENT_WIDTH - WIDTH
Y: 425
Width: 370
Height: 24
Align: Center
Label@SIZE:
X: PARENT_WIDTH - WIDTH
Y: 445
Width: 370
Height: 24
Align: Center
Button@BUTTON_GENERATE:
X: PARENT_WIDTH - 380
Y: PARENT_HEIGHT + 40
Width: 120
Height: 25
Text: button-mapchooser-random-map-generate
Font: Bold
ScrollPanel@SETTINGS_PANEL:
TopBottomSpacing: 5
Width: 485
Height: PARENT_HEIGHT + 30
Children:
Container@CHECKBOX_TEMPLATE:
Width: 230
Height: 30
Children:
Checkbox@CHECKBOX:
X: 10
Y: 5
Width: PARENT_WIDTH - 20
Height: 20
Container@TEXT_TEMPLATE:
Width: 230
Height: 50
Children:
LabelForInput@LABEL:
X: 10
Width: PARENT_WIDTH - 20
Height: 20
For: INPUT
TextField@INPUT:
X: 10
Y: 20
Width: PARENT_WIDTH - 20
Height: 25
Container@DROPDOWN_TEMPLATE:
Width: 230
Height: 50
Children:
LabelForInput@LABEL:
X: 10
Width: PARENT_WIDTH - 20
Height: 20
For: DROPDOWN
DropDownButton@DROPDOWN:
X: 10
Y: 20
Width: PARENT_WIDTH - 20
Height: 25
PanelRoot: GENERATOR_DROPDOWN_PANEL_ROOT # ensure that tooltips for the options are on top of the dropdown panel
Container@GENERATOR_DROPDOWN_PANEL_ROOT:

View File

@@ -353,6 +353,14 @@ button-mapchooser-panel-delete-map = Delete Map
button-mapchooser-panel-delete-all-maps = Delete All Maps
button-mapchooser-panel-ok = Ok
label-mapchooser-random-map-title = Random Map
label-mapchooser-random-map-generating = Generating...
label-mapchooser-random-map-error = Map Generation Failed
button-mapchooser-random-map-generate = Generate
label-mapchooser-random-map-tileset = Environment:
label-mapchooser-random-map-size = Map Size:
label-mapchooser-random-map-error-desc = Adjust the settings to try again.
## missionbrowser.yaml
button-missionbrowser-panel-start-briefing-video = Watch Briefing
button-missionbrowser-panel-stop-briefing-video = Stop Briefing

View File

@@ -514,6 +514,7 @@ options-order-maps =
button-mapchooser-system-maps-tab = Official Maps
button-mapchooser-remote-maps-tab = Server Maps
button-mapchooser-user-maps-tab = Custom Maps
button-mapchooser-generated-maps-tab = Generate Map
## MissionBrowserLogic
dialog-no-video =