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