chore: Separated MapRegionResources with more functions related to placing resources in tiles

This commit is contained in:
Yair Morgenstern 2023-10-02 11:38:57 +03:00
parent 4d81a62b8f
commit f90d8ba267
2 changed files with 181 additions and 165 deletions

View File

@ -0,0 +1,146 @@
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.tile.ResourceType
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.components.extensions.randomWeighted
import kotlin.random.Random
/** This class deals with the internals of *how* to place resources in tiles
* It does not contain the logic of *when* to do so */
object MapRegionResources {
/** Given a [tileList] and possible [resourceOptions], will place a resource on every [frequency] tiles.
* Tries to avoid impacts, but falls back to lowest impact otherwise.
* Goes through the list in order, so pre-shuffle it!
* Assumes all tiles in the list are of the same terrain type when generating weightings, irrelevant if only one option.
* Respects terrainsCanBeFoundOn when there is only one option, unless [forcePlacement] is true.
* @return a map of the resources in the options list to number placed. */
fun placeResourcesInTiles(tileData: TileDataMap, frequency: Int, tileList: List<Tile>, resourceOptions: List<TileResource>,
baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false,
forcePlacement: Boolean = false): Map<TileResource, Int> {
if (tileList.none() || resourceOptions.isEmpty()) return mapOf()
val impactType = when (resourceOptions.first().resourceType) {
ResourceType.Strategic -> MapRegions.ImpactType.Strategic
ResourceType.Bonus -> MapRegions.ImpactType.Bonus
ResourceType.Luxury -> MapRegions.ImpactType.Luxury
}
val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
val weightings = resourceOptions.map {
val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
if (unique != null)
unique.params[0].toFloat()
else
1f
}
val testTerrains = (resourceOptions.size == 1) && !forcePlacement
val amountToPlace = (tileList.size / frequency) + 1
var amountPlaced = 0
val detailedPlaced = HashMap<TileResource, Int>()
resourceOptions.forEach { detailedPlaced[it] = 0 }
val fallbackTiles = ArrayList<Tile>()
// First pass - avoid impacts entirely
for (tile in tileList) {
if (tile.resource != null ||
(testTerrains &&
(tile.lastTerrain.name !in resourceOptions.first().terrainsCanBeFoundOn ||
resourceOptions.first().hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain)) ) ||
tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
continue // Can't place here, can't be a fallback tile
if (tileData[tile.position]!!.impacts.containsKey(impactType)) {
fallbackTiles.add(tile) // Taken but might be a viable fallback tile
} else {
// Add a resource to the tile
val resourceToPlace = resourceOptions.randomWeighted(weightings)
tile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
if (amountPlaced >= amountToPlace) {
return detailedPlaced
}
}
}
// Second pass - place on least impacted tiles
while (amountPlaced < amountToPlace && fallbackTiles.isNotEmpty()) {
// Sorry, we do need to re-sort the list for every pass since new impacts are made with every placement
val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
fallbackTiles.remove(bestTile)
val resourceToPlace = resourceOptions.randomWeighted(weightings)
bestTile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
}
return detailedPlaced
}
/** Attempts to place [amount] [resource] on [tiles], checking tiles in order. A [ratio] below 1 means skipping
* some tiles, ie ratio = 0.25 will put a resource on every 4th eligible tile. Can optionally respect impact flags,
* and places impact if [baseImpact] >= 0. Returns number of placed resources. */
fun tryAddingResourceToTiles(tileData: TileDataMap, resource: TileResource, amount: Int, tiles: Sequence<Tile>, ratio: Float = 1f,
respectImpacts: Boolean = false, baseImpact: Int = -1, randomImpact: Int = 0,
majorDeposit: Boolean = false): Int {
if (amount <= 0) return 0
var amountAdded = 0
var ratioProgress = 1f
val impactType = when (resource.resourceType) {
ResourceType.Luxury -> MapRegions.ImpactType.Luxury
ResourceType.Strategic -> MapRegions.ImpactType.Strategic
ResourceType.Bonus -> MapRegions.ImpactType.Bonus
}
for (tile in tiles) {
val conditionalTerrain = StateForConditionals(attackedTile = tile)
if (tile.resource == null &&
tile.lastTerrain.name in resource.terrainsCanBeFoundOn &&
!tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain) &&
!resource.hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain) &&
resource.getMatchingUniques(UniqueType.TileGenerationConditions).none {
tile.temperature!! !in it.params[0].toDouble() .. it.params[1].toDouble()
|| tile.humidity!! !in it.params[2].toDouble() .. it.params[3].toDouble()
}
) {
if (ratioProgress >= 1f &&
!(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
tile.setTileResource(resource, majorDeposit)
ratioProgress -= 1f
amountAdded++
if (baseImpact + randomImpact >= 0)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(
randomImpact + 1
)
)
if (amountAdded >= amount) break
}
ratioProgress += ratio
}
}
return amountAdded
}
/** Attempts to place major deposits in a [tileList] consisting exclusively of [terrain] tiles.
* Lifted out of the main function to allow postponing water resources.
* @return a map of resource types to placed deposits. */
fun placeMajorDeposits(tileData: TileDataMap, ruleset: Ruleset, tileList: List<Tile>, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map<TileResource, Int> {
if (tileList.isEmpty())
return mapOf()
val frequency = if (terrain.hasUnique(UniqueType.MajorStrategicFrequency))
terrain.getMatchingUniques(UniqueType.MajorStrategicFrequency).first().params[0].toInt()
else 25
val resourceOptions = ruleset.tileResources.values.filter {
it.resourceType == ResourceType.Strategic &&
((fallbackWeightings && terrain.name in it.terrainsCanBeFoundOn) ||
it.uniqueObjects.any { unique -> anonymizeUnique(unique).text == getTerrainRule(terrain,ruleset).text })
}
return if (resourceOptions.isNotEmpty())
placeResourcesInTiles(tileData, frequency, tileList, resourceOptions, baseImpact, randomImpact, true)
else
mapOf()
}
}

