From f90d8ba26795fe511faf541b4605b1038893543f Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Mon, 2 Oct 2023 11:38:57 +0300 Subject: [PATCH] chore: Separated MapRegionResources with more functions related to placing resources in tiles --- .../mapregions/MapRegionResources.kt | 146 +++++++++++++ .../map/mapgenerator/mapregions/MapRegions.kt | 200 +++--------------- 2 files changed, 181 insertions(+), 165 deletions(-) create mode 100644 core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt new file mode 100644 index 0000000000..c2ba1b5b32 --- /dev/null +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegionResources.kt @@ -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, resourceOptions: List, + baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false, + forcePlacement: Boolean = false): Map { + 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() + resourceOptions.forEach { detailedPlaced[it] = 0 } + val fallbackTiles = ArrayList() + // 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, 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, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map { + 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() + } + +} diff --git a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt index 350a91c205..848c8d6bc5 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/mapregions/MapRegions.kt @@ -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, 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, terrain: Terrain, fallbackWeightings: Boolean, baseImpact: Int, randomImpact: Int): Map { - 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 ") - else - Unique("RULE " + ruleset.terrains.values - .filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) } - .joinToString(separator = " ") { "" }) - } else - Unique("RULE ") - } - 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, resourceOptions: List, - baseImpact: Int = 0, randomImpact: Int = 0, majorDeposit: Boolean = false, - forcePlacement: Boolean = false): Map { - 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() - resourceOptions.forEach { detailedPlaced[it] = 0 } - val fallbackTiles = ArrayList() - // 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 ") + else + Unique("RULE " + ruleset.terrains.values + .filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) } + .joinToString(separator = " ") { "" }) + } else + Unique("RULE ") }