Rewrite map generator settings UI logic.

This commit is contained in:
Paul Chote
2025-04-14 09:35:13 +01:00
committed by Gustas Kažukauskas
parent 8f46247dc9
commit 78f660124c
9 changed files with 390 additions and 548 deletions

View File

@@ -13,389 +13,231 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Support;
namespace OpenRA.Mods.Common.MapGenerator namespace OpenRA.Mods.Common.MapGenerator
{ {
public sealed class MapGeneratorSettings public abstract class MapGeneratorOption
{ {
/// <summary>How an option should be treated for UI purposes.</summary> [FieldLoader.Ignore]
public enum UiType public readonly string Id;
public readonly string Label = null;
public readonly int Priority = 0;
protected MapGeneratorOption(string id, MiniYaml yaml)
{ {
Hidden, Id = id;
DropDown, FieldLoader.Load(this, yaml);
Checkbox,
Integer,
Float,
String,
} }
/// <summary>Represents a bunch of settings, along with UI information.</summary> public abstract IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo);
public sealed class Choice
{
/// <summary>Uniquely identifies a Choice within an Option.</summary>
public readonly string Id;
/// <summary>The label to use for UI selection. Post-fluent.</summary> public virtual IEnumerable<string> GetFluentReferences()
{
if (Label != null)
yield return Label;
}
}
public class MapGeneratorBooleanOption : MapGeneratorOption
{
[FieldLoader.Require]
public readonly string Parameter = null;
public readonly bool Default = false;
public bool Value;
public MapGeneratorBooleanOption(string id, MiniYaml yaml)
: base(id, yaml)
{
Value = Default;
}
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
{
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
}
}
public class MapGeneratorIntegerOption : MapGeneratorOption
{
[FieldLoader.Require]
public readonly string Parameter = null;
public readonly int Default = 0;
public int Value;
public MapGeneratorIntegerOption(string id, MiniYaml yaml)
: base(id, yaml)
{
Value = Default;
}
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
{
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
}
}
public class MapGeneratorMultiChoiceOption : MapGeneratorOption
{
public class MapGeneratorDropdownChoice
{
public readonly string Label = null; public readonly string Label = null;
public readonly string[] Tileset = null;
/// <summary>The tooltip to use for UI selection. Post-fluent.</summary> [FieldLoader.LoadUsing(nameof(LoadSettings))]
public readonly string Description = null; [FieldLoader.Require]
public readonly ImmutableList<MiniYamlNode> Settings = null;
/// <summary> static ImmutableList<MiniYamlNode> LoadSettings(MiniYaml yaml)
/// Only offer the Choice for these tilesets. (If null, show for all.)
/// </summary>
public readonly IReadOnlySet<string> Tileset = null;
/// <summary>(Partial) settings to combine into the final overall settings.</summary>
public readonly MiniYaml Settings;
public Choice(string id, MiniYaml my)
{ {
Id = id; return yaml.NodeWithKey("Settings").Value.Nodes.ToImmutableList();
var label = my.NodeWithKeyOrDefault("Label")?.Value.Value;
if (label != null)
{
Label = FluentProvider.GetMessage($"{label}.label");
FluentProvider.TryGetMessage($"{label}.description", out Description);
}
Tileset = my.NodeWithKeyOrDefault("Tileset")?.Value.Value
?.Split(',')
.ToImmutableHashSet();
Settings = my.NodeWithKey("Settings").Value;
}
/// <summary>Create a choice that represents a top-level setting with a given value.</summary>
public Choice(string setting, string value)
{
Id = value;
Label = value;
Description = null;
Tileset = null;
Settings = new MiniYaml(null, [new MiniYamlNode(setting, value)]);
}
public static void DumpFluent(MiniYaml my, List<string> references)
{
var label = my.NodeWithKeyOrDefault("Label")?.Value.Value;
if (label != null)
{
references.Add($"{label}.label");
// Descriptions are optional.
if (FluentProvider.TryGetMessage($"{label}.description", out _))
references.Add($"{label}.description");
}
}
/// <summary>Check whether this choice is permitted for this map.</summary>
public bool Allowed(ITerrainInfo terrainInfo)
{
if (Tileset != null && !Tileset.Contains(terrainInfo.Id))
return false;
return true;
}
/// <summary>For single-setting choices, creates a new choice for that setting with a given value.</summary>
public Choice NewValue(string value)
{
if (Settings.Nodes.Length != 1)
throw new InvalidOperationException("NewValue can only be used on single-setting Choices");
return new(Settings.Nodes[0].Key, value);
} }
} }
public sealed class Option [FieldLoader.LoadUsing(nameof(LoadChoices))]
public readonly Dictionary<string, MapGeneratorDropdownChoice> Choices = null;
static Dictionary<string, MapGeneratorDropdownChoice> LoadChoices(MiniYaml yaml)
{ {
/// <summary>Unique identifier for this Option.</summary> var ret = new Dictionary<string, MapGeneratorDropdownChoice>();
[FieldLoader.Ignore] foreach (var node in yaml.Nodes)
public readonly string Id;
/// <summary>The label to use for UI selection. Post-fluent.</summary>
[FieldLoader.Ignore]
public readonly string Label = null;
/// <summary>
/// <para>For multiple-choice options (dropdowns/checkboxes) holds the allowed Choices.</para>
/// <para>For text entry Options, contains the default value.</para>
/// <para>If this is empty, the map is not compatible with the generator.</para>
/// </summary>
[FieldLoader.Ignore]
public readonly IReadOnlyList<Choice> Choices;
/// <summary>
/// <para>The default Choice for this option.</para>
/// <para>Default will be ignored if it is not valid for the map.</para>
/// </summary>
[FieldLoader.Ignore]
public readonly Choice Default;
public readonly double Min = double.NegativeInfinity;
public readonly double Max = double.PositiveInfinity;
/// <summary>Whether the Option can be randomized.</summary>
public readonly bool Random = false;
/// <summary>How an option should be treated for UI purposes.</summary>
[FieldLoader.Ignore]
public readonly UiType Ui;
/// <summary>Settings layering priority. Higher overrides lower.</summary>
public readonly int Priority = 0;
public Option(string id, MiniYaml my, ITerrainInfo terrainInfo)
{
Id = id;
FieldLoader.Load(this, my);
var label = my.NodeWithKeyOrDefault("Label")?.Value.Value;
if (label != null)
Label = FluentProvider.GetMessage(label);
var choices = new List<Choice>();
Choices = choices;
var integerSetting = my.NodeWithKeyOrDefault("Integer");
var floatSetting = my.NodeWithKeyOrDefault("Float");
var stringSetting = my.NodeWithKeyOrDefault("String");
var checkboxSetting = my.NodeWithKeyOrDefault("Checkbox");
var simpleChoiceSetting = my.NodeWithKeyOrDefault("SimpleChoice");
var textSetting = integerSetting ?? floatSetting ?? stringSetting;
if (textSetting != null)
{
var setting = textSetting.Value.Value;
var value = my.NodeWithKeyOrDefault("Default")?.Value.Value ?? "";
var choice = new Choice(setting, value);
choices.Add(choice);
Default = choice;
Ui =
integerSetting != null ? UiType.Integer :
floatSetting != null ? UiType.Float :
UiType.String;
if (Ui == UiType.Integer)
{
if (Min == double.NegativeInfinity)
Min = int.MinValue;
if (Max == double.PositiveInfinity)
Max = int.MaxValue;
}
}
else if (checkboxSetting != null)
{
var setting = checkboxSetting.Value.Value;
var falseChoice = new Choice(setting, "False");
var trueChoice = new Choice(setting, "True");
choices.Add(falseChoice);
choices.Add(trueChoice);
var enabled = (my.NodeWithKeyOrDefault("Default")?.Value.Value ?? "False") == "True";
Default = enabled ? trueChoice : falseChoice;
Ui = UiType.Checkbox;
}
else
{
if (simpleChoiceSetting != null)
{
var setting = simpleChoiceSetting.Value.Value;
var values = simpleChoiceSetting.Value
.NodeWithKey("Values").Value.Value
.Split(',');
foreach (var value in values)
choices.Add(new Choice(setting, value));
}
else
{
foreach (var node in my.Nodes)
{
var split = node.Key.Split('@');
if (split[0] == "Choice")
{
string choiceId = null;
if (split.Length >= 2)
choiceId = split[1];
var choice = new Choice(choiceId, node.Value);
if (choice.Allowed(terrainInfo))
choices.Add(choice);
}
}
}
if (Choices.Count > 0)
{
var defaultOrder = my.NodeWithKeyOrDefault("Default")?.Value.Value;
if (defaultOrder != null)
{
foreach (var defaultChoice in defaultOrder.Split(','))
{
Default = Choices.FirstOrDefault(choice => choice.Id == defaultChoice);
if (Default != null)
break;
}
if (Default == null)
throw new YamlException($"None of option `{id}`'s default choices `{defaultOrder}` are not valid");
}
else
{
Default = Choices[0];
}
}
Ui = Label == null ? UiType.Hidden : UiType.DropDown;
}
}
public static void DumpFluent(MiniYaml my, List<string> references)
{
var label = my.NodeWithKeyOrDefault("Label")?.Value.Value;
if (label != null)
references.Add(label);
foreach (var node in my.Nodes)
if (node.Key.Split('@')[0] == "Choice")
Choice.DumpFluent(node.Value, references);
}
public bool ValidateChoice(Choice choice)
{
switch (Ui)
{
case UiType.Integer:
{
var valid = long.TryParse(choice.Id, out var value);
if (value < Min || value > Max)
valid = false;
return valid;
}
case UiType.Float:
{
var valid = double.TryParse(choice.Id, out var value);
if (value < Min || value > Max)
valid = false;
return valid;
}
case UiType.String:
return true;
default:
return Choices.Contains(choice);
}
}
public Choice RandomChoice()
{
if (Random)
{
var random = (long)Guid.NewGuid().GetHashCode() - int.MinValue;
switch (Ui)
{
case UiType.Integer:
var intRandom = (int)(random % (long)(Max - Min + 1) + (long)Min);
return Default.NewValue(intRandom.ToStringInvariant());
case UiType.Float:
var floatRandom = (float)((double)0xffffffff * random / (Max - Min) + Min);
return Default.NewValue(floatRandom.ToStringInvariant());
case UiType.Checkbox:
case UiType.DropDown:
if (Choices.Count == 0)
throw new InvalidOperationException($"Map is not compatible with Option `{Id}`");
return Choices[(int)(random % Choices.Count)];
default:
throw new InvalidOperationException($"Option `{Id}` does not have a randomizable type");
}
}
else
{
return Default;
}
}
public bool IsFreeform()
{
switch (Ui)
{
case UiType.Hidden:
case UiType.DropDown:
case UiType.Checkbox:
return false;
case UiType.Integer:
case UiType.Float:
case UiType.String:
return true;
default:
throw new InvalidOperationException("Bad UiType");
}
}
}
public readonly IReadOnlyList<Option> Options;
/// <summary>
/// Parse settings from a MiniYaml definition. Returns null if the map isn't compatible.
/// </summary>
public static MapGeneratorSettings LoadSettings(MiniYaml my, ITerrainInfo terrainInfo)
{
var options = new List<Option>();
foreach (var node in my.Nodes)
{ {
var split = node.Key.Split('@'); var split = node.Key.Split('@');
if (split[0] == "Option") if (split.Length == 2 && split[0] == "Choice")
{ ret.Add(split[1], FieldLoader.Load<MapGeneratorDropdownChoice>(node.Value));
string id = null;
if (split.Length >= 2)
id = split[1];
var option = new Option(id, node.Value, terrainInfo);
if (option.Choices.Count == 0)
return null;
options.Add(option);
}
} }
return new MapGeneratorSettings(options); return ret;
} }
public static List<string> DumpFluent(MiniYaml my) public readonly string[] Default = null;
string value = null;
public MapGeneratorMultiChoiceOption(string id, MiniYaml yaml)
: base(id, yaml) { }
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
{ {
var references = new List<string>(); var validChoices = ValidChoices(terrainInfo);
DumpFluent(my, references); if (validChoices.Contains(value))
return references; return Choices[value].Settings;
var fallback = Default?.FirstOrDefault(validChoices.Contains) ?? validChoices.FirstOrDefault();
return fallback != null ? Choices[fallback].Settings : [];
} }
public static void DumpFluent(MiniYaml my, List<string> references) public string Value
{ {
foreach (var node in my.Nodes) get => value;
if (node.Key.Split('@')[0] == "Option") set
Option.DumpFluent(node.Value, references);
}
MapGeneratorSettings(IReadOnlyList<Option> options)
{
Options = options;
}
public Dictionary<Option, Choice> DefaultChoices()
{
return Options.ToDictionary(option => option, option => option.Default);
}
/// <summary>Merge all choices into a complete settings MiniYaml.</summary>
public MiniYaml Compile(IReadOnlyDictionary<Option, Choice> choices)
{
var layers = new List<IReadOnlyCollection<MiniYamlNode>>();
// Apply the choices in their canonical order.
foreach (var option in Options.OrderBy(option => option.Priority))
{ {
var choice = choices[option]; if (!Choices.ContainsKey(value))
if (!option.ValidateChoice(choice)) throw new ArgumentException($"{value} is not in the list of valid choices");
throw new ArgumentException($"Option `{option.Id}` has illegal choice");
layers.Add(choice.Settings.Nodes);
}
var settingsNodes = MiniYaml.Merge(layers); this.value = value;
return new MiniYaml(null, settingsNodes); }
}
public List<string> ValidChoices(ITerrainInfo terrainInfo)
{
return Choices
.Where(kv => kv.Value.Tileset == null || kv.Value.Tileset.Contains(terrainInfo.Id))
.Select(kv => kv.Key)
.ToList();
}
public override IEnumerable<string> GetFluentReferences()
{
if (Label != null)
yield return Label;
foreach (var c in Choices.Values)
{
if (c.Label == null)
continue;
yield return c.Label + ".label";
// Descriptions are optional
if (FluentProvider.TryGetMessage(c.Label + ".description", out _))
yield return c.Label + ".description";
}
}
}
public class MapGeneratorMultiIntegerChoiceOption : MapGeneratorOption
{
[FieldLoader.Require]
public readonly string Parameter = null;
[FieldLoader.Require]
public readonly int[] Choices = null;
public readonly int? Default = null;
int value;
public MapGeneratorMultiIntegerChoiceOption(string id, MiniYaml yaml)
: base(id, yaml)
{
Value = Default ?? Choices?.First() ?? 0;
}
public int Value
{
get => value;
set
{
if (!Choices.Contains(value))
throw new ArgumentException($"{value} is not in the list of valid choices");
this.value = value;
}
}
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
{
return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))];
}
}
public sealed class MapGeneratorSettings : IMapGeneratorSettings
{
public MapGeneratorSettings(MiniYaml yaml)
{
foreach (var node in yaml.Nodes)
{
var split = node.Key.Split('@');
if (split.Length != 2)
continue;
if (split[0] == "BooleanOption")
Options.Add(new MapGeneratorBooleanOption(split[1], node.Value));
else if (split[0] == "IntegerOption")
Options.Add(new MapGeneratorIntegerOption(split[1], node.Value));
else if (split[0] == "MultiIntegerChoiceOption")
Options.Add(new MapGeneratorMultiIntegerChoiceOption(split[1], node.Value));
else if (split[0] == "MultiChoiceOption")
Options.Add(new MapGeneratorMultiChoiceOption(split[1], node.Value));
}
}
public List<MapGeneratorOption> Options { get; } = [];
public void Randomize(MersenneTwister random)
{
if (Options.FirstOrDefault(o => o.Id == "Seed") is MapGeneratorIntegerOption seed)
seed.Value = random.Next();
}
public MiniYaml Compile(ITerrainInfo terrainInfo)
{
// Apply the choices in their canonical order.
var layers = Options
.OrderBy(option => option.Priority)
.Select(o => o.GetSettings(terrainInfo));
return new MiniYaml(null, MiniYaml.Merge(layers));
} }
} }
} }