View File

@ -598,7 +598,7 @@ class MapRegions (val ruleset: Ruleset){
if (tileMap.mapParameters.mapResources == MapResources.strategicBalance) {
val candidateTiles = startTile.getTilesInDistanceRange(1..2).shuffled() + startTile.getTilesAtDistance(3).shuffled()
for (resource in ruleset.tileResources.values.filter { it.hasUnique(UniqueType.StrategicBalanceResource) }) {
if (tryAddingResourceToTiles(resource, 1, candidateTiles, majorDeposit = true) == 0) {
if (MapRegionResources.tryAddingResourceToTiles(tileData, resource, 1, candidateTiles, majorDeposit = true) == 0) {
// Fallback mode - force placement, even on an otherwise inappropriate terrain. Do still respect water and impassible tiles!
if (isWaterOnlyResource(resource))
MapRegionResources.placeResourcesInTiles(tileData, 999, candidateTiles.filter { it.isWater && !it.isImpassible() }.toList(), listOf(resource), majorDeposit = true, forcePlacement = true)
@ -620,7 +620,7 @@ class MapRegions (val ruleset: Ruleset){
}.shuffled()
val candidateTiles = startTile.getTilesAtDistance(2).shuffled()
for (resource in validResources) {
if (tryAddingResourceToTiles(resource, 1, candidateTiles, majorDeposit = false) > 0)
if (MapRegionResources.tryAddingResourceToTiles(tileData, resource, 1, candidateTiles, majorDeposit = false) > 0)
break
}
}
@ -1163,17 +1163,17 @@ class MapRegions (val ruleset: Ruleset){
// First check 2 inner rings
val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
targetLuxuries -= tryAddingResourceToTiles(luxuryToPlace, targetLuxuries, firstPass, 0.5f) // Skip every 2nd tile on first pass
targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(tileData, luxuryToPlace, targetLuxuries, firstPass, 0.5f) // Skip every 2nd tile on first pass
if (targetLuxuries > 0) {
val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3)
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
targetLuxuries -= tryAddingResourceToTiles(luxuryToPlace, targetLuxuries, secondPass)
targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(tileData, luxuryToPlace, targetLuxuries, secondPass)
}
if (targetLuxuries > 0) {
// Try adding in 1 luxury from the random rotation as compensation
for (luxury in randomLuxuries) {
if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, firstPass) > 0) break
if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, firstPass) > 0) break
}
}
}
@ -1192,7 +1192,7 @@ class MapRegions (val ruleset: Ruleset){
globalLuxuries.shuffled() + cityStateLuxuries.shuffled()
// Now try adding one until we are successful
for (luxury in candidateLuxuries) {
if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) break
if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) break
}
}
// Third add regional luxuries
@ -1211,7 +1211,7 @@ class MapRegions (val ruleset: Ruleset){
val candidates = if (isWaterOnlyResource(resource))
tileMap.getTilesInRectangle(region.rect).filter { it.isShoreOfContinent(region.continentID) }
else region.tiles.asSequence()
tryAddingResourceToTiles(resource, regionTargetNumber, candidates.shuffled(), 0.4f, true, 4, 2)
MapRegionResources.tryAddingResourceToTiles(tileData, resource, regionTargetNumber, candidates.shuffled(), 0.4f, true, 4, 2)
}
// Fourth add random luxuries
if (randomLuxuries.isNotEmpty()) {
@ -1231,7 +1231,7 @@ class MapRegions (val ruleset: Ruleset){
val minimum = max(3, minimumRandomLuxuries - index)
max(minimum, (targetRandomLuxuries * randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt())
}
tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, targetForThisLuxury, worldTiles, 0.25f,
MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, targetForThisLuxury, worldTiles, 0.25f,
true, 4, 2)
}
}
@ -1252,7 +1252,7 @@ class MapRegions (val ruleset: Ruleset){
candidateLuxuries += cityStateLuxuries.shuffled()
candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled()
for (luxury in candidateLuxuries) {
if (tryAddingResourceToTiles(ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) {
if (MapRegionResources.tryAddingResourceToTiles(tileData, ruleset.tileResources[luxury]!!, 1, tilesToCheck) > 0) {
if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now.
placedSpecials[luxury] = placedSpecials[luxury]!! + 1
break
@ -1268,7 +1268,7 @@ class MapRegions (val ruleset: Ruleset){
else -> (regions.size * 0.75f).toInt()
}
val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!)
tryAddingResourceToTiles(special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f,
MapRegionResources.tryAddingResourceToTiles(tileData, special, numberToPlace, tileMap.values.asSequence().shuffled(), 1f,
true, 6, 0)
}
}
@ -1357,9 +1357,10 @@ class MapRegions (val ruleset: Ruleset){
// First place major deposits on land
for (terrain in ruleset.terrains.values.filter { it.type != TerrainType.Water }) {
// Figure out if we generated a list for this terrain
val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain).text }.values.firstOrNull()
val terrainRule = getTerrainRule(terrain, ruleset)
val list = ruleLists.filterKeys { it.text == terrainRule.text }.values.firstOrNull()
?: continue // If not the terrain can be safely skipped
totalPlaced += placeMajorDeposits(list, terrain, fallbackStrategic, 2, 2)
totalPlaced += MapRegionResources.placeMajorDeposits(tileData, ruleset, list, terrain, fallbackStrategic, 2, 2)
}
// Second add some small deposits of modern strategic resources to city states
@ -1374,7 +1375,7 @@ class MapRegions (val ruleset: Ruleset){
.filterKeys { ruleset.nations[it]!!.isCityState }.values.map { it.first() }) {
val resourceToPlace = modernOptions.random()
totalPlaced[resourceToPlace] =
totalPlaced[resourceToPlace]!! + tryAddingResourceToTiles(resourceToPlace, 1, cityStateLocation.getTilesInDistanceRange(1..3))
totalPlaced[resourceToPlace]!! + MapRegionResources.tryAddingResourceToTiles(tileData, resourceToPlace, 1, cityStateLocation.getTilesInDistanceRange(1..3))
}
// Third add some minor deposits to land tiles
@ -1411,9 +1412,9 @@ class MapRegions (val ruleset: Ruleset){
// Fourth add water-based major deposits. Extra impact because we don't want them too clustered and there is usually lots to go around
for (terrain in ruleset.terrains.values.filter { it.type == TerrainType.Water }) {
// Figure out if we generated a list for this terrain
val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain).text }.values.firstOrNull()
val list = ruleLists.filterKeys { it.text == getTerrainRule(terrain, ruleset).text }.values.firstOrNull()
?: continue // If not the terrain can be safely skipped
totalPlaced += placeMajorDeposits(list, terrain, fallbackStrategic, 4, 3)
totalPlaced += MapRegionResources.placeMajorDeposits(tileData, ruleset, list, terrain, fallbackStrategic, 4, 3)
}
// Fifth place up to 2 extra deposits of each resource type if there is < 1 per civ
@ -1421,9 +1422,9 @@ class MapRegions (val ruleset: Ruleset){
val extraNeeded = min(2, regions.size - totalPlaced[resource]!!)
if (extraNeeded > 0) {
if (isWaterOnlyResource(resource))
tryAddingResourceToTiles(resource, extraNeeded, tileMap.values.asSequence().filter { it.isWater }.shuffled(), respectImpacts = true)
MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, tileMap.values.asSequence().filter { it.isWater }.shuffled(), respectImpacts = true)
else
tryAddingResourceToTiles(resource, extraNeeded, landList.asSequence(), respectImpacts = true)
MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, landList.asSequence(), respectImpacts = true)
}
}
@ -1467,97 +1468,17 @@ class MapRegions (val ruleset: Ruleset){
}
val candidateTiles = tileMap[region.startPosition!!].getTilesAtDistance(3).shuffled()
val amount = if (resourceUnique != null) 2 else 1 // Place an extra if the region type requests it
if (tryAddingResourceToTiles(resource, amount, candidateTiles) == 0) {
if (MapRegionResources.tryAddingResourceToTiles(tileData, resource, amount, candidateTiles) == 0) {
// We couldn't place any, try adding a fish instead
val fishyBonus = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus &&
it.terrainsCanBeFoundOn.any { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water } }
.randomOrNull()
if (fishyBonus != null)
tryAddingResourceToTiles(fishyBonus, 1, candidateTiles)
MapRegionResources.tryAddingResourceToTiles(tileData, fishyBonus, 1, candidateTiles)
}
}
}
/** Attempts to place [amount] [resource] on [tiles], checking tiles in order. A [ratio] below 1 means skipping
* some tiles, ie ratio = 0.25 will put a resource on every 4th eligible tile. Can optionally respect impact flags,
* and places impact if [baseImpact] >= 0. Returns number of placed resources. */
private fun tryAddingResourceToTiles(resource: TileResource, amount: Int, tiles: Sequence<Tile>, ratio: Float = 1f,
respectImpacts: Boolean = false, baseImpact: Int = -1, randomImpact: Int = 0,
majorDeposit: Boolean = false): Int {
if (amount <= 0) return 0
var amountAdded = 0
var ratioProgress = 1f
val impactType = when (resource.resourceType) {
ResourceType.Luxury -> ImpactType.Luxury
ResourceType.Strategic -> ImpactType.Strategic
ResourceType.Bonus -> ImpactType.Bonus
}
for (tile in tiles) {
val conditionalTerrain = StateForConditionals(attackedTile = tile)
if (tile.resource == null &&
tile.lastTerrain.name in resource.terrainsCanBeFoundOn &&
!tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain) &&
!resource.hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain) &&
resource.getMatchingUniques(UniqueType.TileGenerationConditions).none {
tile.temperature!! !in it.params[0].toDouble() .. it.params[1].toDouble()
|| tile.humidity!! !in it.params[2].toDouble() .. it.params[3].toDouble()
}
) {
if (ratioProgress >= 1f &&
!(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
tile.setTileResource(resource, majorDeposit)
ratioProgress -= 1f
amountAdded++
if (baseImpact + randomImpact >= 0)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
if (amountAdded >= amount) break
}
ratioProgress += ratio
}
}
return amountAdded
}
/** Attempts to place major deposits in a [tileList] consisting exclusively of [terrain] tiles.
* Lifted out of the main function to allow postponing water resources.
* @return a map of resource types to placed deposits. */
private fun placeMajorDeposits(tileList: List<Tile>, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map<TileResource, Int> {
if (tileList.isEmpty())
return mapOf()
val frequency = if (terrain.hasUnique(UniqueType.MajorStrategicFrequency))
terrain.getMatchingUniques(UniqueType.MajorStrategicFrequency).first().params[0].toInt()
else 25
val resourceOptions = ruleset.tileResources.values.filter {
it.resourceType == ResourceType.Strategic &&
((fallbackWeightings && terrain.name in it.terrainsCanBeFoundOn) ||
it.uniqueObjects.any { unique -> anonymizeUnique(unique).text == getTerrainRule(terrain).text })
}
return if (resourceOptions.isNotEmpty())
MapRegionResources.placeResourcesInTiles(tileData, frequency, tileList, resourceOptions, baseImpact, randomImpact, true)
else
mapOf()
}
/** @return a fake unique with the same conditionals, but sorted alphabetically.
* Used to save some memory and time when building resource lists. */
private fun anonymizeUnique(unique: Unique) = Unique(
"RULE" + unique.conditionals.sortedBy { it.text }.joinToString(prefix = " ", separator = " ") { "<" + it.text + ">" })
/** @return a fake unique with conditionals that will satisfy the same conditions as terrainsCanBeFoundOn */
private fun getTerrainRule(terrain: Terrain): Unique {
return if (terrain.type == TerrainType.TerrainFeature) {
if (terrain.hasUnique(UniqueType.VisibilityElevation))
Unique("RULE <in [${terrain.name}] tiles>")
else
Unique("RULE <in [${terrain.name}] tiles> " + ruleset.terrains.values
.filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) }
.joinToString(separator = " ") { "<in tiles without [${it.name}]>" })
} else
Unique("RULE <in [Featureless] [${terrain.name}] tiles>")
}
private fun isWaterOnlyResource(resource: TileResource) = resource.terrainsCanBeFoundOn
.all { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water }
@ -1570,73 +1491,22 @@ class MapRegions (val ruleset: Ruleset){
}
object MapRegionResources {
/** Given a [tileList] and possible [resourceOptions], will place a resource on every [frequency] tiles.
* Tries to avoid impacts, but falls back to lowest impact otherwise.
* Goes through the list in order, so pre-shuffle it!
* Assumes all tiles in the list are of the same terrain type when generating weightings, irrelevant if only one option.
* Respects terrainsCanBeFoundOn when there is only one option, unless [forcePlacement] is true.
* @return a map of the resources in the options list to number placed. */
fun placeResourcesInTiles(tileData:TileDataMap, frequency: Int, tileList: List<Tile>, resourceOptions: List<TileResource>,
baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false,
forcePlacement: Boolean = false): Map<TileResource, Int> {
if (tileList.none() || resourceOptions.isEmpty()) return mapOf()
val impactType = when (resourceOptions.first().resourceType) {
ResourceType.Strategic -> MapRegions.ImpactType.Strategic
ResourceType.Bonus -> MapRegions.ImpactType.Bonus
ResourceType.Luxury -> MapRegions.ImpactType.Luxury
}
val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
val weightings = resourceOptions.map {
val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
if (unique != null)
unique.params[0].toFloat()
else
1f
}
val testTerrains = (resourceOptions.size == 1) && !forcePlacement
val amountToPlace = (tileList.size / frequency) + 1
var amountPlaced = 0
val detailedPlaced = HashMap<TileResource, Int>()
resourceOptions.forEach { detailedPlaced[it] = 0 }
val fallbackTiles = ArrayList<Tile>()
// First pass - avoid impacts entirely
for (tile in tileList) {
if (tile.resource != null ||
(testTerrains &&
(tile.lastTerrain.name !in resourceOptions.first().terrainsCanBeFoundOn ||
resourceOptions.first().hasUnique(UniqueType.NoNaturalGeneration, conditionalTerrain)) ) ||
tile.getBaseTerrain().hasUnique(UniqueType.BlocksResources, conditionalTerrain))
continue // Can't place here, can't be a fallback tile
if (tileData[tile.position]!!.impacts.containsKey(impactType)) {
fallbackTiles.add(tile) // Taken but might be a viable fallback tile
} else {
// Add a resource to the tile
val resourceToPlace = resourceOptions.randomWeighted(weightings)
tile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
if (amountPlaced >= amountToPlace) {
return detailedPlaced
}
}
}
// Second pass - place on least impacted tiles
while (amountPlaced < amountToPlace && fallbackTiles.isNotEmpty()) {
// Sorry, we do need to re-sort the list for every pass since new impacts are made with every placement
val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
fallbackTiles.remove(bestTile)
val resourceToPlace = resourceOptions.randomWeighted(weightings)
bestTile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
detailedPlaced[resourceToPlace] = detailedPlaced[resourceToPlace]!! + 1
}
return detailedPlaced
}
/** @return a fake unique with the same conditionals, but sorted alphabetically.
* Used to save some memory and time when building resource lists. */
internal fun anonymizeUnique(unique: Unique) = Unique(
"RULE" + unique.conditionals.sortedBy { it.text }.joinToString(prefix = " ", separator = " ") { "<" + it.text + ">" })
/** @return a fake unique with conditionals that will satisfy the same conditions as terrainsCanBeFoundOn */
internal fun getTerrainRule(terrain: Terrain, ruleset: Ruleset): Unique {
return if (terrain.type == TerrainType.TerrainFeature) {
if (terrain.hasUnique(UniqueType.VisibilityElevation))
Unique("RULE <in [${terrain.name}] tiles>")
else
Unique("RULE <in [${terrain.name}] tiles> " + ruleset.terrains.values
.filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) }
.joinToString(separator = " ") { "<in tiles without [${it.name}]>" })
} else
Unique("RULE <in [Featureless] [${terrain.name}] tiles>")
}