diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index b0eaee60f3..f07c16b02f 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -8,6 +8,7 @@ import com.unciv.logic.map.MapParameters import com.unciv.logic.map.MapShape import com.unciv.logic.map.MapType import com.unciv.logic.map.TileMap +import com.unciv.logic.map.mapgenerator.mapregions.MapRegions import com.unciv.logic.map.tile.Tile import com.unciv.models.Counter import com.unciv.models.metadata.GameParameters diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt new file mode 100644 index 0000000000..7a1a174d6c --- /dev/null +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapGenTileData.kt @@ -0,0 +1,94 @@ +package com.unciv.logic.map.mapgenerator.mapregions + +import com.unciv.logic.map.tile.Tile +import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.UniqueType +import kotlin.math.max +import kotlin.math.min + +// Holds a bunch of tile info that is only interesting during map gen +class MapGenTileData(val tile: Tile, val region: Region?, ruleset: Ruleset) { + var closeStartPenalty = 0 + val impacts = HashMap() + var isFood = false + private set + var isProd = false + private set + var isGood = false + private set + var isJunk = false + private set + var isTwoFromCoast = false + private set + + var isGoodStart = true + var startScore = 0 + + init { + evaluate(ruleset) + } + + fun addCloseStartPenalty(penalty: Int) { + if (closeStartPenalty == 0) + closeStartPenalty = penalty + else { + // Multiple overlapping values - take the higher one and add 20 % + closeStartPenalty = max(closeStartPenalty, penalty) + closeStartPenalty = min(97, (closeStartPenalty * 1.2f).toInt()) + } + } + + /** Populates all private-set fields */ + private fun evaluate(ruleset: Ruleset) { + // Check if we are two tiles from coast (a bad starting site) + if (!tile.isCoastalTile() && tile.neighbors.any { it.isCoastalTile() }) + isTwoFromCoast = true + + // Check first available out of unbuildable features, then other features, then base terrain + val terrainToCheck = if (tile.terrainFeatures.isEmpty()) tile.getBaseTerrain() + else tile.terrainFeatureObjects.firstOrNull { it.unbuildable } + ?: tile.terrainFeatureObjects.first() + + // Add all applicable qualities + for (unique in terrainToCheck.getMatchingUniques( + UniqueType.HasQuality, + StateForConditionals(region = region) + )) { + when (unique.params[0]) { + "Food" -> isFood = true + "Desirable" -> isGood = true + "Production" -> isProd = true + "Undesirable" -> isJunk = true + } + } + + // Were there in fact no explicit qualities defined for any region at all? If so let's guess at qualities to preserve mod compatibility. + if (terrainToCheck.uniqueObjects.none { it.type == UniqueType.HasQuality }) { + if (tile.isWater) return // Most water type tiles have no qualities + + // is it junk??? + if (terrainToCheck.impassable) { + isJunk = true + return // Don't bother checking the rest, junk is junk + } + + // Take possible improvements into account + val improvements = ruleset.tileImprovements.values.filter { + terrainToCheck.name in it.terrainsCanBeBuiltOn && + it.uniqueTo == null && + !it.hasUnique(UniqueType.GreatImprovement) + } + + val maxFood = terrainToCheck.food + (improvements.maxOfOrNull { it.food } ?: 0f) + val maxProd = terrainToCheck.production + (improvements.maxOfOrNull { it.production } ?: 0f) + val bestImprovementValue = improvements.maxOfOrNull { it.food + it.production + it.gold + it.culture + it.science + it.faith } ?: 0f + val maxOverall = terrainToCheck.food + terrainToCheck.production + terrainToCheck.gold + + terrainToCheck.culture + terrainToCheck.science + terrainToCheck.faith + bestImprovementValue + + if (maxFood >= 2) isFood = true + if (maxProd >= 2) isProd = true + if (maxOverall >= 3) isGood = true + } + } +} diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt similarity index 92% rename from core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt rename to core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt index a486a510f9..fbc0a95471 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt @@ -1,4 +1,4 @@ -package com.unciv.logic.map.mapgenerator +package com.unciv.logic.map.mapgenerator.mapregions import com.badlogic.gdx.math.Rectangle import com.badlogic.gdx.math.Vector2 @@ -7,6 +7,7 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.map.MapResources import com.unciv.logic.map.MapShape import com.unciv.logic.map.TileMap +import com.unciv.logic.map.mapgenerator.mapregions.MapRegions.BiasTypes.PositiveFallback import com.unciv.logic.map.tile.Tile import com.unciv.models.metadata.GameParameters import com.unciv.models.ruleset.Ruleset @@ -245,8 +246,7 @@ class MapRegions (val ruleset: Ruleset){ // Generate tile data for all tiles for (tile in tileMap.values) { - val newData = MapGenTileData(tile, regions.firstOrNull { it.tiles.contains(tile) }) - newData.evaluate(ruleset) + val newData = MapGenTileData(tile, regions.firstOrNull { it.tiles.contains(tile) }, ruleset) tileData[tile.position] = newData } @@ -1628,156 +1628,6 @@ class MapRegions (val ruleset: Ruleset){ MinorCiv, } - // Holds a bunch of tile info that is only interesting during map gen - class MapGenTileData(val tile: Tile, val region: Region?) { - var closeStartPenalty = 0 - val impacts = HashMap() - var isFood = false - var isProd = false - var isGood = false - var isJunk = false - var isTwoFromCoast = false - - var isGoodStart = true - var startScore = 0 - - fun addCloseStartPenalty(penalty: Int) { - if (closeStartPenalty == 0) - closeStartPenalty = penalty - else { - // Multiple overlapping values - take the higher one and add 20 % - closeStartPenalty = max(closeStartPenalty, penalty) - closeStartPenalty = min(97, (closeStartPenalty * 1.2f).toInt()) - } - } - - fun evaluate(ruleset: Ruleset) { - // Check if we are two tiles from coast (a bad starting site) - if (!tile.isCoastalTile() && tile.neighbors.any { it.isCoastalTile() }) - isTwoFromCoast = true - - // Check first available out of unbuildable features, then other features, then base terrain - val terrainToCheck = if (tile.terrainFeatures.isEmpty()) tile.getBaseTerrain() - else tile.terrainFeatureObjects.firstOrNull { it.unbuildable } - ?: tile.terrainFeatureObjects.first() - - // Add all applicable qualities - for (unique in terrainToCheck.getMatchingUniques(UniqueType.HasQuality, StateForConditionals(region = region))) { - when (unique.params[0]) { - "Food" -> isFood = true - "Desirable" -> isGood = true - "Production" -> isProd = true - "Undesirable" -> isJunk = true - } - } - - // Were there in fact no explicit qualities defined for any region at all? If so let's guess at qualities to preserve mod compatibility. - if (terrainToCheck.uniqueObjects.none { it.type == UniqueType.HasQuality }) { - if (tile.isWater) return // Most water type tiles have no qualities - - // is it junk??? - if (terrainToCheck.impassable) { - isJunk = true - return // Don't bother checking the rest, junk is junk - } - - // Take possible improvements into account - val improvements = ruleset.tileImprovements.values.filter { - terrainToCheck.name in it.terrainsCanBeBuiltOn && - it.uniqueTo == null && - !it.hasUnique(UniqueType.GreatImprovement) - } - - val maxFood = terrainToCheck.food + (improvements.maxOfOrNull { it.food } ?: 0f) - val maxProd = terrainToCheck.production + (improvements.maxOfOrNull { it.production } ?: 0f) - val bestImprovementValue = improvements.maxOfOrNull { it.food + it.production + it.gold + it.culture + it.science + it.faith } ?: 0f - val maxOverall = terrainToCheck.food + terrainToCheck.production + terrainToCheck.gold + - terrainToCheck.culture + terrainToCheck.science + terrainToCheck.faith + bestImprovementValue - - if (maxFood >= 2) isFood = true - if (maxProd >= 2) isProd = true - if (maxOverall >= 3) isGood = true - } - } - } } -class Region (val tileMap: TileMap, val rect: Rectangle, val continentID: Int = -1) { - val tiles = HashSet() - val terrainCounts = HashMap() - var totalFertility = 0 - var type = "Hybrid" // being an undefined or indeterminate type - var luxury: String? = null - var startPosition: Vector2? = null - val assignedMinorCivs = ArrayList() - var affectedByWorldWrap = false - - /** Recalculates tiles and fertility */ - fun updateTiles(trim: Boolean = true) { - totalFertility = 0 - var minColumn = 99999f - var maxColumn = -99999f - var minRow = 99999f - var maxRow = -99999f - - val columnHasTile = HashSet() - - tiles.clear() - for (tile in tileMap.getTilesInRectangle(rect).filter { - continentID == -1 || it.getContinent() == continentID } ) { - val fertility = tile.getTileFertility(continentID != -1) - tiles.add(tile) - totalFertility += fertility - - if (affectedByWorldWrap) - columnHasTile.add(tile.getColumn()) - - if (trim) { - val row = tile.getRow().toFloat() - val column = tile.getColumn().toFloat() - minColumn = min(minColumn, column) - maxColumn = max(maxColumn, column) - minRow = min(minRow, row) - maxRow = max(maxRow, row) - } - } - - if (trim) { - if (affectedByWorldWrap) // Need to be more thorough with origin longitude - rect.x = columnHasTile.filter { !columnHasTile.contains(it - 1) }.maxOf { it }.toFloat() - else - rect.x = minColumn // ez way for non-wrapping regions - rect.y = minRow - rect.height = maxRow - minRow + 1 - if (affectedByWorldWrap && minColumn < rect.x) { // Thorough way - rect.width = columnHasTile.size.toFloat() - } else { - rect.width = maxColumn - minColumn + 1 // ez way - affectedByWorldWrap = false // also we're not wrapping anymore - } - } - } - - /** Counts the terrains in the Region for type and start determination */ - fun countTerrains() { - // Count terrains in the region - terrainCounts.clear() - for (tile in tiles) { - val terrainsToCount = if (tile.terrainHasUnique(UniqueType.IgnoreBaseTerrainForRegion)) - tile.terrainFeatureObjects.map { it.name }.asSequence() - else - tile.allTerrains.map { it.name } - for (terrain in terrainsToCount) { - terrainCounts[terrain] = (terrainCounts[terrain] ?: 0) + 1 - } - if (tile.isCoastalTile()) - terrainCounts["Coastal"] = (terrainCounts["Coastal"] ?: 0) + 1 - } - } - - /** Returns number terrains with [name] */ - fun getTerrainAmount(name: String) = terrainCounts[name] ?: 0 - - override fun toString() = "Region($type, ${tiles.size} tiles, ${terrainCounts.entries.joinToString { "${it.value} ${it.key}" }})" -} diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt new file mode 100644 index 0000000000..550e4eaeaf --- /dev/null +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/Region.kt @@ -0,0 +1,90 @@ +package com.unciv.logic.map.mapgenerator.mapregions + +import com.badlogic.gdx.math.Rectangle +import com.badlogic.gdx.math.Vector2 +import com.unciv.logic.civilization.Civilization +import com.unciv.logic.map.TileMap +import com.unciv.logic.map.tile.Tile +import com.unciv.models.ruleset.unique.UniqueType +import kotlin.math.max +import kotlin.math.min + +class Region (val tileMap: TileMap, val rect: Rectangle, val continentID: Int = -1) { + val tiles = HashSet() + val terrainCounts = HashMap() + var totalFertility = 0 + var type = "Hybrid" // being an undefined or indeterminate type + var luxury: String? = null + var startPosition: Vector2? = null + val assignedMinorCivs = ArrayList() + + var affectedByWorldWrap = false + + /** Recalculates tiles and fertility */ + fun updateTiles(trim: Boolean = true) { + totalFertility = 0 + var minColumn = 99999f + var maxColumn = -99999f + var minRow = 99999f + var maxRow = -99999f + + val columnHasTile = HashSet() + + tiles.clear() + for (tile in tileMap.getTilesInRectangle(rect).filter { + continentID == -1 || it.getContinent() == continentID } ) { + val fertility = tile.getTileFertility(continentID != -1) + tiles.add(tile) + totalFertility += fertility + + if (affectedByWorldWrap) + columnHasTile.add(tile.getColumn()) + + if (trim) { + val row = tile.getRow().toFloat() + val column = tile.getColumn().toFloat() + minColumn = min(minColumn, column) + maxColumn = max(maxColumn, column) + minRow = min(minRow, row) + maxRow = max(maxRow, row) + } + } + + if (trim) { + if (affectedByWorldWrap) // Need to be more thorough with origin longitude + rect.x = columnHasTile.filter { !columnHasTile.contains(it - 1) }.maxOf { it }.toFloat() + else + rect.x = minColumn // ez way for non-wrapping regions + rect.y = minRow + rect.height = maxRow - minRow + 1 + if (affectedByWorldWrap && minColumn < rect.x) { // Thorough way + rect.width = columnHasTile.size.toFloat() + } else { + rect.width = maxColumn - minColumn + 1 // ez way + affectedByWorldWrap = false // also we're not wrapping anymore + } + } + } + + /** Counts the terrains in the Region for type and start determination */ + fun countTerrains() { + // Count terrains in the region + terrainCounts.clear() + for (tile in tiles) { + val terrainsToCount = if (tile.terrainHasUnique(UniqueType.IgnoreBaseTerrainForRegion)) + tile.terrainFeatureObjects.map { it.name }.asSequence() + else + tile.allTerrains.map { it.name } + for (terrain in terrainsToCount) { + terrainCounts[terrain] = (terrainCounts[terrain] ?: 0) + 1 + } + if (tile.isCoastalTile()) + terrainCounts["Coastal"] = (terrainCounts["Coastal"] ?: 0) + 1 + } + } + + /** Returns number terrains with [name] */ + fun getTerrainAmount(name: String) = terrainCounts[name] ?: 0 + + override fun toString() = "Region($type, ${tiles.size} tiles, ${terrainCounts.entries.joinToString { "${it.value} ${it.key}" }})" +} diff --git a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt index 6704982c38..e4aaa3015a 100644 --- a/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt +++ b/core/src/com/unciv/models/ruleset/unique/StateForConditionals.kt @@ -4,7 +4,7 @@ import com.unciv.logic.battle.CombatAction import com.unciv.logic.battle.ICombatant import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization -import com.unciv.logic.map.mapgenerator.Region +import com.unciv.logic.map.mapgenerator.mapregions.Region import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.tile.Tile