diff --git a/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs b/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs
index b8d5cbc793..5fe8ea0e76 100644
--- a/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs
+++ b/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs
@@ -13,389 +13,231 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using OpenRA.Mods.Common.Traits;
+using OpenRA.Support;
namespace OpenRA.Mods.Common.MapGenerator
{
- public sealed class MapGeneratorSettings
+ public abstract class MapGeneratorOption
{
- /// How an option should be treated for UI purposes.
- public enum UiType
+ [FieldLoader.Ignore]
+ public readonly string Id;
+ public readonly string Label = null;
+ public readonly int Priority = 0;
+
+ protected MapGeneratorOption(string id, MiniYaml yaml)
{
- Hidden,
- DropDown,
- Checkbox,
- Integer,
- Float,
- String,
+ Id = id;
+ FieldLoader.Load(this, yaml);
}
- /// Represents a bunch of settings, along with UI information.
- public sealed class Choice
- {
- /// Uniquely identifies a Choice within an Option.
- public readonly string Id;
+ public abstract IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo);
- /// The label to use for UI selection. Post-fluent.
+ public virtual IEnumerable 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 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 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[] Tileset = null;
- /// The tooltip to use for UI selection. Post-fluent.
- public readonly string Description = null;
+ [FieldLoader.LoadUsing(nameof(LoadSettings))]
+ [FieldLoader.Require]
+ public readonly ImmutableList Settings = null;
- ///
- /// Only offer the Choice for these tilesets. (If null, show for all.)
- ///
- public readonly IReadOnlySet Tileset = null;
-
- /// (Partial) settings to combine into the final overall settings.
- public readonly MiniYaml Settings;
-
- public Choice(string id, MiniYaml my)
+ static ImmutableList LoadSettings(MiniYaml yaml)
{
- Id = id;
- 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;
- }
-
- /// Create a choice that represents a top-level setting with a given value.
- 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 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");
- }
- }
-
- /// Check whether this choice is permitted for this map.
- public bool Allowed(ITerrainInfo terrainInfo)
- {
- if (Tileset != null && !Tileset.Contains(terrainInfo.Id))
- return false;
-
- return true;
- }
-
- /// For single-setting choices, creates a new choice for that setting with a given value.
- 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);
+ return yaml.NodeWithKey("Settings").Value.Nodes.ToImmutableList();
}
}
- public sealed class Option
+ [FieldLoader.LoadUsing(nameof(LoadChoices))]
+ public readonly Dictionary Choices = null;
+
+ static Dictionary LoadChoices(MiniYaml yaml)
{
- /// Unique identifier for this Option.
- [FieldLoader.Ignore]
- public readonly string Id;
-
- /// The label to use for UI selection. Post-fluent.
- [FieldLoader.Ignore]
- public readonly string Label = null;
-
- ///
- /// For multiple-choice options (dropdowns/checkboxes) holds the allowed Choices.
- /// For text entry Options, contains the default value.
- /// If this is empty, the map is not compatible with the generator.
- ///
- [FieldLoader.Ignore]
- public readonly IReadOnlyList Choices;
-
- ///
- /// The default Choice for this option.
- /// Default will be ignored if it is not valid for the map.
- ///
- [FieldLoader.Ignore]
- public readonly Choice Default;
-
- public readonly double Min = double.NegativeInfinity;
- public readonly double Max = double.PositiveInfinity;
-
- /// Whether the Option can be randomized.
- public readonly bool Random = false;
-
- /// How an option should be treated for UI purposes.
- [FieldLoader.Ignore]
- public readonly UiType Ui;
-
- /// Settings layering priority. Higher overrides lower.
- 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();
- 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 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