mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 22:06:05 -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) {
|
if (tileMap.mapParameters.mapResources == MapResources.strategicBalance) {
|
||||||
val candidateTiles = startTile.getTilesInDistanceRange(1..2).shuffled() + startTile.getTilesAtDistance(3).shuffled()
|
val candidateTiles = startTile.getTilesInDistanceRange(1..2).shuffled() + startTile.getTilesAtDistance(3).shuffled()
|
||||||
for (resource in ruleset.tileResources.values.filter { it.hasUnique(UniqueType.StrategicBalanceResource) }) {
|
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!
|
// Fallback mode - force placement, even on an otherwise inappropriate terrain. Do still respect water and impassible tiles!
|
||||||
if (isWaterOnlyResource(resource))
|
if (isWaterOnlyResource(resource))
|
||||||
MapRegionResources.placeResourcesInTiles(tileData, 999, candidateTiles.filter { it.isWater && !it.isImpassible() }.toList(), listOf(resource), majorDeposit = true, forcePlacement = true)
|
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()
|
}.shuffled()
|
||||||
val candidateTiles = startTile.getTilesAtDistance(2).shuffled()
|
val candidateTiles = startTile.getTilesAtDistance(2).shuffled()
|
||||||
for (resource in validResources) {
|
for (resource in validResources) {
|
||||||
if (tryAddingResourceToTiles(resource, 1, candidateTiles, majorDeposit = false) > 0)
|
if (MapRegionResources.tryAddingResourceToTiles(tileData, resource, 1, candidateTiles, majorDeposit = false) > 0)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1163,17 +1163,17 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
// First check 2 inner rings
|
// First check 2 inner rings
|
||||||
val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
|
val firstPass = tileMap[region.startPosition!!].getTilesInDistanceRange(1..2)
|
||||||
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
|
.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) {
|
if (targetLuxuries > 0) {
|
||||||
val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3)
|
val secondPass = firstPass + tileMap[region.startPosition!!].getTilesAtDistance(3)
|
||||||
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
|
.shuffled().sortedBy { it.getTileFertility(false) } // Check bad tiles first
|
||||||
targetLuxuries -= tryAddingResourceToTiles(luxuryToPlace, targetLuxuries, secondPass)
|
targetLuxuries -= MapRegionResources.tryAddingResourceToTiles(tileData, luxuryToPlace, targetLuxuries, secondPass)
|
||||||
}
|
}
|
||||||
if (targetLuxuries > 0) {
|
if (targetLuxuries > 0) {
|
||||||
// Try adding in 1 luxury from the random rotation as compensation
|
// Try adding in 1 luxury from the random rotation as compensation
|
||||||
for (luxury in randomLuxuries) {
|
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()
|
globalLuxuries.shuffled() + cityStateLuxuries.shuffled()
|
||||||
// Now try adding one until we are successful
|
// Now try adding one until we are successful
|
||||||
for (luxury in candidateLuxuries) {
|
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
|
// Third add regional luxuries
|
||||||
@ -1211,7 +1211,7 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
val candidates = if (isWaterOnlyResource(resource))
|
val candidates = if (isWaterOnlyResource(resource))
|
||||||
tileMap.getTilesInRectangle(region.rect).filter { it.isShoreOfContinent(region.continentID) }
|
tileMap.getTilesInRectangle(region.rect).filter { it.isShoreOfContinent(region.continentID) }
|
||||||
else region.tiles.asSequence()
|
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
|
// Fourth add random luxuries
|
||||||
if (randomLuxuries.isNotEmpty()) {
|
if (randomLuxuries.isNotEmpty()) {
|
||||||
@ -1231,7 +1231,7 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
val minimum = max(3, minimumRandomLuxuries - index)
|
val minimum = max(3, minimumRandomLuxuries - index)
|
||||||
max(minimum, (targetRandomLuxuries * randomLuxuryRatios[randomLuxuries.size]!![index] + 0.5f).toInt())
|
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)
|
true, 4, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1252,7 +1252,7 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
candidateLuxuries += cityStateLuxuries.shuffled()
|
candidateLuxuries += cityStateLuxuries.shuffled()
|
||||||
candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled()
|
candidateLuxuries += regions.mapNotNull { it.luxury }.shuffled()
|
||||||
for (luxury in candidateLuxuries) {
|
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.
|
if (placedSpecials.containsKey(luxury)) // Keep track of marble-type specials as they may be placed now.
|
||||||
placedSpecials[luxury] = placedSpecials[luxury]!! + 1
|
placedSpecials[luxury] = placedSpecials[luxury]!! + 1
|
||||||
break
|
break
|
||||||
@ -1268,7 +1268,7 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
else -> (regions.size * 0.75f).toInt()
|
else -> (regions.size * 0.75f).toInt()
|
||||||
}
|
}
|
||||||
val numberToPlace = max(2, targetNumber - placedSpecials[special.name]!!)
|
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)
|
true, 6, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1357,9 +1357,10 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
// First place major deposits on land
|
// First place major deposits on land
|
||||||
for (terrain in ruleset.terrains.values.filter { it.type != TerrainType.Water }) {
|
for (terrain in ruleset.terrains.values.filter { it.type != TerrainType.Water }) {
|
||||||
// Figure out if we generated a list for this terrain
|
// 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
|
?: 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
|
// 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() }) {
|
.filterKeys { ruleset.nations[it]!!.isCityState }.values.map { it.first() }) {
|
||||||
val resourceToPlace = modernOptions.random()
|
val resourceToPlace = modernOptions.random()
|
||||||
totalPlaced[resourceToPlace] =
|
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
|
// 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
|
// 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 }) {
|
for (terrain in ruleset.terrains.values.filter { it.type == TerrainType.Water }) {
|
||||||
// Figure out if we generated a list for this terrain
|
// 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
|
?: 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
|
// 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]!!)
|
val extraNeeded = min(2, regions.size - totalPlaced[resource]!!)
|
||||||
if (extraNeeded > 0) {
|
if (extraNeeded > 0) {
|
||||||
if (isWaterOnlyResource(resource))
|
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
|
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 candidateTiles = tileMap[region.startPosition!!].getTilesAtDistance(3).shuffled()
|
||||||
val amount = if (resourceUnique != null) 2 else 1 // Place an extra if the region type requests it
|
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
|
// We couldn't place any, try adding a fish instead
|
||||||
val fishyBonus = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus &&
|
val fishyBonus = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Bonus &&
|
||||||
it.terrainsCanBeFoundOn.any { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water } }
|
it.terrainsCanBeFoundOn.any { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water } }
|
||||||
.randomOrNull()
|
.randomOrNull()
|
||||||
if (fishyBonus != null)
|
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
|
private fun isWaterOnlyResource(resource: TileResource) = resource.terrainsCanBeFoundOn
|
||||||
.all { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water }
|
.all { terrainName -> ruleset.terrains[terrainName]!!.type == TerrainType.Water }
|
||||||
|
|
||||||
@ -1570,73 +1491,22 @@ class MapRegions (val ruleset: Ruleset){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object MapRegionResources {
|
/** @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 + ">" })
|
||||||
|
|
||||||
/** 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.
|
/** @return a fake unique with conditionals that will satisfy the same conditions as terrainsCanBeFoundOn */
|
||||||
* Goes through the list in order, so pre-shuffle it!
|
internal fun getTerrainRule(terrain: Terrain, ruleset: Ruleset): Unique {
|
||||||
* Assumes all tiles in the list are of the same terrain type when generating weightings, irrelevant if only one option.
|
return if (terrain.type == TerrainType.TerrainFeature) {
|
||||||
* Respects terrainsCanBeFoundOn when there is only one option, unless [forcePlacement] is true.
|
if (terrain.hasUnique(UniqueType.VisibilityElevation))
|
||||||
* @return a map of the resources in the options list to number placed. */
|
Unique("RULE <in [${terrain.name}] tiles>")
|
||||||
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
|
else
|
||||||
1f
|
Unique("RULE <in [${terrain.name}] tiles> " + ruleset.terrains.values
|
||||||
}
|
.filter { it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.VisibilityElevation) }
|
||||||
val testTerrains = (resourceOptions.size == 1) && !forcePlacement
|
.joinToString(separator = " ") { "<in tiles without [${it.name}]>" })
|
||||||
val amountToPlace = (tileList.size / frequency) + 1
|
} else
|
||||||
var amountPlaced = 0
|
Unique("RULE <in [Featureless] [${terrain.name}] tiles>")
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user