From cf11c6633ecec35a30b4f1311a42a5957cd8359c Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 18 Apr 2025 11:12:32 +0100 Subject: [PATCH] Simplify Player and Symmetry options. Player now specifies the total player count, and the Symmetry options adjust to show only valid options. --- .../MapGenerator/MapGeneratorSettings.cs | 37 ++++++++--- .../Traits/World/ExperimentalMapGenerator.cs | 18 ++--- OpenRA.Mods.Common/TraitsInterfaces.cs | 2 + .../Logic/Editor/MapGeneratorToolLogic.cs | 12 +++- mods/cnc/fluent/rules.ftl | 36 ++++++---- mods/cnc/rules/map-generators.yaml | 65 ++++++++++++++----- mods/ra/fluent/rules.ftl | 36 ++++++---- mods/ra/rules/map-generators.yaml | 65 ++++++++++++++----- 8 files changed, 197 insertions(+), 74 deletions(-) diff --git a/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs b/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs index 5fe8ea0e76..d6e0bea9d7 100644 --- a/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs +++ b/OpenRA.Mods.Common/MapGenerator/MapGeneratorSettings.cs @@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.MapGenerator FieldLoader.Load(this, yaml); } - public abstract IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo); + public abstract IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo, int playerCount); public virtual IEnumerable GetFluentReferences() { @@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.MapGenerator Value = Default; } - public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo) + public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo, int playerCount) { return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))]; } @@ -72,7 +72,7 @@ namespace OpenRA.Mods.Common.MapGenerator Value = Default; } - public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo) + public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo, int playerCount) { return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))]; } @@ -84,6 +84,7 @@ namespace OpenRA.Mods.Common.MapGenerator { public readonly string Label = null; public readonly string[] Tileset = null; + public readonly int[] Players = null; [FieldLoader.LoadUsing(nameof(LoadSettings))] [FieldLoader.Require] @@ -117,9 +118,9 @@ namespace OpenRA.Mods.Common.MapGenerator public MapGeneratorMultiChoiceOption(string id, MiniYaml yaml) : base(id, yaml) { } - public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo) + public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo, int playerCount) { - var validChoices = ValidChoices(terrainInfo); + var validChoices = ValidChoices(terrainInfo, playerCount); if (validChoices.Contains(value)) return Choices[value].Settings; @@ -139,10 +140,12 @@ namespace OpenRA.Mods.Common.MapGenerator } } - public List ValidChoices(ITerrainInfo terrainInfo) + public List ValidChoices(ITerrainInfo terrainInfo, int playerCount) { return Choices - .Where(kv => kv.Value.Tileset == null || kv.Value.Tileset.Contains(terrainInfo.Id)) + .Where(kv => + (kv.Value.Tileset?.Contains(terrainInfo.Id) ?? true) && + (kv.Value.Players?.Contains(playerCount) ?? true)) .Select(kv => kv.Key) .ToList(); } @@ -195,7 +198,7 @@ namespace OpenRA.Mods.Common.MapGenerator } } - public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo) + public override IReadOnlyCollection GetSettings(ITerrainInfo terrainInfo, int playerCount) { return [new MiniYamlNode(Parameter, FieldSaver.FormatValue(Value))]; } @@ -230,12 +233,28 @@ namespace OpenRA.Mods.Common.MapGenerator seed.Value = random.Next(); } + public int PlayerCount + { + get + { + var o = Options.FirstOrDefault(o => o.Id == "Players"); + if (o is MapGeneratorIntegerOption io) + return io.Value; + + if (o is MapGeneratorMultiIntegerChoiceOption mio) + return mio.Value; + + return 0; + } + } + public MiniYaml Compile(ITerrainInfo terrainInfo) { // Apply the choices in their canonical order. + var playerCount = PlayerCount; var layers = Options .OrderBy(option => option.Priority) - .Select(o => o.GetSettings(terrainInfo)); + .Select(o => o.GetSettings(terrainInfo, playerCount)); return new MiniYaml(null, MiniYaml.Merge(layers)); } diff --git a/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs b/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs index 5d5692ce1f..6ee8f994ae 100644 --- a/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs +++ b/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs @@ -455,9 +455,12 @@ namespace OpenRA.Mods.Common.Traits if (!(terrainInfo.Templates.TryGetValue(LandTile, out var waterTemplate) && waterTemplate.Contains(0))) throw new MapGenerationException("WaterTile is not valid"); - var symmetryCount = Symmetry.RotateAndMirrorProjectionCount(Rotations, Mirror); - if (Players * symmetryCount > 32) + if (Players > 32) throw new MapGenerationException("Total number of players must not exceed 32"); + + var symmetryCount = Symmetry.RotateAndMirrorProjectionCount(Rotations, Mirror); + if (Players % symmetryCount != 0) + throw new MapGenerationException($"Total number of players must be a multiple of {symmetryCount}"); } public static (T[] Types, int[] Weights) SplitWeights(IReadOnlyDictionary typeWeights) @@ -997,8 +1000,7 @@ namespace OpenRA.Mods.Common.Traits if (largest == null) throw new MapGenerationException("could not find a playable region"); - var totalPlayers = param.Players * Symmetry.RotateAndMirrorProjectionCount(param.Rotations, param.Mirror); - var minimumPlayableSpace = (int)(totalPlayers * Math.PI * param.SpawnBuildSize * param.SpawnBuildSize); + var minimumPlayableSpace = (int)(param.Players * Math.PI * param.SpawnBuildSize * param.SpawnBuildSize); if (largest.PlayableArea < minimumPlayableSpace) throw new MapGenerationException("playable space is too small"); @@ -1244,14 +1246,14 @@ namespace OpenRA.Mods.Common.Traits var zoneableArea = zoneable.Count(v => v); var symmetryCount = Symmetry.RotateAndMirrorProjectionCount(param.Rotations, param.Mirror); - var totalPlayers = param.Players * symmetryCount; var entityMultiplier = (long)zoneableArea * param.AreaEntityBonus + - (long)totalPlayers * param.PlayerCountEntityBonus; + (long)param.Players * param.PlayerCountEntityBonus; var perSymmetryEntityMultiplier = entityMultiplier / symmetryCount; // Spawn generation - for (var iteration = 0; iteration < param.Players; iteration++) + var symmetryPlayers = param.Players / symmetryCount; + for (var iteration = 0; iteration < symmetryPlayers; iteration++) { var spawnPreference = new CellLayer(map); CellLayerUtils.ChebyshevRoom(spawnPreference, zoneable, false); @@ -1774,7 +1776,7 @@ namespace OpenRA.Mods.Common.Traits MultiBrush.PaintArea(map, actorPlans, replace, collection, repaintRandom); } - map.PlayerDefinitions = new MapPlayers(map.Rules, 0).ToMiniYaml(); + map.PlayerDefinitions = new MapPlayers(map.Rules, param.Players).ToMiniYaml(); map.ActorDefinitions = actorPlans .Select((plan, i) => new MiniYamlNode($"Actor{i}", plan.Reference.Save())) .ToImmutableArray(); diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 506fa3c95f..1d638f37d8 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -980,6 +980,8 @@ namespace OpenRA.Mods.Common.Traits /// Returns the options that allow users to customise the result. List Options { get; } + int PlayerCount { get; } + void Randomize(MersenneTwister random); /// Merge all choices into a complete settings MiniYaml. diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapGeneratorToolLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapGeneratorToolLogic.cs index ecb5b8d4b3..13f8bc3d2b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapGeneratorToolLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapGeneratorToolLogic.cs @@ -110,6 +110,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (generator == null) return; + var playerCount = settings.PlayerCount; foreach (var o in settings.Options) { Widget settingWidget = null; @@ -161,9 +162,14 @@ namespace OpenRA.Mods.Common.Widgets.Logic ScrollItemWidget SetupItem(int choice, ScrollItemWidget template) { bool IsSelected() => choice == mio.Value; - void OnClick() => mio.Value = choice; - var item = ScrollItemWidget.Setup(template, IsSelected, OnClick); + void OnClick() + { + mio.Value = choice; + if (o.Id == "Players") + UpdateSettingsUi(); + } + var item = ScrollItemWidget.Setup(template, IsSelected, OnClick); var itemLabel = FieldSaver.FormatValue(choice); item.Get("LABEL").GetText = () => itemLabel; item.GetTooltipText = null; @@ -177,7 +183,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic case MapGeneratorMultiChoiceOption mo: { - var validChoices = mo.ValidChoices(world.Map.Rules.TerrainInfo); + var validChoices = mo.ValidChoices(world.Map.Rules.TerrainInfo, playerCount); if (!validChoices.Contains(mo.Value)) mo.Value = mo.Default?.FirstOrDefault(validChoices.Contains) ?? validChoices.FirstOrDefault(); diff --git a/mods/cnc/fluent/rules.ftl b/mods/cnc/fluent/rules.ftl index 200c562d84..3938b13076 100644 --- a/mods/cnc/fluent/rules.ftl +++ b/mods/cnc/fluent/rules.ftl @@ -821,19 +821,31 @@ label-cnc-map-generator-choice-terrain-type-mountain-lakes = .label = Mountain Lakes .description = Lakes and many long cliffs -label-cnc-map-generator-option-rotations = Rotations - -label-cnc-map-generator-option-mirror = Mirror +label-cnc-map-generator-option-symmetry = Symmetry label-cnc-map-generator-choice-mirror-none = .label = None -label-cnc-map-generator-choice-mirror-left-matches-right = - .label = Left vs right -label-cnc-map-generator-choice-mirror-top-left-matches-bottom-right = - .label = Top left vs bottom right -label-cnc-map-generator-choice-mirror-top-matches-bottom = - .label = Top vs bottom -label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left = - .label = Top right vs bottom left +label-cnc-map-generator-choice-symmetry-mirror-horizontal = + .label = Mirror Horizontal +label-cnc-map-generator-choice-symmetry-mirror-vertical = + .label = Mirror Vertical +label-cnc-map-generator-choice-symmetry-mirror-diagonal-tl = + .label = Mirror Diagonal (Top-Left) +label-cnc-map-generator-choice-symmetry-mirror-diagonal-tr = + .label = Mirror Diagonal (Top-Right) +label-cnc-map-generator-choice-symmetry-mirror-2-rotations = + .label = 2 Rotations +label-cnc-map-generator-choice-symmetry-mirror-3-rotations = + .label = 3 Rotations +label-cnc-map-generator-choice-symmetry-mirror-4-rotations = + .label = 4 Rotations +label-cnc-map-generator-choice-symmetry-mirror-5-rotations = + .label = 5 Rotations +label-cnc-map-generator-choice-symmetry-mirror-6-rotations = + .label = 6 Rotations +label-cnc-map-generator-choice-symmetry-mirror-7-rotations = + .label = 7 Rotations +label-cnc-map-generator-choice-symmetry-mirror-8-rotations = + .label = 8 Rotations label-cnc-map-generator-option-shape = Bounds Shape label-cnc-map-generator-choice-shape-square = @@ -846,7 +858,7 @@ label-cnc-map-generator-choice-shape-circle-water = .label = Circle in water .description = Terrain generation is constrained to a circle enclosed by water -label-cnc-map-generator-option-players = Players per side +label-cnc-map-generator-option-players = Players label-cnc-map-generator-option-resources = Resources label-cnc-map-generator-choice-resources-none = diff --git a/mods/cnc/rules/map-generators.yaml b/mods/cnc/rules/map-generators.yaml index f4078384ab..90d2b32864 100644 --- a/mods/cnc/rules/map-generators.yaml +++ b/mods/cnc/rules/map-generators.yaml @@ -71,6 +71,8 @@ ForestObstacles: Trees UnplayableObstacles: Obstructions CivilianBuildingsObstacles: CivilianBuildings + Mirror: None + Rotations: 1 MultiChoiceOption@hidden_tileset_overrides: Choice@common: Tileset: DESERT,SNOW,TEMPERAT @@ -169,14 +171,14 @@ Mountains: 1000 Roughness: 850 MinimumTerrainContourSpacing: 5 - MultiIntegerChoiceOption@Rotations: - Label: label-cnc-map-generator-option-rotations - Parameter: Rotations + MultiIntegerChoiceOption@Players: + Label: label-cnc-map-generator-option-players + Parameter: Players Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 Default: 2 Priority: 1 - MultiChoiceOption@Mirror: - Label: label-cnc-map-generator-option-mirror + MultiChoiceOption@Symmetry: + Label: label-cnc-map-generator-option-symmetry Default: None Priority: 1 Choice@None: @@ -184,21 +186,60 @@ Settings: Mirror: None Choice@LeftMatchesRight: - Label: label-cnc-map-generator-choice-mirror-left-matches-right + Label: label-cnc-map-generator-choice-symmetry-mirror-horizontal + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: LeftMatchesRight Choice@TopLeftMatchesBottomRight: - Label: label-cnc-map-generator-choice-mirror-top-left-matches-bottom-right + Label: label-cnc-map-generator-choice-symmetry-mirror-diagonal-tl + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopLeftMatchesBottomRight Choice@TopMatchesBottom: - Label: label-cnc-map-generator-choice-mirror-top-matches-bottom + Label: label-cnc-map-generator-choice-symmetry-mirror-vertical + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopMatchesBottom Choice@TopRightMatchesBottomLeft: - Label: label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left + Label: label-cnc-map-generator-choice-symmetry-mirror-diagonal-tr + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopRightMatchesBottomLeft + Choice@2Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-2-rotations + Players: 2, 4, 6, 8, 10, 12, 14, 16 + Settings: + Rotations: 2 + Choice@3Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-3-rotations + Players: 3, 6, 9, 12, 15 + Settings: + Rotations: 3 + Choice@4Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-4-rotations + Players: 4, 8, 12, 16 + Settings: + Rotations: 4 + Choice@5Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-5-rotations + Players: 5, 10, 15 + Settings: + Rotations: 5 + Choice@6Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-6-rotations + Players: 6, 12 + Settings: + Rotations: 6 + Choice@7Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-7-rotations + Players: 7, 14 + Settings: + Rotations: 7 + Choice@8Rotations: + Label: label-cnc-map-generator-choice-symmetry-mirror-8-rotations + Players: 8, 16 + Settings: + Rotations: 8 MultiChoiceOption@Shape: Label: label-cnc-map-generator-option-shape Default: Square @@ -216,12 +257,6 @@ Tileset: DESERT Settings: ExternalCircularBias: -1 - MultiIntegerChoiceOption@Players: - Label: label-cnc-map-generator-option-players - Parameter: Players - Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 - Default: 1 - Priority: 1 MultiChoiceOption@Resources: Label: label-cnc-map-generator-option-resources Default: Medium diff --git a/mods/ra/fluent/rules.ftl b/mods/ra/fluent/rules.ftl index 2fcfe0fc18..6ea5f95804 100644 --- a/mods/ra/fluent/rules.ftl +++ b/mods/ra/fluent/rules.ftl @@ -1003,19 +1003,31 @@ label-ra-map-generator-choice-terrain-type-narrow-wetlands = .label = Narrow Wetlands .description = Tight mixtures of land and water -label-ra-map-generator-option-rotations = Rotations - -label-ra-map-generator-option-mirror = Mirror +label-ra-map-generator-option-symmetry = Symmetry label-ra-map-generator-choice-mirror-none = .label = None -label-ra-map-generator-choice-mirror-left-matches-right = - .label = Left vs right -label-ra-map-generator-choice-mirror-top-left-matches-bottom-right = - .label = Top left vs bottom right -label-ra-map-generator-choice-mirror-top-matches-bottom = - .label = Top vs bottom -label-ra-map-generator-choice-mirror-top-right-matches-bottom-left = - .label = Top right vs bottom left +label-ra-map-generator-choice-symmetry-mirror-horizontal = + .label = Mirror Horizontal +label-ra-map-generator-choice-symmetry-mirror-vertical = + .label = Mirror Vertical +label-ra-map-generator-choice-symmetry-mirror-diagonal-tl = + .label = Mirror Diagonal (Top-Left) +label-ra-map-generator-choice-symmetry-mirror-diagonal-tr = + .label = Mirror Diagonal (Top-Right) +label-ra-map-generator-choice-symmetry-mirror-2-rotations = + .label = 2 Rotations +label-ra-map-generator-choice-symmetry-mirror-3-rotations = + .label = 3 Rotations +label-ra-map-generator-choice-symmetry-mirror-4-rotations = + .label = 4 Rotations +label-ra-map-generator-choice-symmetry-mirror-5-rotations = + .label = 5 Rotations +label-ra-map-generator-choice-symmetry-mirror-6-rotations = + .label = 6 Rotations +label-ra-map-generator-choice-symmetry-mirror-7-rotations = + .label = 7 Rotations +label-ra-map-generator-choice-symmetry-mirror-8-rotations = + .label = 8 Rotations label-ra-map-generator-option-shape = Bounds Shape label-ra-map-generator-choice-shape-square = @@ -1028,7 +1040,7 @@ label-ra-map-generator-choice-shape-circle-water = .label = Circle in water .description = Terrain generation is constrained to a circle enclosed by water -label-ra-map-generator-option-players = Players per side +label-ra-map-generator-option-players = Players label-ra-map-generator-option-resources = Resources label-ra-map-generator-choice-resources-none = diff --git a/mods/ra/rules/map-generators.yaml b/mods/ra/rules/map-generators.yaml index 02aa14e51f..a22b684175 100644 --- a/mods/ra/rules/map-generators.yaml +++ b/mods/ra/rules/map-generators.yaml @@ -70,6 +70,8 @@ ForestObstacles: Trees UnplayableObstacles: Obstructions CivilianBuildingsObstacles: CivilianBuildings + Mirror: None + Rotations: 1 MultiChoiceOption@hidden_tileset_overrides: Choice@desert: Tileset: DESERT @@ -192,14 +194,14 @@ Forests: 0 SpawnBuildSize: 6 MinimumSpawnRadius: 4 - MultiIntegerChoiceOption@Rotations: - Label: label-ra-map-generator-option-rotations - Parameter: Rotations + MultiIntegerChoiceOption@Players: + Label: label-ra-map-generator-option-players + Parameter: Players Choices: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 Default: 2 Priority: 1 - MultiChoiceOption@Mirror: - Label: label-ra-map-generator-option-mirror + MultiChoiceOption@Symmetry: + Label: label-ra-map-generator-option-symmetry Default: None Priority: 1 Choice@None: @@ -207,21 +209,60 @@ Settings: Mirror: None Choice@LeftMatchesRight: - Label: label-ra-map-generator-choice-mirror-left-matches-right + Label: label-ra-map-generator-choice-symmetry-mirror-horizontal + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: LeftMatchesRight Choice@TopLeftMatchesBottomRight: - Label: label-ra-map-generator-choice-mirror-top-left-matches-bottom-right + Label: label-ra-map-generator-choice-symmetry-mirror-diagonal-tl + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopLeftMatchesBottomRight Choice@TopMatchesBottom: - Label: label-ra-map-generator-choice-mirror-top-matches-bottom + Label: label-ra-map-generator-choice-symmetry-mirror-vertical + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopMatchesBottom Choice@TopRightMatchesBottomLeft: - Label: label-ra-map-generator-choice-mirror-top-right-matches-bottom-left + Label: label-ra-map-generator-choice-symmetry-mirror-diagonal-tr + Players: 2, 4, 6, 8, 10, 12, 14, 16 Settings: Mirror: TopRightMatchesBottomLeft + Choice@2Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-2-rotations + Players: 2, 4, 6, 8, 10, 12, 14, 16 + Settings: + Rotations: 2 + Choice@3Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-3-rotations + Players: 3, 6, 9, 12, 15 + Settings: + Rotations: 3 + Choice@4Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-4-rotations + Players: 4, 8, 12, 16 + Settings: + Rotations: 4 + Choice@5Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-5-rotations + Players: 5, 10, 15 + Settings: + Rotations: 5 + Choice@6Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-6-rotations + Players: 6, 12 + Settings: + Rotations: 6 + Choice@7Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-7-rotations + Players: 7, 14 + Settings: + Rotations: 7 + Choice@8Rotations: + Label: label-ra-map-generator-choice-symmetry-mirror-8-rotations + Players: 8, 16 + Settings: + Rotations: 8 MultiChoiceOption@Shape: Label: label-ra-map-generator-option-shape Default: Square @@ -238,12 +279,6 @@ Label: label-ra-map-generator-choice-shape-circle-water Settings: ExternalCircularBias: -1 - MultiIntegerChoiceOption@Players: - Label: label-ra-map-generator-option-players - Parameter: Players - Choices: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 - Default: 1 - Priority: 1 MultiChoiceOption@Resources: Label: label-ra-map-generator-option-resources Default: Medium