mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-25 21:03:15 -04:00
chore: Separated MapRegionResources with more functions related to placing resources in tiles
This commit is contained in:
parent
4d81a62b8f
commit
f90d8ba267
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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>")
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user