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.isActive
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.pow
import kotlin.math.roundToInt
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
val suitableTiles = candidateTiles
.filterNot { it.baseTerrain == Constants.snow && it.isHill() }
.filter { it.resource == null
&& resource.terrainsCanBeFoundOn.contains(it.lastTerrain.name) }
.filter { it.resource == null && resource.generatesNaturallyOn(it) }
val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, mapRadius)
@ -361,8 +359,10 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
val suitableTiles = tileMap.values
.filterNot { it.baseTerrain == Constants.snow && it.isHill() }
.filter { it.resource == null && it.neighbors.none { neighbor -> neighbor.isNaturalWonder() }
&& resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.lastTerrain.name) } }
.filter { it.resource == null
&& it.neighbors.none { neighbor -> neighbor.isNaturalWonder() }
&& resourcesOfType.any { r -> r.generatesNaturallyOn(it) }
}
val numberOfResources = tileMap.values.count { it.isLand && !it.isImpassible() } *
tileMap.mapParameters.resourceRichness
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>()
for (tile in locations) {
val possibleResources = resourcesOfType
.filter { it.terrainsCanBeFoundOn.contains(tile.lastTerrain.name) }
val possibleResources = resourcesOfType.filter { it.generatesNaturallyOn(tile) }
if (possibleResources.isEmpty()) continue
val resourceWithLeastAssignments = possibleResources.minByOrNull { resourceToNumber[it.name] }!!
resourceToNumber.add(resourceWithLeastAssignments.name, 1)

View File

@ -30,14 +30,11 @@ object MapRegionResources {
ResourceType.Luxury -> MapRegions.ImpactType.Luxury
}
val conditionalTerrain = StateForConditionals(attackedTile = tileList.firstOrNull())
val weightings = resourceOptions.map {
val weightings = resourceOptions.associateWith {
val unique = it.getMatchingUniques(UniqueType.ResourceWeighting, conditionalTerrain).firstOrNull()
if (unique != null)
unique.params[0].toFloat()
else
1f
val weight = if (unique != null) unique.params[0].toFloat() else 1f
weight
}
val testTerrains = (resourceOptions.size == 1) && !forcePlacement
val amountToPlace = (tileList.size / frequency) + 1
var amountPlaced = 0
val detailedPlaced = HashMap<TileResource, Int>()
@ -45,17 +42,15 @@ object MapRegionResources {
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 (tile.resource != null) continue
val possibleResourcesForTile = resourceOptions.filter { it.generatesNaturallyOn(tile) }
if (possibleResourcesForTile.isEmpty()) continue
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)
val resourceToPlace = possibleResourcesForTile.randomWeighted(possibleResourcesForTile.map { weightings[it] ?: 0f })
tile.setTileResource(resourceToPlace, majorDeposit)
tileData.placeImpact(impactType, tile, baseImpact + Random.nextInt(randomImpact + 1))
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
val bestTile = fallbackTiles.minByOrNull { tileData[it.position]!!.impacts[impactType]!! }!!
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)
tileData.placeImpact(impactType, bestTile, baseImpact + Random.nextInt(randomImpact + 1))
amountPlaced++
@ -95,17 +91,7 @@ object MapRegionResources {
}
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 (tile.resource == null && resource.generatesNaturallyOn(tile)) {
if (ratioProgress >= 1f &&
!(respectImpacts && tileData[tile.position]!!.impacts.containsKey(impactType))) {
tile.setTileResource(resource, majorDeposit)

View File

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

View File

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

View File

@ -380,7 +380,7 @@ open class Tile : IsPartOfGameInfoSerialization {
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 */
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.unciv.Constants
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetStatsObject
@ -143,6 +144,10 @@ class Terrain : RulesetStatsObject() {
return textList
}
fun canBePlacedOn(tile: Tile){
}
fun setTransients() {
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 resourcesImprovedByThis = ruleset.tileResources.values.filter { it.isImprovedBy(name) }
val expandedCanBeBuiltOn = sequence {
val expandedTerrainsCanBeBuiltOn = sequence {
yieldAll(terrainsCanBeBuiltOn)
yieldAll(terrainsCanBeBuiltOn.asSequence().mapNotNull { ruleset.terrains[it] }.flatMap { it.occursOn.asSequence() })
if (hasUnique(UniqueType.CanOnlyImproveResource))
@ -220,21 +220,21 @@ class TileImprovement : RulesetStatsObject() {
}.filter { it !in cannotFilters }.toMutableSet()
val terrainsCanBeBuiltOnTypes = sequence {
yieldAll(expandedCanBeBuiltOn.asSequence()
yieldAll(expandedTerrainsCanBeBuiltOn.asSequence()
.mapNotNull { ruleset.terrains[it]?.type })
yieldAll(TerrainType.values().asSequence()
.filter { it.name in expandedCanBeBuiltOn })
.filter { it.name in expandedTerrainsCanBeBuiltOn })
}.filter { it.name !in cannotFilters }.toMutableSet()
if (canOnlyFilters.isNotEmpty() && canOnlyFilters.intersect(expandedCanBeBuiltOn).isEmpty()) {
expandedCanBeBuiltOn.clear()
if (canOnlyFilters.isNotEmpty() && canOnlyFilters.intersect(expandedTerrainsCanBeBuiltOn).isEmpty()) {
expandedTerrainsCanBeBuiltOn.clear()
if (terrainsCanBeBuiltOnTypes.none { it.name in canOnlyFilters })
terrainsCanBeBuiltOnTypes.clear()
}
fun matchesBuildImprovementsFilter(filter: String) =
matchesFilter(filter) ||
filter in expandedCanBeBuiltOn ||
filter in expandedTerrainsCanBeBuiltOn ||
terrainsCanBeBuiltOnTypes.any { it.name == filter }
return ruleset.units.values.asSequence()

View File

@ -50,9 +50,8 @@ class TileResource : RulesetStatsObject() {
if (terrainsCanBeFoundOn.isNotEmpty()) {
textList += FormattedLine()
if (terrainsCanBeFoundOn.size == 1) {
with (terrainsCanBeFoundOn[0]) {
textList += FormattedLine("{Can be found on} {$this}", link = "Terrain/$this")
}
val terrainName = terrainsCanBeFoundOn[0]
textList += FormattedLine("{Can be found on} {$terrainName}", link = "Terrain/$terrainName")
} else {
textList += FormattedLine("{Can be found on}:")
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)
class DepositAmount {
@ -149,5 +163,4 @@ class TileResource : RulesetStatsObject() {
var default: Int = 2
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
??? example "+30% Strength when fighting City-State units and cities"
Applicable to: Global
??? example "[amount] 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
??? 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;"
Applicable to: Conditional
@ -2066,23 +2068,23 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
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 [Farm] tiles&gt;"
Applicable to: Conditional
??? example "&lt;in [tileFilter] [tileFilter] tiles&gt;"
Example: "&lt;in [Farm] [Farm] tiles&gt;"
??? example "&lt;in tiles without [tileFilter]&gt;"
Example: "&lt;in tiles without [Farm]&gt;"
Applicable to: Conditional
??? example "&lt;in tiles without [tileFilter]&gt;"
Example: "&lt;in tiles without [Farm]&gt;"
??? example "&lt;in tiles adjacent to [tileFilter]&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