View File

@@ -11,6 +11,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.MapGenerator; using OpenRA.Mods.Common.MapGenerator;
using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Terrain;
using OpenRA.Support; using OpenRA.Support;
@@ -50,12 +51,13 @@ namespace OpenRA.Mods.Common.Traits
static List<string> FluentReferencesLoader(MiniYaml my) static List<string> FluentReferencesLoader(MiniYaml my)
{ {
return MapGeneratorSettings.DumpFluent(my.NodeWithKey("Settings").Value); return new MapGeneratorSettings(my.NodeWithKey("Settings").Value)
.Options.SelectMany(o => o.GetFluentReferences()).ToList();
} }
public MapGeneratorSettings GetSettings(ITerrainInfo terrainInfo) public IMapGeneratorSettings GetSettings()
{ {
return MapGeneratorSettings.LoadSettings(Settings, terrainInfo); return new MapGeneratorSettings(Settings);
} }
public void Generate(Map map, MiniYaml settings) public void Generate(Map map, MiniYaml settings)

View File

@@ -50,7 +50,8 @@ namespace OpenRA.Mods.Common.Traits
static List<string> FluentReferencesLoader(MiniYaml my) static List<string> FluentReferencesLoader(MiniYaml my)
{ {
return MapGeneratorSettings.DumpFluent(my.NodeWithKey("Settings").Value); return new MapGeneratorSettings(my.NodeWithKey("Settings").Value)
.Options.SelectMany(o => o.GetFluentReferences()).ToList();
} }
const int FractionMax = 1000; const int FractionMax = 1000;
@@ -481,9 +482,9 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
public MapGeneratorSettings GetSettings(ITerrainInfo terrainInfo) public IMapGeneratorSettings GetSettings()
{ {
return MapGeneratorSettings.LoadSettings(Settings, terrainInfo); return new MapGeneratorSettings(Settings);
} }
public void Generate(Map map, MiniYaml settings) public void Generate(Map map, MiniYaml settings)

