Simplify Player and Symmetry options.

Player now specifies the total player count,
and the Symmetry options adjust to show only
valid options.
This commit is contained in:
Paul Chote
2025-04-18 11:12:32 +01:00
committed by Gustas Kažukauskas
parent 31154e3d2d
commit cf11c6633e
8 changed files with 197 additions and 74 deletions

View File

@@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.MapGenerator
FieldLoader.Load(this, yaml);
}
public abstract IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo);
public abstract IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo, int playerCount);
public virtual IEnumerable<string> GetFluentReferences()
{
@@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.MapGenerator
Value = Default;
}
public override IReadOnlyCollection<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
public override IReadOnlyCollection<MiniYamlNode> 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<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
public override IReadOnlyCollection<MiniYamlNode> 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<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
public override IReadOnlyCollection<MiniYamlNode> 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<string> ValidChoices(ITerrainInfo terrainInfo)
public List<string> 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<MiniYamlNode> GetSettings(ITerrainInfo terrainInfo)
public override IReadOnlyCollection<MiniYamlNode> 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));
}

View File

@@ -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<T>(IReadOnlyDictionary<T, int> 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<int>(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();

View File

@@ -980,6 +980,8 @@ namespace OpenRA.Mods.Common.Traits
/// <summary>Returns the options that allow users to customise the result.</summary>
List<MapGeneratorOption> Options { get; }
int PlayerCount { get; }
void Randomize(MersenneTwister random);
/// <summary>Merge all choices into a complete settings MiniYaml.</summary>

View File

@@ -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<LabelWidget>("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();

View File

@@ -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 =

View File

@@ -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

View File

@@ -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 =

View File

@@ -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