diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index f10d6d77dc..0d6df69081 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -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 diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index ea9337f461..2c8ead8dcd 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -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 = - 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()) { diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 0b202f84ea..9d2cbbbfee 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -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 }) { diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt index eaf276ac0f..0c6a904921 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt @@ -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(), diff --git a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt index 79f22bff60..f9c9fabc1c 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt @@ -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) {