Current map generation is extremely dependant on specific terrains existing in the ruleset. (#6067)

* Current map generation is extremely dependant on specific terrains existing in the ruleset.
This attempts to eliminate those dependencies.
All changes indicate areas where a crash occured before the change.

I still encounter problems in generateRegions when trying to generate a map with no water tiles - @SimorCedar I think the splitRegion doesn't like the fact that there are land tiles on edges of the map?

* Split 'equal fertility' regions as close to the center as possible
Also, don't crash if no luxury resources are defined
This commit is contained in:
Yair Morgenstern 2022-01-29 23:28:00 +02:00 committed by GitHub
parent a237e7bf82
commit 461385fff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 34 additions and 16 deletions

View File

@ -793,7 +793,7 @@ open class TileInfo {
// Uninitialized tilemap - when you're displaying a tile in the civilopedia or map editor
if (::tileMap.isInitialized) convertHillToTerrainFeature()
if (!ruleset.terrains.containsKey(baseTerrain))
throw Exception()
throw Exception("Terrain $baseTerrain does not exist in ruleset!")
baseTerrainObject = ruleset.terrains[baseTerrain]!!
isWater = getBaseTerrain().type == TerrainType.Water
isLand = getBaseTerrain().type == TerrainType.Land

View File

@ -9,6 +9,7 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.Nation
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unique.UniqueType
import kotlin.math.abs
@ -94,8 +95,10 @@ class TileMap {
/** creates a hexagonal map of given radius (filled with grassland) */
constructor(radius: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
startingLocations.clear()
val firstAvailableLandTerrain = ruleset.terrains.values.firstOrNull { it.type==TerrainType.Land }
?: throw Exception("Cannot create map - no land terrains found!")
for (vector in HexMath.getVectorsInDistance(Vector2.Zero, radius, worldWrap))
tileList.add(TileInfo().apply { position = vector; baseTerrain = Constants.grassland })
tileList.add(TileInfo().apply { position = vector; baseTerrain = firstAvailableLandTerrain.name })
setTransients(ruleset)
}
@ -205,8 +208,11 @@ class TileMap {
/** @return all tiles within [rectangle], respecting world edges and wrap.
* If using even Q coordinates the rectangle will be "straight" ie parallel with rectangular map edges. */
fun getTilesInRectangle(rectangle: Rectangle, evenQ: Boolean = false): Sequence<TileInfo> =
if (rectangle.width <= 0 || rectangle.height <= 0)
sequenceOf(get(rectangle.x.toInt(), rectangle.y.toInt()))
if (rectangle.width <= 0 || rectangle.height <= 0) {
val tile = getIfTileExistsOrNull(rectangle.x.toInt(), rectangle.y.toInt())
if (tile == null) sequenceOf()
else sequenceOf(tile)
}
else
sequence {
for (x in 0 until rectangle.width.toInt()) {

View File

@ -400,15 +400,16 @@ class MapGenerator(val ruleset: Ruleset) {
// Old, static map generation rules - necessary for existing base ruleset mods to continue to function
if (noTerrainUniques) {
tile.baseTerrain = when {
temperature < -0.4 -> if (humidity < 0.5) Constants.snow else Constants.tundra
temperature < 0.8 -> if (humidity < 0.5) Constants.plains else Constants.grassland
val autoTerrain = when {
temperature < -0.4 -> if (humidity < 0.5) Constants.snow else Constants.tundra
temperature < 0.8 -> if (humidity < 0.5) Constants.plains else Constants.grassland
temperature <= 1.0 -> if (humidity < 0.7) Constants.desert else Constants.plains
else -> {
println("applyHumidityAndTemperature: Invalid temperature $temperature")
Constants.grassland
}
}
if (ruleset.terrains.containsKey(autoTerrain)) tile.baseTerrain = autoTerrain
tile.setTerrainTransients()
continue
}
@ -432,7 +433,7 @@ class MapGenerator(val ruleset: Ruleset) {
*/
private fun spawnVegetation(tileMap: TileMap) {
val vegetationSeed = randomness.RNG.nextInt().toDouble()
val candidateTerrains = Constants.vegetation.flatMap{ ruleset.terrains[it]!!.occursOn }
val candidateTerrains = Constants.vegetation.mapNotNull { ruleset.terrains[it] }.flatMap{ it.occursOn }
//Checking it.baseTerrain in candidateTerrains to make sure forest does not spawn on desert hill
for (tile in tileMap.values.asSequence().filter { it.baseTerrain in candidateTerrains
&& it.getLastTerrain().name in candidateTerrains }) {

View File

@ -154,8 +154,11 @@ class MapRegions (val ruleset: Ruleset){
var bestSplitPoint = 1 // will be the size of the split-off region
var closestFertility = 0
var cumulativeFertility = 0
val pointsToTry = if (widerThanTall) 1..regionToSplit.rect.width.toInt()
else 1..regionToSplit.rect.height.toInt()
val highestPointToTry = if (widerThanTall) regionToSplit.rect.width.toInt()
else regionToSplit.rect.height.toInt()
val pointsToTry = 1..highestPointToTry
val halfwayPoint = highestPointToTry/2
for (splitPoint in pointsToTry) {
val nextRect = if (widerThanTall)
@ -175,7 +178,11 @@ class MapRegions (val ruleset: Ruleset){
nextRect.sumOf { if (it.getContinent() == splitOffRegion.continentID) it.getTileFertility(true) else 0 }
// Better than last try?
if (abs(cumulativeFertility - targetFertility) <= abs(closestFertility - targetFertility)) {
val bestSplitPointFertilityDeltaFromTarget = abs(closestFertility - targetFertility)
val currentSplitPointFertilityDeltaFromTarget = abs(cumulativeFertility - targetFertility)
if (currentSplitPointFertilityDeltaFromTarget < bestSplitPointFertilityDeltaFromTarget
|| (currentSplitPointFertilityDeltaFromTarget == bestSplitPointFertilityDeltaFromTarget // same fertility split but better 'amount of tiles' split
&& abs(halfwayPoint- splitPoint) < abs(halfwayPoint- bestSplitPoint) )) { // current split point is closer to the halfway point
bestSplitPoint = splitPoint
closestFertility = cumulativeFertility
}
@ -1137,7 +1144,7 @@ class MapRegions (val ruleset: Ruleset){
targetLuxuries++
}
val luxuryToPlace = ruleset.tileResources[region.luxury]!!
val luxuryToPlace = ruleset.tileResources[region.luxury] ?: continue
// First check 2 inner rings
val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
@ -1184,7 +1191,7 @@ class MapRegions (val ruleset: Ruleset){
}
regionTargetNumber = max(1, regionTargetNumber)
for (region in regions) {
val resource = ruleset.tileResources[region.luxury]!!
val resource = ruleset.tileResources[region.luxury] ?: continue
if (isWaterOnlyResource(resource))
tryAddingResourceToTiles(resource, regionTargetNumber,
tileMap.getTilesInRectangle(region.rect).filter { it.isWater && it.neighbors.any { neighbor -> neighbor.getContinent() == region.continentID } }.shuffled(),

View File

@ -16,17 +16,21 @@ class RiverGenerator(
}
fun spawnRivers() {
if (tileMap.values.none { it.isWater }) return
val numberOfRivers = tileMap.values.count { it.isLand } / MAP_TILES_PER_RIVER
var optionalTiles = tileMap.values.asSequence()
.filter { it.baseTerrain == Constants.mountain && it.isFarEnoughFromWater() }.toMutableList()
.filter { it.baseTerrain == Constants.mountain && it.isFarEnoughFromWater() }
.toMutableList()
if (optionalTiles.size < numberOfRivers)
optionalTiles.addAll(tileMap.values.filter { it.isHill() && it.isFarEnoughFromWater() })
if (optionalTiles.size < numberOfRivers)
optionalTiles = tileMap.values.filter { it.isLand && it.isFarEnoughFromWater() }.toMutableList()
optionalTiles =
tileMap.values.filter { it.isLand && it.isFarEnoughFromWater() }.toMutableList()
val mapRadius = tileMap.mapParameters.mapSize.radius
val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius)
val riverStarts =
randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius)
for (tile in riverStarts) spawnRiver(tile)
for (tile in tileMap.values) {