Add CnC map generator support

Adds map generator support for CnC (all tilesets), largely on par with
RA.

Most tilesets do not have a complete set of beach templates and
therefore do not support terrain settings involving water. DESERT is the
only tileset supporting water. Unlike in RA, water is not playable space
in CnC (no naval units), so only terrain settings with small bodies of
water are available.

Most changes are configuration (tileset and map generator config), with
just a small number of code changes.
This commit is contained in:
Ashley Newson
2025-01-08 23:08:28 +00:00
committed by Gustas
parent bdc50899de
commit 2126f3c5a2
13 changed files with 4881 additions and 18 deletions

View File

@@ -32,9 +32,6 @@ namespace OpenRA.Mods.Common.Lint
// Includes validation of actor types and template IDs.
var multiBrush = new MultiBrush(map, info);
// Validates there is at least something in the MultiBrush.
multiBrush.Contract();
foreach (var (_, tile) in multiBrush.Tiles)
if (!templatedTerrainInfo.TryGetTerrainInfo(tile, out var _))
emitError($"Tileset {terrainInfoName} has invalid MultiBrush collection `{collectionName}`: tileset does not have tile {tile.Type},{tile.Index}");

View File

@@ -33,27 +33,29 @@ namespace OpenRA.Mods.Common.MapGenerator
public sealed class Choice
{
/// <summary>Uniquely identifies a Choice within an Option.</summary>
[FieldLoader.Ignore]
public readonly string Id;
/// <summary>The label to use for UI selection. Post-fluent.</summary>
[FieldLoader.Ignore]
public readonly string Label = null;
/// <summary>Only offer the Choice for this tileset. (If null, show for all.)</summary>
public readonly string Tileset = null;
/// <summary>
/// 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>
[FieldLoader.LoadUsing(nameof(SettingsLoader))]
public readonly MiniYaml Settings;
public Choice(string id, MiniYaml my)
{
Id = id;
FieldLoader.Load(this, my);
var label = my.NodeWithKeyOrDefault("Label")?.Value.Value;
if (label != null)
Label = FluentProvider.GetMessage(label);
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>
@@ -72,12 +74,10 @@ namespace OpenRA.Mods.Common.MapGenerator
references.Add(label);
}
static MiniYaml SettingsLoader(MiniYaml my) => my.NodeWithKey("Settings").Value;
/// <summary>Check whether this choice is permitted for this map.</summary>
public bool Allowed(Map map)
{
if (Tileset != null && map.Tileset != Tileset)
if (Tileset != null && !Tileset.Contains(map.Tileset))
return false;
return true;
@@ -208,12 +208,18 @@ namespace OpenRA.Mods.Common.MapGenerator
if (Choices.Count > 0)
{
var defaultNode = my.NodeWithKeyOrDefault("Default");
if (defaultNode != null)
var defaultOrder = my.NodeWithKeyOrDefault("Default")?.Value.Value;
if (defaultOrder != null)
{
Default = Choices.FirstOrDefault(choice => choice.Id == defaultNode.Value.Value);
foreach (var defaultChoice in defaultOrder.Split(','))
{
Default = Choices.FirstOrDefault(choice => choice.Id == defaultChoice);
if (Default != null)
break;
}
if (Default == null)
throw new YamlException($"Option `{id}` default choice `{defaultNode.Value.Value}` is not valid");
throw new YamlException($"None of option `{id}`'s default choices `{defaultOrder}` are not valid");
}
else
{

View File

@@ -126,7 +126,7 @@ namespace OpenRA.Mods.Common.MapGenerator
else if (!hasTiles && hasActorPlans)
return Replaceability.Actor;
else
throw new ArgumentException("MultiBrush has no tiles or actors");
return Replaceability.None;
}
/// <summary>
@@ -190,7 +190,10 @@ namespace OpenRA.Mods.Common.MapGenerator
foreach (var cpos in actorPlan.Footprint().Keys)
xys.Add(new CVec(cpos.X, cpos.Y));
shape = xys.OrderBy(xy => (xy.Y, xy.X)).ToArray();
if (xys.Count != 0)
shape = xys.OrderBy(xy => (xy.Y, xy.X)).ToArray();
else
shape = new[] { new CVec(0, 0) };
}
/// <summary>

View File

@@ -174,6 +174,8 @@ namespace OpenRA.Mods.Common.Traits
public readonly IReadOnlyList<MultiBrush> ForestObstacles;
[FieldLoader.Ignore]
public readonly IReadOnlyList<MultiBrush> UnplayableObstacles;
[FieldLoader.Ignore]
public readonly IReadOnlyDictionary<ushort, IReadOnlyList<MultiBrush>> RepaintTiles;
[FieldLoader.Ignore]
public readonly IReadOnlyDictionary<string, ResourceTypeInfo> ResourceTypes;
@@ -221,6 +223,16 @@ namespace OpenRA.Mods.Common.Traits
TemplatedTerrainInfo = Map.Rules.TerrainInfo as ITemplatedTerrainInfo;
ForestObstacles = MultiBrush.LoadCollection(map, my.NodeWithKey("ForestObstacles").Value.Value);
UnplayableObstacles = MultiBrush.LoadCollection(map, my.NodeWithKey("UnplayableObstacles").Value.Value);
RepaintTiles = my.NodeWithKeyOrDefault("RepaintTiles")?.Value.ToDictionary(
k =>
{
if (Exts.TryParseUshortInvariant(k, out var tile))
return tile;
else
throw new YamlException($"RepaintTile {k} is not a ushort");
},
v => MultiBrush.LoadCollection(map, v.Value) as IReadOnlyList<MultiBrush>);
RepaintTiles ??= ImmutableDictionary<ushort, IReadOnlyList<MultiBrush>>.Empty;
ResourceTypes = map.Rules.Actors[SystemActors.World].TraitInfoOrDefault<ResourceLayerInfo>().ResourceTypes;
if (!ResourceTypes.TryGetValue(my.NodeWithKey("DefaultResource").Value.Value, out DefaultResource))
@@ -559,6 +571,7 @@ namespace OpenRA.Mods.Common.Traits
var expansionRandom = new MersenneTwister(random.Next());
var buildingRandom = new MersenneTwister(random.Next());
var topologyRandom = new MersenneTwister(random.Next());
var repaintRandom = new MersenneTwister(random.Next());
TerrainTile PickTile(ushort tileType)
{
@@ -1601,6 +1614,19 @@ namespace OpenRA.Mods.Common.Traits
}
}
// Cosmetically repaint tiles
foreach (var (tile, collection) in param.RepaintTiles.OrderBy(kv => kv.Key))
{
var replace = new CellLayer<MultiBrush.Replaceability>(map);
foreach (var mpos in replace.CellRegion.MapCoords)
replace[mpos] =
map.Tiles[mpos].Type == tile
? MultiBrush.Replaceability.Any
: MultiBrush.Replaceability.None;
MultiBrush.PaintArea(map, actorPlans, replace, collection, repaintRandom);
}
map.PlayerDefinitions = new MapPlayers(map.Rules, 0).ToMiniYaml();
map.ActorDefinitions = actorPlans
.Select((plan, i) => new MiniYamlNode($"Actor{i}", plan.Reference.Save()))

View File

@@ -51,6 +51,9 @@ faction-nod =
and the alien substance Tiberium. They use stealth technology
and guerilla tactics to defeat those who oppose them.
map-generator-ra = RA Experimental (CnC)
map-generator-clear = Clear
## defaults.yaml
notification-unit-lost = Unit lost.
notification-unit-promoted = Unit promoted.
@@ -776,3 +779,65 @@ bot-watson =
bot-hal9001 =
.name = HAL 9001
## map-generators.yaml
label-clear-map-generator-option-tile = Tile
label-clear-map-generator-choice-tile-clear = Clear
label-clear-map-generator-choice-tile-water = Water
label-cnc-map-generator-option-seed = Seed
label-cnc-map-generator-option-terrain-type = Terrain Type
label-cnc-map-generator-choice-terrain-type-lakes = Lakes
label-cnc-map-generator-choice-terrain-type-puddles = Puddles
label-cnc-map-generator-choice-terrain-type-gardens = Gardens
label-cnc-map-generator-choice-terrain-type-plains = Plains
label-cnc-map-generator-choice-terrain-type-parks = Parks
label-cnc-map-generator-choice-terrain-type-woodlands = Woodlands
label-cnc-map-generator-choice-terrain-type-overgrown = Overgrown
label-cnc-map-generator-choice-terrain-type-rocky = Rocky
label-cnc-map-generator-choice-terrain-type-mountains = Mountains
label-cnc-map-generator-choice-terrain-type-mountain-lakes = Mountain Lakes
label-cnc-map-generator-option-rotations = Rotations
label-cnc-map-generator-option-mirror = Mirror
label-cnc-map-generator-choice-mirror-none = None
label-cnc-map-generator-choice-mirror-left-matches-right = Left vs right
label-cnc-map-generator-choice-mirror-top-left-matches-bottom-right = Top left vs bottom right
label-cnc-map-generator-choice-mirror-top-matches-bottom = Top vs bottom
label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left = Top right vs bottom left
label-cnc-map-generator-option-shape = Bounds Shape
label-cnc-map-generator-choice-shape-square = Square
label-cnc-map-generator-choice-shape-circle-mountain = Circle in mountains
label-cnc-map-generator-choice-shape-circle-water = Circle in water
label-cnc-map-generator-option-players = Players per side
label-cnc-map-generator-option-resources = Resources
label-cnc-map-generator-choice-resources-none = None
label-cnc-map-generator-choice-resources-low = Low
label-cnc-map-generator-choice-resources-medium = Medium
label-cnc-map-generator-choice-resources-high = High
label-cnc-map-generator-choice-resources-very-high = Very High
label-cnc-map-generator-choice-resources-full = Oreful
label-cnc-map-generator-option-buildings = Buildings
label-cnc-map-generator-choice-buildings-none = None
label-cnc-map-generator-choice-buildings-standard = Standard
label-cnc-map-generator-choice-buildings-extra = Extra
label-cnc-map-generator-choice-buildings-oil-only = Oil Only
label-cnc-map-generator-choice-buildings-oil-rush = Oil Rush
label-cnc-map-generator-option-density = Entity Density
label-cnc-map-generator-choice-density-players = Scale with players
label-cnc-map-generator-choice-density-area-and-players = Scale with area and players
label-cnc-map-generator-choice-density-area-very-low = Scale with area (very low density)
label-cnc-map-generator-choice-density-area-low = Scale with area (low density)
label-cnc-map-generator-choice-density-area-medium = Scale with area (medium density)
label-cnc-map-generator-choice-density-area-high = Scale with area (high density)
label-cnc-map-generator-choice-density-area-very-high = Scale with area (very high density)
label-cnc-map-generator-option-roads = Roads
label-cnc-map-generator-option-deny-walled-areas = Obstruct walled areas

View File

@@ -59,6 +59,7 @@ Rules:
cnc|rules/ships.yaml
cnc|rules/aircraft.yaml
cnc|rules/husks.yaml
cnc|rules/map-generators.yaml
Sequences:
cnc|sequences/structures.yaml

View File

@@ -0,0 +1,384 @@
^MapGenerators:
RaMapGenerator@ra:
Type: ra
Name: map-generator-ra
Settings:
Option@hidden_defaults:
Choice@hidden_defaults:
Settings:
TerrainFeatureSize: 20.0
ForestFeatureSize: 20.0
ResourceFeatureSize: 20.0
Water: 0.0
Mountains: 0.1
Forests: 0.025
ForestCutout: 2
MaximumCutoutSpacing: 12
TerrainSmoothing: 4
SmoothingThreshold: 0.833333
MinimumLandSeaThickness: 5
MinimumMountainThickness: 5
MaximumAltitude: 8
RoughnessRadius: 5
Roughness: 0.5
MinimumTerrainContourSpacing: 6
MinimumCliffLength: 10
ForestClumpiness: 0.5
DenyWalledAreas: True
EnforceSymmetry: 0
Roads: True
RoadSpacing: 5
RoadShrink: 0
CreateEntities: True
CentralSpawnReservationFraction: 0.25
ResourceSpawnReservation: 8
SpawnRegionSize: 12
SpawnBuildSize: 8
SpawnResourceSpawns: 3
SpawnReservation: 20
SpawnResourceBias: 1.05
ResourcesPerPlayer: 50000
OreUniformity: 0.5
OreClumpiness: 0.5
MaximumExpansionResourceSpawns: 5
MaximumResourceSpawnsPerExpansion: 2
MinimumExpansionSize: 2
MaximumExpansionSize: 12
ExpansionInner: 2
ExpansionBorder: 1
DefaultResource: Tiberium
ResourceSpawnSeeds:
split2: Tiberium
split3: Tiberium
splitblue: BlueTiberium
ClearTerrain: Clear
PlayableTerrain: Beach,BlueTiberium,Bridge,Clear,Road,Rough,Tiberium,Wall
PartiallyPlayableTerrain: River,Tree,Water
UnplayableTerrain: Rock
DominantTerrain: River,Rock,Tree,Water
PartiallyPlayableCategories: Beach,Road
ClearSegmentTypes: Clear
BeachSegmentTypes: Beach
CliffSegmentTypes: Cliff
RoadSegmentTypes: Road,RoadIn,RoadOut
ForestObstacles: Trees
UnplayableObstacles: Obstructions
Option@hidden_tileset_overrides:
Choice@common:
Tileset: DESERT,JUNGLE,SNOW,TEMPERAT
Settings:
LandTile: 255
WaterTile: 1
RepaintTiles:
1: Water
Choice@winter:
Tileset: WINTER
Settings:
LandTile: 255
WaterTile: 1
RepaintTiles:
255: Snow
1: Water
Option@Seed:
Label: label-cnc-map-generator-option-seed
Random: True
Default: 0
Integer: Seed
Option@TerrainType:
Label: label-cnc-map-generator-option-terrain-type
Priority: 2
Default: Gardens,Rocky
Choice@Lakes:
Label: label-cnc-map-generator-choice-terrain-type-lakes
Tileset: DESERT
Settings:
Water: 0.2
Choice@Puddles:
Label: label-cnc-map-generator-choice-terrain-type-puddles
Tileset: DESERT
Settings:
Water: 0.1
Choice@Gardens:
Label: label-cnc-map-generator-choice-terrain-type-gardens
Tileset: DESERT
Settings:
Water: 0.05
Forests: 0.3
ForestCutout: 3
EnforceSymmetry: 2
RoadSpacing: 3
RoadShrink: 4
Choice@Plains:
Label: label-cnc-map-generator-choice-terrain-type-plains
Settings:
Water: 0.0
Choice@Parks:
Label: label-cnc-map-generator-choice-terrain-type-parks
Settings:
Water: 0.0
Forests: 0.1
Choice@Woodlands:
Label: label-cnc-map-generator-choice-terrain-type-woodlands
Settings:
Water: 0.0
Forests: 0.4
ForestCutout: 3
EnforceSymmetry: 2
RoadSpacing: 3
RoadShrink: 4
Choice@Overgrown:
Label: label-cnc-map-generator-choice-terrain-type-overgrown
Settings:
Water: 0.0
Forests: 0.5
EnforceSymmetry: 2
Mountains: 0.5
Roughness: 0.25
Choice@Rocky:
Label: label-cnc-map-generator-choice-terrain-type-rocky
Settings:
Water: 0.0
Forests: 0.3
ForestCutout: 3
EnforceSymmetry: 2
Mountains: 0.5
Roughness: 0.25
RoadSpacing: 3
RoadShrink: 4
Choice@Mountains:
Label: label-cnc-map-generator-choice-terrain-type-mountains
Settings:
Water: 0.0
Mountains: 1.0
Roughness: 0.60
MinimumTerrainContourSpacing: 5
Choice@MountainLakes:
Label: label-cnc-map-generator-choice-terrain-type-mountain-lakes
Tileset: DESERT
Settings:
Water: 0.2
Mountains: 1.0
Roughness: 0.85
MinimumTerrainContourSpacing: 5
Option@Rotations:
Label: label-cnc-map-generator-option-rotations
SimpleChoice: Rotations
Values: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
Default: 2
Priority: 1
Option@Mirror:
Label: label-cnc-map-generator-option-mirror
Default: None
Priority: 1
Choice@None:
Label: label-cnc-map-generator-choice-mirror-none
Settings:
Mirror: None
Choice@LeftMatchesRight:
Label: label-cnc-map-generator-choice-mirror-left-matches-right
Settings:
Mirror: LeftMatchesRight
Choice@TopLeftMatchesBottomRight:
Label: label-cnc-map-generator-choice-mirror-top-left-matches-bottom-right
Settings:
Mirror: TopLeftMatchesBottomRight
Choice@TopMatchesBottom:
Label: label-cnc-map-generator-choice-mirror-top-matches-bottom
Settings:
Mirror: TopMatchesBottom
Choice@TopRightMatchesBottomLeft:
Label: label-cnc-map-generator-choice-mirror-top-right-matches-bottom-left
Settings:
Mirror: TopRightMatchesBottomLeft
Option@Shape:
Label: label-cnc-map-generator-option-shape
Default: Square
Priority: 1
Choice@Square:
Label: label-cnc-map-generator-choice-shape-square
Settings:
ExternalCircularBias: 0
Choice@CircleMountain:
Label: label-cnc-map-generator-choice-shape-circle-mountain
Settings:
ExternalCircularBias: 1
Choice@CircleWater:
Label: label-cnc-map-generator-choice-shape-circle-water
Tileset: DESERT
Settings:
ExternalCircularBias: -1
Option@Players:
Label: label-cnc-map-generator-option-players
SimpleChoice: Players
Values: 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
Default: 1
Priority: 1
Option@Resources:
Label: label-cnc-map-generator-option-resources
Default: Medium
Choice@None:
Label: label-cnc-map-generator-choice-resources-none
Settings:
SpawnResourceSpawns: 0
ResourcesPerPlayer: 0
ResourceSpawnWeights:
MaximumExpansionResourceSpawns: 0
MaximumResourceSpawnsPerExpansion: 1
Choice@Low:
Label: label-cnc-map-generator-choice-resources-low
Settings:
SpawnResourceSpawns: 1
ResourcesPerPlayer: 25000
ResourceSpawnWeights:
split2: 1.0
split3: 1.0
MaximumExpansionResourceSpawns: 2
MaximumResourceSpawnsPerExpansion: 1
Choice@Medium:
Label: label-cnc-map-generator-choice-resources-medium
Settings:
SpawnResourceSpawns: 2
ResourcesPerPlayer: 50000
ResourceSpawnWeights:
split2: 0.95
split3: 0.95
splitblue: 0.10
MaximumExpansionResourceSpawns: 3
MaximumResourceSpawnsPerExpansion: 1
Choice@High:
Label: label-cnc-map-generator-choice-resources-high
Settings:
SpawnResourceSpawns: 3
ResourcesPerPlayer: 75000
ResourceSpawnWeights:
split2: 0.9
split3: 0.9
splitblue: 0.2
MaximumExpansionResourceSpawns: 5
MaximumResourceSpawnsPerExpansion: 2
Choice@VeryHigh:
Label: label-cnc-map-generator-choice-resources-very-high
Settings:
SpawnResourceSpawns: 4
ResourcesPerPlayer: 100000
ResourceSpawnWeights:
split2: 0.8
split3: 0.8
splitblue: 0.4
MaximumExpansionResourceSpawns: 8
MaximumResourceSpawnsPerExpansion: 2
Choice@Full:
Label: label-cnc-map-generator-choice-resources-full
Settings:
SpawnResourceSpawns: 0
ResourcesPerPlayer: 1000000000
ResourceSpawnWeights:
MaximumExpansionResourceSpawns: 0
MaximumResourceSpawnsPerExpansion: 1
Option@Buildings:
Label: label-cnc-map-generator-option-buildings
Default: Standard
Choice@None:
Label: label-cnc-map-generator-choice-buildings-none
Settings:
MinimumBuildings: 0
MaximumBuildings: 0
BuildingWeights:
Choice@Standard:
Label: label-cnc-map-generator-choice-buildings-standard
Settings:
MinimumBuildings: 0
MaximumBuildings: 3
BuildingWeights:
hosp: 2
miss: 1
v19: 9
Choice@Extra:
Label: label-cnc-map-generator-choice-buildings-extra
Settings:
MinimumBuildings: 3
MaximumBuildings: 6
BuildingWeights:
hosp: 2
miss: 1
v19: 9
gtwr: 2
Choice@OilOnly:
Label: label-cnc-map-generator-choice-buildings-oil-only
Settings:
MinimumBuildings: 0
MaximumBuildings: 3
BuildingWeights:
v19: 1
Choice@OilRush:
Label: label-cnc-map-generator-choice-buildings-oil-rush
Settings:
MinimumBuildings: 8
MaximumBuildings: 10
BuildingWeights:
v19: 1
Option@Density:
Label: label-cnc-map-generator-option-density
Default: Players
Priority: 1
Choice@Players:
Label: label-cnc-map-generator-choice-density-players
Settings:
AreaEntityBonus: 0.0
PlayerCountEntityBonus: 1.0
Choice@AreaAndPlayers:
Label: label-cnc-map-generator-choice-density-area-and-players
Settings:
AreaEntityBonus: 0.0002
PlayerCountEntityBonus: 0.5
Choice@AreaVeryLow:
Label: label-cnc-map-generator-choice-density-area-very-low
Settings:
AreaEntityBonus: 0.0001
PlayerCountEntityBonus: 0.0
Choice@AreaLow:
Label: label-cnc-map-generator-choice-density-area-low
Settings:
AreaEntityBonus: 0.0002
PlayerCountEntityBonus: 0.0
Choice@AreaMedium:
Label: label-cnc-map-generator-choice-density-area-medium
Settings:
AreaEntityBonus: 0.0004
PlayerCountEntityBonus: 0.0
Choice@AreaHigh:
Label: label-cnc-map-generator-choice-density-area-high
Settings:
AreaEntityBonus: 0.0006
PlayerCountEntityBonus: 0.0
Choice@AreaVeryHigh:
Label: label-cnc-map-generator-choice-density-area-very-high
Settings:
AreaEntityBonus: 0.0008
PlayerCountEntityBonus: 0.0
Option@DenyWalledArea:
Label: label-cnc-map-generator-option-deny-walled-areas
Checkbox: DenyWalledAreas
Default: True
Priority: 1
Option@Roads:
Label: label-cnc-map-generator-option-roads
Checkbox: Roads
Default: True
Priority: 1
ClearMapGenerator@clear:
Type: clear
Name: map-generator-clear
Settings:
Option@Tile:
Label: label-clear-map-generator-option-tile
Choice@CommonClear:
Label: label-clear-map-generator-choice-tile-clear
Tileset: DESERT,JUNGLE,SNOW,TEMPERAT,WINTER
Settings:
Tile: 255
Choice@CommonWater:
Label: label-clear-map-generator-choice-tile-water
Tileset: DESERT,JUNGLE,SNOW,TEMPERAT,WINTER
Settings:
Tile: 1

View File

@@ -280,6 +280,7 @@ World:
EditorWorld:
Inherits: ^BaseWorld
Inherits@MapGenerators: ^MapGenerators
EditorActorLayer:
EditorCursorLayer:
EditorResourceLayer:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff