diff --git a/OpenRA.Mods.Common/MapGenerator/Symmetry.cs b/OpenRA.Mods.Common/MapGenerator/Symmetry.cs
index 50e0cacdc2..b69ecc82af 100644
--- a/OpenRA.Mods.Common/MapGenerator/Symmetry.cs
+++ b/OpenRA.Mods.Common/MapGenerator/Symmetry.cs
@@ -278,26 +278,5 @@ namespace OpenRA.Mods.Common.MapGenerator
action(projections, original);
}
}
-
- ///
- /// Returns true iff xy is within reservationRadius of the center of a given CellLayer. If
- /// a mirroring is specified, the radius is measured from the mirror line instead of the
- /// center point.
- ///
- public static bool IsCPosNearCenter(
- CPos cpos,
- CellLayer cellLayer,
- int reservationRadius,
- Mirror mirror)
- {
- CPos[] testPoints;
- if (mirror == Mirror.None)
- testPoints = RotateAndMirrorCPos(cpos, cellLayer, 2, Mirror.None);
- else
- testPoints = RotateAndMirrorCPos(cpos, cellLayer, 1, mirror);
-
- var separation = (testPoints[1] - testPoints[0]).LengthSquared;
- return separation <= reservationRadius * reservationRadius * 4;
- }
}
}
diff --git a/OpenRA.Mods.Common/Traits/World/RaMapGenerator.cs b/OpenRA.Mods.Common/Traits/World/RaMapGenerator.cs
index d7de7b5df3..a6f0f7f96d 100644
--- a/OpenRA.Mods.Common/Traits/World/RaMapGenerator.cs
+++ b/OpenRA.Mods.Common/Traits/World/RaMapGenerator.cs
@@ -139,6 +139,8 @@ namespace OpenRA.Mods.Common.Traits
[FieldLoader.Require]
public readonly int SpawnBuildSize = default;
[FieldLoader.Require]
+ public readonly int MinimumSpawnRadius = default;
+ [FieldLoader.Require]
public readonly int SpawnResourceSpawns = default;
[FieldLoader.Require]
public readonly int SpawnReservation = default;
@@ -204,6 +206,8 @@ namespace OpenRA.Mods.Common.Traits
[FieldLoader.Ignore]
public readonly IReadOnlySet DominantTerrain;
[FieldLoader.Ignore]
+ public readonly IReadOnlySet ZoneableTerrain;
+ [FieldLoader.Ignore]
public readonly IReadOnlySet PartiallyPlayableCategories;
[FieldLoader.Ignore]
public readonly IReadOnlyList ClearSegmentTypes;
@@ -290,6 +294,7 @@ namespace OpenRA.Mods.Common.Traits
PartiallyPlayableTerrain = ParseTerrainIndexes("PartiallyPlayableTerrain");
UnplayableTerrain = ParseTerrainIndexes("UnplayableTerrain");
DominantTerrain = ParseTerrainIndexes("DominantTerrain");
+ ZoneableTerrain = ParseTerrainIndexes("ZoneableTerrain");
PartiallyPlayableCategories = my.NodeWithKey("PartiallyPlayableCategories").Value.Value
.Split(',', StringSplitOptions.RemoveEmptyEntries)
@@ -394,6 +399,8 @@ namespace OpenRA.Mods.Common.Traits
throw new MapGenerationException("SpawnReservation must be >= 1");
if (SpawnBuildSize < 1)
throw new MapGenerationException("SpawnBuildSize must be >= 1");
+ if (MinimumSpawnRadius < 1)
+ throw new MapGenerationException("MinimumSpawnRadius must be >= 1");
if (SpawnResourceSpawns < 0)
throw new MapGenerationException("SpawnResourceSpawns must be >= 0");
if (ResourceSpawnReservation < 1)
@@ -1167,18 +1174,23 @@ namespace OpenRA.Mods.Common.Traits
(projections, cpos) =>
projectionSpacing[cpos] = Symmetry.ProjectionProximity(projections) / 2);
- var spawnReservationRadius = minSpan * param.CentralSpawnReservationFraction / FractionMax;
- var spawnReservation = new CellLayer(map);
+ // Spawn bias tries to move spawns away from the map center and their symmetry
+ // projections.
+ var spawnBiasRadius = Math.Max(1, minSpan * param.CentralSpawnReservationFraction / FractionMax);
+ var spawnBias = new CellLayer(map);
+ spawnBias.Clear(spawnBiasRadius);
+ CellLayerUtils.OverCircle(
+ cellLayer: spawnBias,
+ wCenter: wMapCenter,
+ wRadius: 1024 * spawnBiasRadius,
+ outside: false,
+ action: (mpos, _, _, wrSq) => spawnBias[mpos] = (int)Exts.ISqrt(wrSq) / 1024);
foreach (var mpos in map.AllCells.MapCoords)
- spawnReservation[mpos] = Symmetry.IsCPosNearCenter(
- mpos.ToCPos(gridType),
- spawnReservation,
- spawnReservationRadius,
- param.Mirror);
+ spawnBias[mpos] = Math.Min(spawnBias[mpos], projectionSpacing[mpos]);
var zoneable = new CellLayer(map);
foreach (var mpos in map.AllCells.MapCoords)
- zoneable[mpos] = playableArea[mpos] && param.ClearTerrain.Contains(tileset.GetTerrainIndex(map.Tiles[mpos]));
+ zoneable[mpos] = playableArea[mpos] && param.ZoneableTerrain.Contains(tileset.GetTerrainIndex(map.Tiles[mpos]));
foreach (var actorPlan in actorPlans)
foreach (var cpos in actorPlan.Footprint().Keys)
@@ -1198,7 +1210,7 @@ namespace OpenRA.Mods.Common.Traits
zoneable = newZoneable;
}
- if (param.Rotations > 1 || param.Mirror != 0)
+ if (param.Rotations > 1 || param.Mirror != Symmetry.Mirror.None)
{
// Reserve the center of the map - otherwise it will mess with rotations
CellLayerUtils.OverCircle(
@@ -1221,14 +1233,15 @@ namespace OpenRA.Mods.Common.Traits
var spawnPreference = new CellLayer(map);
CellLayerUtils.ChebyshevRoom(spawnPreference, zoneable, false);
foreach (var mpos in map.AllCells.MapCoords)
- {
- var preference = Math.Min(spawnPreference[mpos], projectionSpacing[mpos]);
- if (spawnReservation[mpos] && preference > 1)
- preference = 1;
- else if (preference > param.SpawnRegionSize)
- preference = param.SpawnRegionSize;
- spawnPreference[mpos] = preference;
- }
+ if (spawnPreference[mpos] >= param.MinimumSpawnRadius &&
+ projectionSpacing[mpos] * 2 >= param.SpawnReservation + param.MinimumSpawnRadius)
+ {
+ spawnPreference[mpos] = spawnBias[mpos] * Math.Min(param.SpawnRegionSize, spawnPreference[mpos]);
+ }
+ else
+ {
+ spawnPreference[mpos] = 0;
+ }
var (chosenMPos, chosenValue) = CellLayerUtils.FindRandomBest(
spawnPreference,
@@ -1238,7 +1251,6 @@ namespace OpenRA.Mods.Common.Traits
if (chosenValue < 1)
throw new MapGenerationException("Not enough room for player spawns");
- var room = chosenValue - 1;
var spawn = new ActorPlan(map, "mpspawn")
{
Location = chosenMPos.ToCPos(gridType),
@@ -1299,7 +1311,7 @@ namespace OpenRA.Mods.Common.Traits
CellLayerUtils.OverCircle(
cellLayer: zoneable,
wCenter: projectedResourceSpawn.WPosLocation,
- wRadius: param.SpawnReservation * 1024,
+ wRadius: param.ResourceSpawnReservation * 1024,
outside: false,
action: (mpos, _, _, _) => zoneable[mpos] = false);
}
diff --git a/mods/cnc/rules/map-generators.yaml b/mods/cnc/rules/map-generators.yaml
index ac9403eceb..cd29a258e3 100644
--- a/mods/cnc/rules/map-generators.yaml
+++ b/mods/cnc/rules/map-generators.yaml
@@ -34,8 +34,9 @@
ResourceSpawnReservation: 8
SpawnRegionSize: 12
SpawnBuildSize: 8
+ MinimumSpawnRadius: 5
SpawnResourceSpawns: 3
- SpawnReservation: 20
+ SpawnReservation: 16
SpawnResourceBias: 1050
ResourcesPerPlayer: 50000
OreUniformity: 500
@@ -56,6 +57,7 @@
PartiallyPlayableTerrain: River,Tree,Water
UnplayableTerrain: Rock
DominantTerrain: River,Rock,Tree,Water
+ ZoneableTerrain: Clear,Road
PartiallyPlayableCategories: Beach,Road
ClearSegmentTypes: Clear
BeachSegmentTypes: Beach
diff --git a/mods/ra/rules/map-generators.yaml b/mods/ra/rules/map-generators.yaml
index 1b0b16d4d9..49e2952493 100644
--- a/mods/ra/rules/map-generators.yaml
+++ b/mods/ra/rules/map-generators.yaml
@@ -34,8 +34,9 @@
ResourceSpawnReservation: 8
SpawnRegionSize: 12
SpawnBuildSize: 8
+ MinimumSpawnRadius: 5
SpawnResourceSpawns: 3
- SpawnReservation: 20
+ SpawnReservation: 16
SpawnResourceBias: 1150
ResourcesPerPlayer: 50000
OreUniformity: 250
@@ -55,6 +56,7 @@
PartiallyPlayableTerrain: Tree
UnplayableTerrain: River,Rock
DominantTerrain: River,Rock,Tree,Water
+ ZoneableTerrain: Clear,Road
PartiallyPlayableCategories: Beach,Road
ClearSegmentTypes: Clear
BeachSegmentTypes: Beach
@@ -159,6 +161,7 @@
Settings:
Water: 800
Forests: 0
+ MinimumSpawnRadius: 4
Choice@LargeIslands:
Label: label-ra-map-generator-choice-terrain-type-large-islands
Settings:
@@ -181,6 +184,7 @@
TerrainFeatureSize: 5120
Forests: 0
SpawnBuildSize: 6
+ MinimumSpawnRadius: 4
Option@Rotations:
Label: label-ra-map-generator-option-rotations
SimpleChoice: Rotations