Unified 'does resource generate naturally on' checks to include all uniques everywhere

This commit is contained in:
Yair Morgenstern 2024-01-25 19:52:19 +02:00
parent 36baea9250
commit 903963787a
9 changed files with 67 additions and 64 deletions

View File

@ -23,7 +23,6 @@ import com.unciv.utils.debug
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.math.sign import kotlin.math.sign
@ -342,8 +341,7 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
// remove the tiles where previous resources have been placed // remove the tiles where previous resources have been placed
val suitableTiles = candidateTiles val suitableTiles = candidateTiles
.filterNot { it.baseTerrain == Constants.snow && it.isHill() } .filterNot { it.baseTerrain == Constants.snow && it.isHill() }
.filter { it.resource == null .filter { it.resource == null && resource.generatesNaturallyOn(it) }
&& resource.terrainsCanBeFoundOn.contains(it.lastTerrain.name) }
val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, mapRadius) val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, mapRadius)
@ -361,8 +359,10 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
val suitableTiles = tileMap.values val suitableTiles = tileMap.values
.filterNot { it.baseTerrain == Constants.snow && it.isHill() } .filterNot { it.baseTerrain == Constants.snow && it.isHill() }
.filter { it.resource == null && it.neighbors.none { neighbor -> neighbor.isNaturalWonder() } .filter { it.resource == null
&& resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.lastTerrain.name) } } && it.neighbors.none { neighbor -> neighbor.isNaturalWonder() }
&& resourcesOfType.any { r -> r.generatesNaturallyOn(it) }
}
val numberOfResources = tileMap.values.count { it.isLand && !it.isImpassible() } * val numberOfResources = tileMap.values.count { it.isLand && !it.isImpassible() } *
tileMap.mapParameters.resourceRichness tileMap.mapParameters.resourceRichness
val locations = randomness.chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, mapRadius) val locations = randomness.chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, mapRadius)
@ -370,8 +370,7 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
val resourceToNumber = Counter<String>() val resourceToNumber = Counter<String>()
for (tile in locations) { for (tile in locations) {
val possibleResources = resourcesOfType val possibleResources = resourcesOfType.filter { it.generatesNaturallyOn(tile) }
.filter { it.terrainsCanBeFoundOn.contains(tile.lastTerrain.name) }
if (possibleResources.isEmpty()) continue if (possibleResources.isEmpty()) continue
val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name] }!! val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name] }!!
resourceToNumber.add(resourceWithLeastAssignments.name, 1) resourceToNumber.add(resourceWithLeastAssignments.name, 1)

View File