View File

@@ -969,16 +969,23 @@ namespace OpenRA.Mods.Common.Traits
: base(message, inner) { } : base(message, inner) { }
} }
public interface IMapGeneratorSettings
{
/// <summary>Returns the options that allow users to customise the result.</summary>
List<MapGeneratorOption> Options { get; }
void Randomize(MersenneTwister random);
/// <summary>Merge all choices into a complete settings MiniYaml.</summary>
MiniYaml Compile(ITerrainInfo terrainInfo);
}
public interface IMapGeneratorInfo : ITraitInfoInterface public interface IMapGeneratorInfo : ITraitInfoInterface
{ {
string Type { get; } string Type { get; }
string Name { get; } string Name { get; }
/// <summary> IMapGeneratorSettings GetSettings();
/// Get the generator settings available for this tileset.
/// Returns null if not compatible with the given tileset.
/// </summary>
MapGeneratorSettings GetSettings(ITerrainInfo terrainInfo);
/// <summary> /// <summary>
/// Generate or manipulate a supplied map in-place. /// Generate or manipulate a supplied map in-place.

View File

@@ -15,6 +15,7 @@ using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using OpenRA.Mods.Common.MapGenerator;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
namespace OpenRA.Mods.Common.UtilityCommands namespace OpenRA.Mods.Common.UtilityCommands
@@ -204,9 +205,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
.Select(variable => config.Choices[variable].Length) .Select(variable => config.Choices[variable].Length)
.ToImmutableArray(); .ToImmutableArray();
var generator = var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
modData.DefaultRules.Actors[SystemActors.EditorWorld].TraitInfos<IMapGeneratorInfo>() .TraitInfos<IMapGeneratorInfo>()
.FirstOrDefault(info => info.Type == config.MapGeneratorType); .FirstOrDefault(info => info.Type == config.MapGeneratorType);
if (generator == null) if (generator == null)
throw new ArgumentException($"No map generator with type `{config.MapGeneratorType}`"); throw new ArgumentException($"No map generator with type `{config.MapGeneratorType}`");
@@ -246,7 +247,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
.Select(kv => $"{kv.Key}={kv.Value}") .Select(kv => $"{kv.Key}={kv.Value}")
.ToHashSet(); .ToHashSet();
var tileset = modData.DefaultTerrainInfo[iterationChoices[Configuration.TilesetVariable]]; var terrainInfo = modData.DefaultTerrainInfo[iterationChoices[Configuration.TilesetVariable]];
iterationChoices.Remove(Configuration.TilesetVariable); iterationChoices.Remove(Configuration.TilesetVariable);
var size = iterationChoices[Configuration.SizeVariable] var size = iterationChoices[Configuration.SizeVariable]
@@ -263,26 +264,31 @@ namespace OpenRA.Mods.Common.UtilityCommands
if (size.Length != 2) if (size.Length != 2)
throw new ArgumentException($"bad map size `{iterationChoices[Configuration.SizeVariable]}`"); throw new ArgumentException($"bad map size `{iterationChoices[Configuration.SizeVariable]}`");
var map = new Map(modData, tileset, size[0], size[1]); var map = new Map(modData, terrainInfo, size[0], size[1]);
var maxTerrainHeight = map.Grid.MaximumTerrainHeight; var maxTerrainHeight = map.Grid.MaximumTerrainHeight;
var tl = new PPos(1, 1 + maxTerrainHeight); var tl = new PPos(1, 1 + maxTerrainHeight);
var br = new PPos(size[0], size[1] + maxTerrainHeight); var br = new PPos(size[0], size[1] + maxTerrainHeight);
map.SetBounds(tl, br); map.SetBounds(tl, br);
var settings = generator.GetSettings(tileset); var settings = generator.GetSettings();
var choices = settings.DefaultChoices(); foreach (var o in settings.Options)
foreach (var option in choices.Keys) {
if (iterationChoices.TryGetValue(option.Id, out var choice)) if (iterationChoices.TryGetValue(o.Id, out var choice))
{ {
choices[option] = option.IsFreeform() if (o is MapGeneratorBooleanOption bo)
? choices[option].NewValue(choice) bo.Value = FieldLoader.GetValue<bool>("choice", choice);
: option.Choices.First(c => c.Id == choice); else if (o is MapGeneratorIntegerOption io)
iterationChoices.Remove(option.Id); io.Value = FieldLoader.GetValue<int>("choice", choice);
else if (o is MapGeneratorMultiIntegerChoiceOption mio)
mio.Value = FieldLoader.GetValue<int>("choice", choice);
else if (o is MapGeneratorMultiChoiceOption mo)
mo.Value = choice;
iterationChoices.Remove(o.Id);
} }
else if (config.NoDefaults) else if (config.NoDefaults)
{ throw new ArgumentException($"No choices specified for option `{o.Id}`");
throw new ArgumentException($"No choices specified for option `{option.Id}`"); }
}
if (iterationChoices.Count != 0) if (iterationChoices.Count != 0)
throw new ArgumentException($"Unknown options: {string.Join(", ", iterationChoices.Keys)}"); throw new ArgumentException($"Unknown options: {string.Join(", ", iterationChoices.Keys)}");
@@ -292,8 +298,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
tests++; tests++;
try try
{ {
var my = settings.Compile(choices); generator.Generate(map, settings.Compile(terrainInfo));
generator.Generate(map, my);
} }
catch (Exception e) when (e is MapGenerationException || e is YamlException) catch (Exception e) when (e is MapGenerationException || e is YamlException)
{ {

View File

@@ -27,24 +27,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
[FluentReference("name")] [FluentReference("name")]
const string StrGenerated = "notification-map-generator-generated"; const string StrGenerated = "notification-map-generator-generated";
[FluentReference] [FluentReference]
const string StrBadOption = "notification-map-generator-bad-option";
[FluentReference]
const string StrFailed = "notification-map-generator-failed"; const string StrFailed = "notification-map-generator-failed";
[FluentReference] [FluentReference]
const string StrFailedCancel = "label-map-generator-failed-cancel"; const string StrFailedCancel = "label-map-generator-failed-cancel";
readonly EditorActionManager editorActionManager; readonly EditorActionManager editorActionManager;
readonly ButtonWidget generateButtonWidget;
readonly ButtonWidget generateRandomButtonWidget;
readonly World world; readonly World world;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly ModData modData; readonly ModData modData;
// nullable // nullable
IMapGeneratorInfo selectedGenerator; IMapGeneratorInfo selectedGenerator;
IMapGeneratorSettings selectedSettings;
readonly Dictionary<IMapGeneratorInfo, MapGeneratorSettings> generatorsToSettings;
readonly Dictionary<IMapGeneratorInfo, Dictionary<MapGeneratorSettings.Option, MapGeneratorSettings.Choice>> generatorsToSettingsChoices;
readonly ScrollPanelWidget settingsPanel; readonly ScrollPanelWidget settingsPanel;
readonly Widget checkboxSettingTemplate; readonly Widget checkboxSettingTemplate;
@@ -60,42 +54,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
this.modData = modData; this.modData = modData;
selectedGenerator = null;
generatorsToSettings = [];
generatorsToSettingsChoices = [];
var mapGenerators = new List<IMapGeneratorInfo>();
var terrainInfo = modData.DefaultTerrainInfo[world.Map.Tileset];
foreach (var generator in world.Map.Rules.Actors[SystemActors.EditorWorld].TraitInfos<IMapGeneratorInfo>())
{
var settings = generator.GetSettings(terrainInfo);
if (settings == null)
continue;
var choices = settings.DefaultChoices();
mapGenerators.Add(generator);
generatorsToSettingsChoices.Add(generator, choices);
generatorsToSettings.Add(generator, settings);
}
generateButtonWidget = widget.Get<ButtonWidget>("GENERATE_BUTTON");
generateRandomButtonWidget = widget.Get<ButtonWidget>("GENERATE_RANDOM_BUTTON");
settingsPanel = widget.Get<ScrollPanelWidget>("SETTINGS_PANEL"); settingsPanel = widget.Get<ScrollPanelWidget>("SETTINGS_PANEL");
checkboxSettingTemplate = settingsPanel.Get<Widget>("CHECKBOX_TEMPLATE"); checkboxSettingTemplate = settingsPanel.Get<Widget>("CHECKBOX_TEMPLATE");
textSettingTemplate = settingsPanel.Get<Widget>("TEXT_TEMPLATE"); textSettingTemplate = settingsPanel.Get<Widget>("TEXT_TEMPLATE");
dropDownSettingTemplate = settingsPanel.Get<Widget>("DROPDOWN_TEMPLATE"); dropDownSettingTemplate = settingsPanel.Get<Widget>("DROPDOWN_TEMPLATE");
var generateButtonWidget = widget.Get<ButtonWidget>("GENERATE_BUTTON");
generateButtonWidget.OnClick = GenerateMap; generateButtonWidget.OnClick = GenerateMap;
var generateRandomButtonWidget = widget.Get<ButtonWidget>("GENERATE_RANDOM_BUTTON");
generateRandomButtonWidget.OnClick = () => generateRandomButtonWidget.OnClick = () =>
{ {
Randomize(); selectedSettings?.Randomize(world.LocalRandom);
UpdateSettingsUi();
GenerateMap(); GenerateMap();
}; };
var generatorDropDown = widget.Get<DropDownButtonWidget>("GENERATOR"); var generatorDropDown = widget.Get<DropDownButtonWidget>("GENERATOR");
ChangeGenerator(mapGenerators.FirstOrDefault()); var mapGenerators = world.Map.Rules.Actors[SystemActors.EditorWorld].TraitInfos<IMapGeneratorInfo>();
if (selectedGenerator != null) if (mapGenerators.Count > 0)
{ {
var label = new CachedTransform<IMapGeneratorInfo, string>(g => FluentProvider.GetMessage(g.Name)); var label = new CachedTransform<IMapGeneratorInfo, string>(g => FluentProvider.GetMessage(g.Name));
generatorDropDown.GetText = () => label.Update(selectedGenerator); generatorDropDown.GetText = () => label.Update(selectedGenerator);
@@ -104,7 +81,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
ScrollItemWidget SetupItem(IMapGeneratorInfo g, ScrollItemWidget template) ScrollItemWidget SetupItem(IMapGeneratorInfo g, ScrollItemWidget template)
{ {
bool IsSelected() => g.Type == selectedGenerator.Type; bool IsSelected() => g.Type == selectedGenerator.Type;
void OnClick() => ChangeGenerator(mapGenerators.First(generator => generator.Type == g.Type)); void OnClick() => ChangeGenerator(g);
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick); var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var label = FluentProvider.GetMessage(g.Name); var label = FluentProvider.GetMessage(g.Name);
item.Get<LabelWidget>("LABEL").GetText = () => label; item.Get<LabelWidget>("LABEL").GetText = () => label;
@@ -120,6 +97,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
generateRandomButtonWidget.IsDisabled = () => true; generateRandomButtonWidget.IsDisabled = () => true;
generatorDropDown.IsDisabled = () => true; generatorDropDown.IsDisabled = () => true;
} }
ChangeGenerator(mapGenerators.FirstOrDefault());
} }
sealed class RandomMapEditorAction : IEditorAction sealed class RandomMapEditorAction : IEditorAction
@@ -155,6 +134,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void ChangeGenerator(IMapGeneratorInfo newGenerator) void ChangeGenerator(IMapGeneratorInfo newGenerator)
{ {
selectedGenerator = newGenerator; selectedGenerator = newGenerator;
selectedSettings = newGenerator?.GetSettings();
UpdateSettingsUi(); UpdateSettingsUi();
} }
@@ -166,89 +146,119 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (selectedGenerator == null) if (selectedGenerator == null)
return; return;
var settings = generatorsToSettings[selectedGenerator]; foreach (var o in selectedSettings.Options)
var choices = generatorsToSettingsChoices[selectedGenerator];
foreach (var option in settings.Options)
{ {
Widget settingWidget; Widget settingWidget = null;
switch (o)
switch (option.Ui)
{ {
case MapGeneratorSettings.UiType.Hidden: case MapGeneratorBooleanOption bo:
continue; {
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;
break;
}
case MapGeneratorSettings.UiType.DropDown: 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; };
break;
}
case MapGeneratorMultiIntegerChoiceOption mio:
{ {
settingWidget = dropDownSettingTemplate.Clone(); settingWidget = dropDownSettingTemplate.Clone();
var label = settingWidget.Get<LabelWidget>("LABEL"); var labelWidget = settingWidget.Get<LabelWidget>("LABEL");
label.GetText = () => option.Label; var label = FluentProvider.GetMessage(mio.Label);
var dropDown = settingWidget.Get<DropDownButtonWidget>("DROPDOWN"); labelWidget.GetText = () => label;
dropDown.GetText = () => choices[option].Label;
dropDown.OnMouseDown = _ =>
{
ScrollItemWidget SetupItem(MapGeneratorSettings.Choice choice, ScrollItemWidget template)
{
bool IsSelected() => choice == choices[option];
void OnClick() => choices[option] = choice;
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
item.Get<LabelWidget>("LABEL").GetText = () => choice.Label;
item.GetTooltipText =
choice.Description != null
? () => choice.Description
: null;
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;
var item = ScrollItemWidget.Setup(template, IsSelected, OnClick);
var itemLabel = FieldSaver.FormatValue(choice);
item.Get<LabelWidget>("LABEL").GetText = () => itemLabel;
item.GetTooltipText = null;
return item; return item;
} }
dropDown.ShowDropDown("LABEL_DROPDOWN_WITH_TOOLTIP_TEMPLATE", option.Choices.Count * 30, option.Choices, SetupItem); dropDownWidget.ShowDropDown("LABEL_DROPDOWN_WITH_TOOLTIP_TEMPLATE", mio.Choices.Length * 30, mio.Choices, SetupItem);
}; };
break; break;
} }
case MapGeneratorSettings.UiType.Checkbox: case MapGeneratorMultiChoiceOption mo:
{ {
if (option.Choices.Count != 2) var validChoices = mo.ValidChoices(world.Map.Rules.TerrainInfo);
throw new InvalidOperationException("Checkbox option that does not have two choices"); if (!validChoices.Contains(mo.Value))
settingWidget = checkboxSettingTemplate.Clone(); mo.Value = mo.Default?.FirstOrDefault(validChoices.Contains) ?? validChoices.FirstOrDefault();
var checkbox = settingWidget.Get<CheckboxWidget>("CHECKBOX");
checkbox.GetText = () => option.Label;
checkbox.IsChecked = () => choices[option] == option.Choices[1];
checkbox.OnClick = () =>
choices[option] =
choices[option] == option.Choices[1]
? option.Choices[0]
: option.Choices[1];
break;
}
case MapGeneratorSettings.UiType.Integer: if (mo.Label != null && validChoices.Count > 0)
case MapGeneratorSettings.UiType.Float:
case MapGeneratorSettings.UiType.String:
{
if (option.Choices.Count != 1)
throw new InvalidOperationException("Text option that does not have one choice");
settingWidget = textSettingTemplate.Clone();
var label = settingWidget.Get<LabelWidget>("LABEL");
label.GetText = () => option.Label;
var input = settingWidget.Get<TextFieldWidget>("INPUT");
input.Text = choices[option].Id;
input.OnTextEdited = () =>
{ {
var choice = choices[option].NewValue(input.Text); settingWidget = dropDownSettingTemplate.Clone();
choices[option] = choice; var labelWidget = settingWidget.Get<LabelWidget>("LABEL");
var valid = option.ValidateChoice(choice); var label = FluentProvider.GetMessage(mo.Label);
input.IsValid = () => valid; 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;
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);
};
}
input.OnEscKey = _ => { input.YieldKeyboardFocus(); return true; };
input.OnEnterKey = _ => { input.YieldKeyboardFocus(); return true; };
break; break;
} }
default: default:
throw new NotSupportedException("Unsupported MapGeneratorSettings.UiType"); throw new NotImplementedException($"Unhandled MapGeneratorOption type {o.GetType().Name}");
} }
if (settingWidget == null)
continue;
settingWidget.IsVisible = () => true; settingWidget.IsVisible = () => true;
settingsPanel.AddChild(settingWidget); settingsPanel.AddChild(settingWidget);
} }
@@ -266,18 +276,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
cancelText: StrFailedCancel); cancelText: StrFailedCancel);
} }
void Randomize()
{
var choices = generatorsToSettingsChoices[selectedGenerator];
foreach (var option in choices.Keys)
{
if (option.Random)
choices[option] = option.RandomChoice();
}
UpdateSettingsUi();
}
void GenerateMap() void GenerateMap()
{ {
try try
@@ -293,22 +291,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void GenerateMapMayThrow() void GenerateMapMayThrow()
{ {
var map = world.Map; var map = world.Map;
var tileset = modData.DefaultTerrainInfo[map.Tileset]; var terrainInfo = map.Rules.TerrainInfo;
var generatedMap = new Map(modData, tileset, map.MapSize.X, map.MapSize.Y); var generatedMap = new Map(modData, terrainInfo, map.MapSize.X, map.MapSize.Y);
var bounds = map.Bounds; var bounds = map.Bounds;
generatedMap.SetBounds(new PPos(bounds.Left, bounds.Top), new PPos(bounds.Right - 1, bounds.Bottom - 1)); generatedMap.SetBounds(new PPos(bounds.Left, bounds.Top), new PPos(bounds.Right - 1, bounds.Bottom - 1));
var choices = generatorsToSettingsChoices[selectedGenerator];
foreach (var optionChoice in choices) var settings = selectedSettings.Compile(terrainInfo);
{
var option = optionChoice.Key;
var choice = optionChoice.Value;
if (!option.ValidateChoice(choice))
throw new MapGenerationException(
FluentProvider.GetMessage(StrBadOption, "option", option.Label));
}
var settings = generatorsToSettings[selectedGenerator].Compile(choices);
// Run main generator logic. May throw. // Run main generator logic. May throw.
var generateStopwatch = Stopwatch.StartNew(); var generateStopwatch = Stopwatch.StartNew();

View File

@@ -3,7 +3,7 @@
Type: experimental Type: experimental
Name: map-generator-experimental Name: map-generator-experimental
Settings: Settings:
Option@hidden_defaults: MultiChoiceOption@hidden_defaults:
Choice@hidden_defaults: Choice@hidden_defaults:
Settings: Settings:
TerrainFeatureSize: 20480 TerrainFeatureSize: 20480
@@ -71,7 +71,7 @@
ForestObstacles: Trees ForestObstacles: Trees
UnplayableObstacles: Obstructions UnplayableObstacles: Obstructions
CivilianBuildingsObstacles: CivilianBuildings CivilianBuildingsObstacles: CivilianBuildings
Option@hidden_tileset_overrides: MultiChoiceOption@hidden_tileset_overrides:
Choice@common: Choice@common:
Tileset: DESERT,SNOW,TEMPERAT Tileset: DESERT,SNOW,TEMPERAT
Settings: Settings:
@@ -87,12 +87,11 @@
RepaintTiles: RepaintTiles:
255: Snow 255: Snow
1: Water 1: Water
Option@Seed: IntegerOption@Seed:
Label: label-cnc-map-generator-option-seed Label: label-cnc-map-generator-option-seed
Random: True Parameter: Seed
Default: 0 Default: 0
Integer: Seed MultiChoiceOption@TerrainType:
Option@TerrainType:
Label: label-cnc-map-generator-option-terrain-type Label: label-cnc-map-generator-option-terrain-type
Priority: 2 Priority: 2
Default: Gardens,Rocky Default: Gardens,Rocky
@@ -170,13 +169,13 @@
Mountains: 1000 Mountains: 1000
Roughness: 850 Roughness: 850
MinimumTerrainContourSpacing: 5 MinimumTerrainContourSpacing: 5
Option@Rotations: MultiIntegerChoiceOption@Rotations:
Label: label-cnc-map-generator-option-rotations Label: label-cnc-map-generator-option-rotations
SimpleChoice: Rotations Parameter: Rotations
Values: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
Default: 2 Default: 2
Priority: 1 Priority: 1
Option@Mirror: MultiChoiceOption@Mirror:
Label: label-cnc-map-generator-option-mirror Label: label-cnc-map-generator-option-mirror
Default: None Default: None
Priority: 1 Priority: 1
@@ -200,7 +199,7 @@
Label: label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left Label: label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left
Settings: Settings:
Mirror: TopRightMatchesBottomLeft Mirror: TopRightMatchesBottomLeft
Option@Shape: MultiChoiceOption@Shape:
Label: label-cnc-map-generator-option-shape Label: label-cnc-map-generator-option-shape
Default: Square Default: Square
Priority: 1 Priority: 1
@@ -217,13 +216,13 @@
Tileset: DESERT Tileset: DESERT
Settings: Settings:
ExternalCircularBias: -1 ExternalCircularBias: -1
Option@Players: MultiIntegerChoiceOption@Players:
Label: label-cnc-map-generator-option-players Label: label-cnc-map-generator-option-players
SimpleChoice: Players Parameter: Players
Values: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
Default: 1 Default: 1
Priority: 1 Priority: 1
Option@Resources: MultiChoiceOption@Resources:
Label: label-cnc-map-generator-option-resources Label: label-cnc-map-generator-option-resources
Default: Medium Default: Medium
Choice@None: Choice@None:
@@ -285,7 +284,7 @@
ResourceSpawnWeights: ResourceSpawnWeights:
MaximumExpansionResourceSpawns: 0 MaximumExpansionResourceSpawns: 0
MaximumResourceSpawnsPerExpansion: 1 MaximumResourceSpawnsPerExpansion: 1
Option@Buildings: MultiChoiceOption@Buildings:
Label: label-cnc-map-generator-option-buildings Label: label-cnc-map-generator-option-buildings
Default: Standard Default: Standard
Choice@None: Choice@None:
@@ -327,7 +326,7 @@
MaximumBuildings: 10 MaximumBuildings: 10
BuildingWeights: BuildingWeights:
v19: 1 v19: 1
Option@Density: MultiChoiceOption@Density:
Label: label-cnc-map-generator-option-density Label: label-cnc-map-generator-option-density
Default: Players Default: Players
Priority: 1 Priority: 1
@@ -366,17 +365,17 @@
Settings: Settings:
AreaEntityBonus: 800 AreaEntityBonus: 800
PlayerCountEntityBonus: 0 PlayerCountEntityBonus: 0
Option@DenyWalledArea: BooleanOption@DenyWalledArea:
Label: label-cnc-map-generator-option-deny-walled-areas Label: label-cnc-map-generator-option-deny-walled-areas
Checkbox: DenyWalledAreas Parameter: DenyWalledAreas
Default: True Default: True
Priority: 1 Priority: 1
Option@Roads: BooleanOption@Roads:
Label: label-cnc-map-generator-option-roads Label: label-cnc-map-generator-option-roads
Checkbox: Roads Parameter: Roads
Default: True Default: True
Priority: 1 Priority: 1
Option@CivilianDensity: MultiChoiceOption@CivilianDensity:
Label: label-cnc-map-generator-option-civilian-density Label: label-cnc-map-generator-option-civilian-density
Default: Default Default: Default
Priority: 3 Priority: 3
@@ -411,7 +410,7 @@
Type: clear Type: clear
Name: map-generator-clear Name: map-generator-clear
Settings: Settings:
Option@Tile: MultiChoiceOption@Tile:
Label: label-clear-map-generator-option-tile Label: label-clear-map-generator-option-tile
Choice@CommonClear: Choice@CommonClear:
Label: label-clear-map-generator-choice-tile-clear Label: label-clear-map-generator-choice-tile-clear

View File

@@ -1142,5 +1142,4 @@ keycode =
## MapGeneratorToolLogic ## MapGeneratorToolLogic
label-map-generator-failed-cancel = Dismiss label-map-generator-failed-cancel = Dismiss
notification-map-generator-generated = Generated using { $name } notification-map-generator-generated = Generated using { $name }
notification-map-generator-bad-option = Option "{ $option }" is invalid
notification-map-generator-failed = Map generation failed notification-map-generator-failed = Map generation failed

View File

@@ -3,7 +3,7 @@
Type: experimental Type: experimental
Name: map-generator-experimental Name: map-generator-experimental
Settings: Settings:
Option@hidden_defaults: MultiChoiceOption@hidden_defaults:
Choice@hidden_defaults: Choice@hidden_defaults:
Settings: Settings:
TerrainFeatureSize: 20480 TerrainFeatureSize: 20480
@@ -70,7 +70,7 @@
ForestObstacles: Trees ForestObstacles: Trees
UnplayableObstacles: Obstructions UnplayableObstacles: Obstructions
CivilianBuildingsObstacles: CivilianBuildings CivilianBuildingsObstacles: CivilianBuildings
Option@hidden_tileset_overrides: MultiChoiceOption@hidden_tileset_overrides:
Choice@desert: Choice@desert:
Tileset: DESERT Tileset: DESERT
Settings: Settings:
@@ -86,12 +86,11 @@
Settings: Settings:
LandTile: 255 LandTile: 255
WaterTile: 1 WaterTile: 1
Option@Seed: IntegerOption@Seed:
Label: label-ra-map-generator-option-seed Label: label-ra-map-generator-option-seed
Random: True Parameter: Seed
Default: 0 Default: 0
Integer: Seed MultiChoiceOption@TerrainType:
Option@TerrainType:
Label: label-ra-map-generator-option-terrain-type Label: label-ra-map-generator-option-terrain-type
Priority: 2 Priority: 2
Default: Gardens Default: Gardens
@@ -193,13 +192,13 @@
Forests: 0 Forests: 0
SpawnBuildSize: 6 SpawnBuildSize: 6
MinimumSpawnRadius: 4 MinimumSpawnRadius: 4
Option@Rotations: MultiIntegerChoiceOption@Rotations:
Label: label-ra-map-generator-option-rotations Label: label-ra-map-generator-option-rotations
SimpleChoice: Rotations Parameter: Rotations
Values: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
Default: 2 Default: 2
Priority: 1 Priority: 1
Option@Mirror: MultiChoiceOption@Mirror:
Label: label-ra-map-generator-option-mirror Label: label-ra-map-generator-option-mirror
Default: None Default: None
Priority: 1 Priority: 1
@@ -223,7 +222,7 @@
Label: label-ra-map-generator-choice-mirror-top-right-matches-bottom-left Label: label-ra-map-generator-choice-mirror-top-right-matches-bottom-left
Settings: Settings:
Mirror: TopRightMatchesBottomLeft Mirror: TopRightMatchesBottomLeft
Option@Shape: MultiChoiceOption@Shape:
Label: label-ra-map-generator-option-shape Label: label-ra-map-generator-option-shape
Default: Square Default: Square
Priority: 1 Priority: 1
@@ -239,13 +238,13 @@
Label: label-ra-map-generator-choice-shape-circle-water Label: label-ra-map-generator-choice-shape-circle-water
Settings: Settings:
ExternalCircularBias: -1 ExternalCircularBias: -1
Option@Players: MultiIntegerChoiceOption@Players:
Label: label-ra-map-generator-option-players Label: label-ra-map-generator-option-players
SimpleChoice: Players Parameter: Players
Values: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 Choices: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
Default: 1 Default: 1
Priority: 1 Priority: 1
Option@Resources: MultiChoiceOption@Resources:
Label: label-ra-map-generator-option-resources Label: label-ra-map-generator-option-resources
Default: Medium Default: Medium
Choice@None: Choice@None:
@@ -303,7 +302,7 @@
ResourceSpawnWeights: ResourceSpawnWeights:
MaximumExpansionResourceSpawns: 0 MaximumExpansionResourceSpawns: 0
MaximumResourceSpawnsPerExpansion: 1 MaximumResourceSpawnsPerExpansion: 1
Option@Buildings: MultiChoiceOption@Buildings:
Label: label-ra-map-generator-option-buildings Label: label-ra-map-generator-option-buildings
Default: Standard Default: Standard
Choice@None: Choice@None:
@@ -346,7 +345,7 @@
MaximumBuildings: 10 MaximumBuildings: 10
BuildingWeights: BuildingWeights:
oilb: 1 oilb: 1
Option@Density: MultiChoiceOption@Density:
Label: label-ra-map-generator-option-density Label: label-ra-map-generator-option-density
Default: Players Default: Players
Priority: 1 Priority: 1
@@ -385,17 +384,17 @@
Settings: Settings:
AreaEntityBonus: 800 AreaEntityBonus: 800
PlayerCountEntityBonus: 0 PlayerCountEntityBonus: 0
Option@DenyWalledArea: BooleanOption@DenyWalledArea:
Label: label-ra-map-generator-option-deny-walled-areas Label: label-ra-map-generator-option-deny-walled-areas
Checkbox: DenyWalledAreas Parameter: DenyWalledAreas
Default: True Default: True
Priority: 1 Priority: 1
Option@Roads: BooleanOption@Roads:
Label: label-ra-map-generator-option-roads Label: label-ra-map-generator-option-roads
Checkbox: Roads Parameter: Roads
Default: True Default: True
Priority: 1 Priority: 1
Option@CivilianDensity: MultiChoiceOption@CivilianDensity:
Label: label-ra-map-generator-option-civilian-density Label: label-ra-map-generator-option-civilian-density
Default: Default Default: Default
Priority: 3 Priority: 3
@@ -430,7 +429,7 @@
Type: clear Type: clear
Name: map-generator-clear Name: map-generator-clear
Settings: Settings:
Option@Tile: MultiChoiceOption@Tile:
Label: label-clear-map-generator-option-tile Label: label-clear-map-generator-option-tile
Choice@DesertClear: Choice@DesertClear:
Label: label-clear-map-generator-choice-tile-clear Label: label-clear-map-generator-choice-tile-clear