@ -30,14 +30,11 @@ object MapRegionResources {
ResourceType.Luxury -> MapRegions.ImpactType.Luxury ResourceType.Luxury -> MapRegions.ImpactType.Luxury
} }
val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull()) val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
val weightings = resourceOptions.map { val weightings = resourceOptions.associateWith {
val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull() val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
if (unique != null) val weight = if (unique != null) unique.params[0].toFloat() else 1f
unique.params[0].toFloat() weight
else
1f
} }
val testTerrains = (resourceOptions.size == 1) && !forcePlacement
val amountToPlace = (tileList.size / frequency) + 1 val amountToPlace = (tileList.size / frequency) + 1
var amountPlaced = 0 var amountPlaced = 0
val detailedPlaced = HashMap<TileResource, Int>() val detailedPlaced = HashMap<TileResource, Int>()
@ -45,17 +42,15 @@ object MapRegionResources {
val fallbackTiles = ArrayList<Tile>() val fallbackTiles = ArrayList<Tile>()
// First pass - avoid impacts entirely // First pass - avoid impacts entirely
for (tile in tileList) { for (tile in tileList) {
if (tile.resource != null || if (tile.resource != null) continue
(testTerrains && val possibleResourcesForTile = resourceOptions.filter { it.generatesNaturallyOn(tile) }
(tile.lastTerrain.name !in resourceOptions.first().terrainsCanBeFoundOn || if (possibleResourcesForTile.isEmpty()) continue
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)) { if (tileData[tile.position]!!.impacts.containsKey(impactType)) {
fallbackTiles.add(tile) // Taken but might be a viable fallback tile fallbackTiles.add(tile) // Taken but might be a viable fallback tile
} else { } else {
// Add a resource to the tile // Add a resource to the tile
val resourceToPlace = resourceOptions.randomWeighted(weightings) val resourceToPlace = possibleResourcesForTile.randomWeighted(possibleResourcesForTile.map { weightings[it] ?: 0f })
tile.setTileResource(resourceToPlace, majorDeposit) tile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1)) tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++ amountPlaced++
@ -70,7 +65,8 @@ object MapRegionResources {
// Sorry, we do need to re-sort the list for every pass since new impacts are made with every placement // 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]!! }!! val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
fallbackTiles.remove(bestTile) fallbackTiles.remove(bestTile)
val resourceToPlace = resourceOptions.randomWeighted(weightings) val possibleResourcesForTile = resourceOptions.filter { it.generatesNaturallyOn(bestTile) }
val resourceToPlace = possibleResourcesForTile.randomWeighted(possibleResourcesForTile.map { weightings[it] ?: 0f })
bestTile.setTileResource(resourceToPlace, majorDeposit) bestTile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1)) tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++ amountPlaced++
@ -95,17 +91,7 @@ object MapRegionResources {
} }
for (tile in tiles) { for (tile in tiles) {
val conditionalTerrain = StateForConditionals(attackedTile = tile) if (tile.resource == null && resource.generatesNaturallyOn(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 && if (ratioProgress >= 1f &&
!(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) { !(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
tile.setTileResource(resource, majorDeposit) tile.setTileResource(resource, majorDeposit)

View File

@ -792,7 +792,7 @@ class MapRegions (val ruleset: Ruleset) {
continue continue
val weightings = strategicResources.map { val weightings = strategicResources.map {
if (fallbackStrategic) { if (fallbackStrategic) {
if (tile.lastTerrain.name in it.terrainsCanBeFoundOn) 1f else 0f if (it.generatesNaturallyOn(tile)) 1f else 0f
} else { } else {
val uniques = it.getMatchingUniques(UniqueType.MinorDepositWeighting, conditionalTerrain).toList() val uniques = it.getMatchingUniques(UniqueType.MinorDepositWeighting, conditionalTerrain).toList()
uniques.sumOf { unique -> unique.params[0].toInt() }.toFloat() uniques.sumOf { unique -> unique.params[0].toInt() }.toFloat()
@ -822,10 +822,10 @@ class MapRegions (val ruleset: Ruleset) {
for (resource in strategicResources) { for (resource in strategicResources) {
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, ruleset)) val tilesToAddTo = if (!isWaterOnlyResource(resource, ruleset)) landList.asSequence()
MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, tileMap.values.asSequence().filter { it.isWater }.shuffled(), respectImpacts = true) else tileMap.values.asSequence().filter { it.isWater }.shuffled()
else
MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, landList.asSequence(), respectImpacts = true) MapRegionResources.tryAddingResourceToTiles(tileData, resource, extraNeeded, tilesToAddTo, respectImpacts = true)
} }
} }

View File

@ -162,9 +162,7 @@ object StartNormalizer {
if (plot.resource != null) continue if (plot.resource != null) continue
val bonusToPlace = val bonusToPlace = productionBonuses.filter { it.generatesNaturallyOn(plot) }.randomOrNull()
productionBonuses.filter { plot.lastTerrain.name in it.terrainsCanBeFoundOn }
.randomOrNull()
if (bonusToPlace != null) { if (bonusToPlace != null) {
plot.resource = bonusToPlace.name plot.resource = bonusToPlace.name
productionBonusesNeeded-- productionBonusesNeeded--
@ -280,7 +278,7 @@ object StartNormalizer {
val validBonuses = ruleset.tileResources.values.filter { val validBonuses = ruleset.tileResources.values.filter {
it.resourceType == ResourceType.Bonus && it.resourceType == ResourceType.Bonus &&
it.food >= 1 && it.food >= 1 &&
plot.lastTerrain.name in it.terrainsCanBeFoundOn it.generatesNaturallyOn(plot)
} }
val goodPlotForOasis = val goodPlotForOasis =
canPlaceOasis && plot.lastTerrain.name in oasisEquivalent!!.occursOn canPlaceOasis && plot.lastTerrain.name in oasisEquivalent!!.occursOn

View File

@ -380,7 +380,7 @@ open class Tile : IsPartOfGameInfoSerialization {
return civInfo.isAtWarWith(tileOwner) return civInfo.isAtWarWith(tileOwner)
} }
fun isRoughTerrain() = allTerrains.any{ it.isRough() } fun isRoughTerrain() = allTerrains.any { it.isRough() }
/** Checks whether any of the TERRAINS of this tile has a certain unique */ /** Checks whether any of the TERRAINS of this tile has a certain unique */
fun terrainHasUnique(uniqueType: UniqueType) = terrainUniqueMap.getUniques(uniqueType).any() fun terrainHasUnique(uniqueType: UniqueType) = terrainUniqueMap.getUniques(uniqueType).any()

View File

@ -2,6 +2,7 @@ package com.unciv.models.ruleset.tile
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetStatsObject import com.unciv.models.ruleset.RulesetStatsObject
@ -143,6 +144,10 @@ class Terrain : RulesetStatsObject() {
return textList return textList
} }
fun canBePlacedOn(tile: Tile){
}
fun setTransients() { fun setTransients() {
damagePerTurn = getMatchingUniques(UniqueType.DamagesContainingUnits).sumOf { it.params[0].toInt() } damagePerTurn = getMatchingUniques(UniqueType.DamagesContainingUnits).sumOf { it.params[0].toInt() }
} }

View File

@ -207,7 +207,7 @@ class TileImprovement : RulesetStatsObject() {
val cannotFilters = getMatchingUniques(UniqueType.CannotBuildOnTile).map { it.params[0] }.toSet() val cannotFilters = getMatchingUniques(UniqueType.CannotBuildOnTile).map { it.params[0] }.toSet()
val resourcesImprovedByThis = ruleset.tileResources.values.filter { it.isImprovedBy(name) } val resourcesImprovedByThis = ruleset.tileResources.values.filter { it.isImprovedBy(name) }
val expandedCanBeBuiltOn = sequence { val expandedTerrainsCanBeBuiltOn = sequence {
yieldAll(terrainsCanBeBuiltOn) yieldAll(terrainsCanBeBuiltOn)
yieldAll(terrainsCanBeBuiltOn.asSequence().mapNotNull { ruleset.terrains[it] }.flatMap { it.occursOn.asSequence() }) yieldAll(terrainsCanBeBuiltOn.asSequence().mapNotNull { ruleset.terrains[it] }.flatMap { it.occursOn.asSequence() })
if (hasUnique(UniqueType.CanOnlyImproveResource)) if (hasUnique(UniqueType.CanOnlyImproveResource))
@ -220,21 +220,21 @@ class TileImprovement : RulesetStatsObject() {
}.filter { it !in cannotFilters }.toMutableSet() }.filter { it !in cannotFilters }.toMutableSet()
val terrainsCanBeBuiltOnTypes = sequence { val terrainsCanBeBuiltOnTypes = sequence {
yieldAll(expandedCanBeBuiltOn.asSequence() yieldAll(expandedTerrainsCanBeBuiltOn.asSequence()
.mapNotNull { ruleset.terrains[it]?.type }) .mapNotNull { ruleset.terrains[it]?.type })
yieldAll(TerrainType.values().asSequence() yieldAll(TerrainType.values().asSequence()
.filter { it.name in expandedCanBeBuiltOn }) .filter { it.name in expandedTerrainsCanBeBuiltOn })
}.filter { it.name !in cannotFilters }.toMutableSet() }.filter { it.name !in cannotFilters }.toMutableSet()
if (canOnlyFilters.isNotEmpty() && canOnlyFilters.intersect(expandedCanBeBuiltOn).isEmpty()) { if (canOnlyFilters.isNotEmpty() && canOnlyFilters.intersect(expandedTerrainsCanBeBuiltOn).isEmpty()) {
expandedCanBeBuiltOn.clear() expandedTerrainsCanBeBuiltOn.clear()
if (terrainsCanBeBuiltOnTypes.none { it.name in canOnlyFilters }) if (terrainsCanBeBuiltOnTypes.none { it.name in canOnlyFilters })
terrainsCanBeBuiltOnTypes.clear() terrainsCanBeBuiltOnTypes.clear()
} }
fun matchesBuildImprovementsFilter(filter: String) = fun matchesBuildImprovementsFilter(filter: String) =
matchesFilter(filter) || matchesFilter(filter) ||
filter in expandedCanBeBuiltOn || filter in expandedTerrainsCanBeBuiltOn ||
terrainsCanBeBuiltOnTypes.any { it.name == filter } terrainsCanBeBuiltOnTypes.any { it.name == filter }
return ruleset.units.values.asSequence() return ruleset.units.values.asSequence()

View File

@ -50,9 +50,8 @@ class TileResource : RulesetStatsObject() {
if (terrainsCanBeFoundOn.isNotEmpty()) { if (terrainsCanBeFoundOn.isNotEmpty()) {
textList += FormattedLine() textList += FormattedLine()
if (terrainsCanBeFoundOn.size == 1) { if (terrainsCanBeFoundOn.size == 1) {
with (terrainsCanBeFoundOn[0]) { val terrainName = terrainsCanBeFoundOn[0]
textList += FormattedLine("{Can be found on} {$this}", link = "Terrain/$this") textList += FormattedLine("{Can be found on} {$terrainName}", link = "Terrain/$terrainName")
}
} else { } else {
textList += FormattedLine("{Can be found on}:") textList += FormattedLine("{Can be found on}:")
terrainsCanBeFoundOn.forEach { terrainsCanBeFoundOn.forEach {
@ -142,6 +141,21 @@ class TileResource : RulesetStatsObject() {
} }
} }
fun generatesNaturallyOn(tile:Tile): Boolean {
if (tile.lastTerrain.name !in terrainsCanBeFoundOn) return false
val stateForConditionals = StateForConditionals(tile = tile)
if (hasUnique(UniqueType.NoNaturalGeneration, stateForConditionals)) return false
if (tile.allTerrains.any { it.hasUnique(UniqueType.BlocksResources, stateForConditionals) }) return false
if (tile.temperature!=null && tile.humidity!=null) // Only works when in map generation
for (unique in getMatchingUniques(UniqueType.TileGenerationConditions, stateForConditionals)){
if (tile.temperature!! !in unique.params[0].toDouble() .. unique.params[1].toDouble()) return false
if (tile.humidity!! !in unique.params[2].toDouble() .. unique.params[3].toDouble()) return false
}
return true
}
fun isStockpiled() = hasUnique(UniqueType.Stockpiled) fun isStockpiled() = hasUnique(UniqueType.Stockpiled)
class DepositAmount { class DepositAmount {
@ -149,5 +163,4 @@ class TileResource : RulesetStatsObject() {
var default: Int = 2 var default: Int = 2
var abundant: Int = 3 var abundant: Int = 3
} }
} }

View File

@ -790,9 +790,6 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Global, Unit Applicable to: Global, Unit
??? example "+30% Strength when fighting City-State units and cities"
Applicable to: Global
??? example "[amount] additional attacks per turn" ??? example "[amount] additional attacks per turn"
Example: "[3] additional attacks per turn" Example: "[3] additional attacks per turn"
@ -2026,6 +2023,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Conditional Applicable to: Conditional
??? example "&lt;vs [combatantFilter]&gt;"
Example: "&lt;vs [City]&gt;"
Applicable to: Conditional
??? example "&lt;when fighting units from a Civilization with more Cities than you&gt;" ??? example "&lt;when fighting units from a Civilization with more Cities than you&gt;"
Applicable to: Conditional Applicable to: Conditional
@ -2066,23 +2068,23 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Conditional Applicable to: Conditional
??? example "&lt;with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles&gt;"
Example: "&lt;with [3] to [3] neighboring [Farm] [Farm] tiles&gt;"
Applicable to: Conditional
??? example "&lt;in [tileFilter] tiles&gt;" ??? example "&lt;in [tileFilter] tiles&gt;"
Example: "&lt;in [Farm] tiles&gt;" Example: "&lt;in [Farm] tiles&gt;"
Applicable to: Conditional Applicable to: Conditional
??? example "&lt;in [tileFilter] [tileFilter] tiles&gt;" ??? example "&lt;in tiles without [tileFilter]&gt;"
Example: "&lt;in [Farm] [Farm] tiles&gt;" Example: "&lt;in tiles without [Farm]&gt;"
Applicable to: Conditional Applicable to: Conditional
??? example "&lt;in tiles without [tileFilter]&gt;" ??? example "&lt;in tiles adjacent to [tileFilter]&gt;"
Example: "&lt;in tiles without [Farm]&gt;" Example: "&lt;in tiles adjacent to [Farm]&gt;"
Applicable to: Conditional
??? example "&lt;in tiles not adjacent to [tileFilter]&gt;"
Example: "&lt;in tiles not adjacent to [Farm]&gt;"
Applicable to: Conditional Applicable to: Conditional