Natural Wonders un-hardcoded (#5279)

* Natural Wonders un-hardcoded

* Natural Wonders un-hardcoded - oops testing change revert

* Natural Wonders un-hardcoded - tests OK

* Natural Wonders un-hardcoded - requests

* Natural Wonders un-hardcoded - hide from pedia

* Natural Wonders un-hardcoded - readable unique
This commit is contained in:
SomeTroglodyte
2021-09-23 10:21:08 +02:00
committed by GitHub
parent c7d5caf08c
commit a5a8ea1338
10 changed files with 327 additions and 423 deletions

View File

@ -84,10 +84,10 @@ object GameStarter {
addCivStats(gameInfo)
}
runAndMeasure("assignContinents?") {
if (tileMap.continentSizes.isEmpty()) // Probably saved map without continent data
if (tileMap.continentSizes.isEmpty()) // Probably saved map without continent data
runAndMeasure("assignContinents") {
mapGen.assignContinents(tileMap)
}
}
runAndMeasure("addCivStartingUnits") {
// and only now do we add units for everyone, because otherwise both the gameInfo.setTransients() and the placeUnit will both add the unit to the civ's unit list!

View File

@ -11,7 +11,7 @@ class BFS(
) {
/** Maximum number of tiles to search */
var maxSize = Int.MAX_VALUE
/** remaining tiles to check */
private val tilesToCheck = ArrayDeque<TileInfo>(37) // needs resize at distance 4
@ -23,13 +23,10 @@ class BFS(
tilesReached[startingPoint] = startingPoint
}
/** Process fully until there's nowhere left to check
* Optionally assigns a continent ID as it goes */
fun stepToEnd(continent: Int? = null) {
if (continent != null)
startingPoint.setContinent(continent)
/** Process fully until there's nowhere left to check */
fun stepToEnd() {
while (!hasEnded())
nextStep(continent)
nextStep()
}
/**
@ -49,15 +46,13 @@ class BFS(
*
* Will do nothing when [hasEnded] returns `true`
*/
fun nextStep(continent: Int? = null) {
fun nextStep() {
if (tilesReached.size >= maxSize) { tilesToCheck.clear(); return }
val current = tilesToCheck.removeFirstOrNull() ?: return
for (neighbor in current.neighbors) {
if (neighbor !in tilesReached && predicate(neighbor)) {
tilesReached[neighbor] = current
tilesToCheck.add(neighbor)
if (continent != null)
neighbor.setContinent(continent)
}
}
}

View File

@ -778,7 +778,13 @@ open class TileInfo {
}
}
// Should only be set once at map generation
/**
* Assign a continent ID to this tile.
*
* Should only be set once at map generation.
* @param continent Numeric ID >= 0
* @throws Exception when tile already has a continent ID
*/
fun setContinent(continent: Int) {
if (this.continent != -1)
throw Exception("Continent already assigned @ $position")

View File

@ -464,8 +464,10 @@ class MapGenerator(val ruleset: Ruleset) {
}
}
// Set a continent id for each tile, so we can quickly see which tiles are connected.
// Can also be called on saved maps
/** Set a continent id for each tile, so we can quickly see which tiles are connected.
* Can also be called on saved maps.
* @throws Exception when any land tile already has a continent ID
*/
fun assignContinents(tileMap: TileMap) {
var landTiles = tileMap.values
.filter { it.isLand && !it.isImpassible()}
@ -473,7 +475,10 @@ class MapGenerator(val ruleset: Ruleset) {
while (landTiles.any()) {
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
bfs.stepToEnd(currentContinent)
bfs.stepToEnd()
bfs.getReachedTiles().forEach {
it.setContinent(currentContinent)
}
val continent = bfs.getReachedTiles()
tileMap.continentSizes[currentContinent] = continent.size
if (continent.size > 20) {

View File

@ -6,11 +6,17 @@ import com.unciv.logic.map.TileMap
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import kotlin.math.abs
import kotlin.math.round
class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGenerationRandomness) {
private val allTerrainFeatures = ruleset.terrains.values
.filter { it.type == TerrainType.TerrainFeature }
.map { it.name }.toSet()
/*
https://gaming.stackexchange.com/questions/95095/do-natural-wonders-spawn-more-closely-to-city-states/96479
https://www.reddit.com/r/civ/comments/1jae5j/information_on_the_occurrence_of_natural_wonders/
@ -22,18 +28,19 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration
// number of Natural Wonders scales linearly with mapRadius as #wonders = mapRadius * 0.13133208 - 0.56128831
val numberToSpawn = round(mapRadius * 0.13133208f - 0.56128831f).toInt()
val toBeSpawned = ArrayList<Terrain>()
val spawned = mutableListOf<Terrain>()
val allNaturalWonders = ruleset.terrains.values
.filter { it.type == TerrainType.NaturalWonder }.toMutableList()
while (allNaturalWonders.isNotEmpty() && toBeSpawned.size < numberToSpawn) {
val totalWeight = allNaturalWonders.map { it.weight }.sum().toFloat()
while (allNaturalWonders.isNotEmpty() && spawned.size < numberToSpawn) {
val totalWeight = allNaturalWonders.sumOf { it.weight }.toFloat()
val random = randomness.RNG.nextDouble()
var sum = 0f
for (wonder in allNaturalWonders) {
sum += wonder.weight / totalWeight
if (random <= sum) {
toBeSpawned.add(wonder)
if (spawnSpecificWonder(tileMap, wonder))
spawned.add(wonder)
allNaturalWonders.remove(wonder)
break
}
@ -41,391 +48,183 @@ class NaturalWonderGenerator(val ruleset: Ruleset, val randomness: MapGeneration
}
if (MapGenerator.consoleOutput)
println("Natural Wonders for this game: $toBeSpawned")
for (wonder in toBeSpawned) {
when (wonder.name) {
Constants.barringerCrater -> spawnBarringerCrater(tileMap)
Constants.mountFuji -> spawnMountFuji(tileMap)
Constants.grandMesa -> spawnGrandMesa(tileMap)
Constants.greatBarrierReef -> spawnGreatBarrierReef(tileMap)
Constants.krakatoa -> spawnKrakatoa(tileMap)
Constants.rockOfGibraltar -> spawnRockOfGibraltar(tileMap)
Constants.oldFaithful -> spawnOldFaithful(tileMap)
Constants.cerroDePotosi -> spawnCerroDePotosi(tileMap)
Constants.elDorado -> spawnElDorado(tileMap)
Constants.fountainOfYouth -> spawnFountainOfYouth(tileMap)
Constants.mountKailash -> spawnMountKailash(tileMap)
Constants.mountSinai -> spawnMountSinai(tileMap)
Constants.sriPada -> spawnSriPada(tileMap)
Constants.uluru -> spawnUluru(tileMap)
/*
Constants.kingSolomonsMines -> spawnSolomonMines(tileMap)
Constants.lakeVictoria -> spawnLakeVictoria(tileMap)
Constants.mountKilimanjaro -> spawnMountKilimanjaro(tileMap)
*/
}
}
println("Natural Wonders for this game: $spawned")
}
private fun trySpawnOnSuitableLocation(suitableLocations: List<TileInfo>, wonder: Terrain): TileInfo? {
if (suitableLocations.isNotEmpty()) {
private fun Unique.getIntParam(index: Int) = params[index].toInt()
private fun spawnSpecificWonder(tileMap: TileMap, wonder: Terrain): Boolean {
val suitableLocations = tileMap.values.filter { tile->
tile.resource == null &&
wonder.occursOn.contains(tile.getLastTerrain().name) &&
wonder.uniqueObjects.all { unique ->
when (unique.type) {
UniqueType.NaturalWonderNeighborCount -> {
val count = tile.neighbors.count {
it.matchesWonderFilter(unique.params[1])
}
count == unique.getIntParam(0)
}
UniqueType.NaturalWonderNeighborsRange -> {
val count = tile.neighbors.count {
it.matchesWonderFilter(unique.params[2])
}
count in unique.getIntParam(0)..unique.getIntParam(1)
}
UniqueType.NaturalWonderLandmass -> {
val sortedContinents = tileMap.continentSizes.asSequence()
.sortedByDescending { it.value }
.map { it.key }
.toList()
tile.getContinent() !in sortedContinents.take(unique.getIntParam(0))
}
UniqueType.NaturalWonderLatitude -> {
val lower = tileMap.maxLatitude * unique.getIntParam(0) * 0.01f
val upper = tileMap.maxLatitude * unique.getIntParam(1) * 0.01f
abs(tile.latitude) in lower..upper
}
else -> true
}
}
}
return trySpawnOnSuitableLocation(suitableLocations, wonder)
}
private fun trySpawnOnSuitableLocation(suitableLocations: List<TileInfo>, wonder: Terrain): Boolean {
val minGroupSize: Int
val maxGroupSize: Int
val groupUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderGroups).firstOrNull()
if (groupUnique == null) {
minGroupSize = 1
maxGroupSize = 1
} else {
minGroupSize = groupUnique.getIntParam(0)
maxGroupSize = groupUnique.getIntParam(1)
}
val targetGroupSize = if (minGroupSize == maxGroupSize) maxGroupSize
else (minGroupSize..maxGroupSize).random(randomness.RNG)
var convertNeighborsExcept: String? = null
var convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighbors).firstOrNull()
var convertNeighborsTo = convertUnique?.params?.get(0)
if (convertNeighborsTo == null) {
convertUnique = wonder.getMatchingUniques(UniqueType.NaturalWonderConvertNeighborsExcept).firstOrNull()
convertNeighborsExcept = convertUnique?.params?.get(0)
convertNeighborsTo = convertUnique?.params?.get(1)
}
if (suitableLocations.size >= minGroupSize) {
val location = suitableLocations.random(randomness.RNG)
clearTile(location)
location.naturalWonder = wonder.name
location.baseTerrain = wonder.turnsInto!!
return location
val list = mutableListOf(location)
while (list.size < targetGroupSize) {
val allNeighbors = list.flatMap { it.neighbors }.minus(list).toHashSet()
val candidates = suitableLocations.filter { it in allNeighbors }
if (candidates.isEmpty()) break
list.add(candidates.random(randomness.RNG))
}
if (list.size >= minGroupSize) {
list.forEach {
clearTile(it)
it.naturalWonder = wonder.name
it.baseTerrain = wonder.turnsInto!!
}
if (convertNeighborsTo != null) {
for (tile in location.neighbors) {
if (tile.baseTerrain == convertNeighborsTo) continue
if (tile.baseTerrain == convertNeighborsExcept) continue
if (convertNeighborsTo == Constants.coast)
for (neighbor in tile.neighbors) {
// This is so we don't have this tile turn into Coast, and then it's touching a Lake tile.
// We just turn the lake tiles into this kind of tile.
if (neighbor.baseTerrain == Constants.lakes) {
neighbor.baseTerrain = tile.baseTerrain
neighbor.setTerrainTransients()
}
}
tile.baseTerrain = convertNeighborsTo
clearTile(tile)
}
}
if (MapGenerator.consoleOutput)
println("Natural Wonder ${wonder.name} @${location.position}")
return true
}
}
if (MapGenerator.consoleOutput)
println("No suitable location for ${wonder.name}")
return null
return false
}
/*
Barringer Crater: Must be in tundra or desert; cannot be adjacent to grassland; can be adjacent to a maximum
of 2 mountains and a maximum of 4 hills and mountains; avoids oceans; becomes mountain
*/
private fun spawnBarringerCrater(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.barringerCrater]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain || neighbor.isHill() } <= 4
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Mt. Fuji: Must be in grass or plains; avoids oceans and the biggest landmass; cannot be adjacent to tundra,
desert, marsh, or mountains;can be adjacent to a maximum of 2 hills; becomes mountain
// ToDo: avoids the biggest landmass
*/
private fun spawnMountFuji(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.mountFuji]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.tundra }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.desert }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain }
&& it.neighbors.none { neighbor -> neighbor.getLastTerrain().name == Constants.marsh }
&& it.neighbors.count { neighbor -> neighbor.isHill() } <= 2
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Grand Mesa: Must be in plains, desert, or tundra, and must be adjacent to at least 2 hills;
cannot be adjacent to grass; can be adjacent to a maximum of 2 mountains; avoids oceans; becomes mountain
*/
private fun spawnGrandMesa(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.grandMesa]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.count { neighbor -> neighbor.isHill() } >= 2
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Great Barrier Reef: Specifics currently unknown;
Assumption: at least 1 neighbour coast; no tundra; at least 1 neighbour coast; becomes coast
*/
private fun spawnGreatBarrierReef(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.greatBarrierReef]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& abs(it.latitude) > tileMap.maxLatitude * 0.1
&& abs(it.latitude) < tileMap.maxLatitude * 0.7
&& it.neighbors.any { it.baseTerrain == Constants.coast }
&& it.neighbors.all { neighbor -> neighbor.isWater }
&& it.neighbors.any { neighbor ->
neighbor.resource == null && neighbor.improvement == null
&& wonder.occursOn.contains(neighbor.getLastTerrain().name)
&& neighbor.neighbors.all { it.isWater }
}
}
val location = trySpawnOnSuitableLocation(suitableLocations, wonder)
if (location != null) {
val possibleLocations = location.neighbors
.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.all { it.isWater }
}.toList()
trySpawnOnSuitableLocation(possibleLocations, wonder)
}
}
/*
Krakatoa: Must spawn in the ocean next to at least 1 shallow water tile; cannot be adjacent
to ice; changes tiles around it to shallow water; mountain
*/
private fun spawnKrakatoa(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.krakatoa]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.any { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.none { neighbor -> neighbor.getLastTerrain().name == Constants.ice }
}
val location = trySpawnOnSuitableLocation(suitableLocations, wonder)
if (location != null) {
for (tile in location.neighbors) {
if (tile.baseTerrain == Constants.coast) continue
tile.baseTerrain = Constants.coast
clearTile(tile)
}
}
}
/*
Rock of Gibraltar: Specifics currently unknown
Assumption: spawn on grassland, at least 1 coast and 1 mountain adjacent;
turn neighbours into coast)
*/
private fun spawnRockOfGibraltar(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.rockOfGibraltar]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.any { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } == 1
}
val location = trySpawnOnSuitableLocation(suitableLocations, wonder)
if (location != null) {
for (tile in location.neighbors) {
if (tile.baseTerrain == Constants.coast) continue
if (tile.baseTerrain == Constants.mountain) continue
for (neighbor in tile.neighbors)
// This is so we don't have this tile turn into Coast, and then it's touching a Lake tile.
// We just turn the lake tiles into this kind of tile.
if (neighbor.baseTerrain == Constants.lakes) {
neighbor.baseTerrain = tile.baseTerrain
neighbor.setTerrainTransients()
}
tile.baseTerrain = Constants.coast
clearTile(tile)
}
}
}
/*
Old Faithful: Must be adjacent to at least 3 hills and mountains; cannot be adjacent to
more than 4 mountains, and cannot be adjacent to more than 3 desert or 3 tundra tiles;
avoids oceans; becomes mountain
*/
private fun spawnOldFaithful(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.oldFaithful]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 4
&& it.neighbors.count { neighbor ->
neighbor.getBaseTerrain().name == Constants.mountain ||
neighbor.isHill()
} >= 3
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.desert } <= 3
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.tundra } <= 3
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Cerro de Potosi: Must be adjacent to at least 1 hill; avoids oceans; becomes mountain
*/
private fun spawnCerroDePotosi(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.cerroDePotosi]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.any { neighbor -> neighbor.isHill() }
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
El Dorado: Must be next to at least 1 jungle tile; avoids oceans; becomes flatland plains
*/
private fun spawnElDorado(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.elDorado]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.any { neighbor -> neighbor.getLastTerrain().name == Constants.jungle }
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Fountain of Youth: Avoids oceans; becomes flatland plains
*/
private fun spawnFountainOfYouth(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.fountainOfYouth]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
// G&K Natural Wonders
/*
Mount Kailash: Must be in plains or grassland, and must be adjacent to at least 4 hills and/or mountains;
cannot be adjacent to marshes; can be adjacent to a maximum of 1 desert tile; avoids oceans; becomes mountain
*/
private fun spawnMountKailash(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.mountKailash]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.marsh }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain || neighbor.isHill() } >= 4
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.desert} <= 1
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Mount Sinai: Must be in plains or desert, and must be adjacent to a minimum of 3 desert tiles;
cannot be adjacent to tundra, marshes, or grassland; avoids oceans; becomes mountain
*/
private fun spawnMountSinai(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.mountSinai]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.marsh }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.tundra }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.desert } >= 3
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Sri Pada: Must be in a grass or plains; cannot be adjacent to desert, tundra, or marshes;
avoids the biggest landmass ; becomes mountain
// ToDo: avoids the biggest landmass
*/
private fun spawnSriPada(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.sriPada]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.desert }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.tundra }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.marsh }
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Uluru: Must be in plains or desert, and must be adjacent to a minimum of 3 plains tiles;
cannot be adjacent to grassland, tundra, or marshes; avoids oceans; becomes mountain
*/
private fun spawnUluru(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.uluru]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.marsh }
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.tundra }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.plains } >= 3
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
//BNW Natural Wonders
/*
/*
King Solomon's Mines: Cannot be adjacent to more than 2 mountains; avoids oceans; becomes flatland plains
*/
private fun spawnSolomonMines(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.kingSolomonsMines]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Lake Victoria: Avoids oceans; becomes flatland plains
*/
private fun spawnLakeVictoria(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.lakeVictoria]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
/*
Mount Kilimanjaro: Must be in plains or grassland, and must be adjacent to at least 2 hills;
cannot be adjacent to more than 2 mountains; avoids oceans; becomes mountain
*/
private fun spawnMountKilimanjaro(tileMap: TileMap) {
val wonder = ruleset.terrains[Constants.mountKilimanjaro]!!
val suitableLocations = tileMap.values.filter {
it.resource == null
&& wonder.occursOn.contains(it.getLastTerrain().name)
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.coast }
&& it.neighbors.count { neighbor -> neighbor.isHill() } >= 2
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
}
trySpawnOnSuitableLocation(suitableLocations, wonder)
}
*/
private fun clearTile(tile: TileInfo){
tile.terrainFeatures.clear()
tile.resource = null
tile.improvement = null
tile.setTerrainTransients()
}
private fun TileInfo.matchesWonderFilter(filter: String) = when (filter) {
"Elevated" -> baseTerrain == Constants.mountain || isHill()
"Water" -> isWater
"Hill" -> isHill()
in allTerrainFeatures -> getLastTerrain().name == filter
else -> baseTerrain == filter
}
/*
Barringer Crater: Must be in tundra or desert; cannot be adjacent to grassland; can be adjacent to a maximum
of 2 mountains and a maximum of 4 hills and mountains; avoids oceans; becomes mountain
Grand Mesa: Must be in plains, desert, or tundra, and must be adjacent to at least 2 hills;
cannot be adjacent to grass; can be adjacent to a maximum of 2 mountains; avoids oceans; becomes mountain
Mt. Fuji: Must be in grass or plains; avoids oceans and the biggest landmass; cannot be adjacent to tundra,
desert, marsh, or mountains;can be adjacent to a maximum of 2 hills; becomes mountain
Great Barrier Reef: Specifics currently unknown;
Assumption: at least 1 neighbour coast; no tundra; at least 1 neighbour coast; becomes coast
Krakatoa: Must spawn in the ocean next to at least 1 shallow water tile; cannot be adjacent
to ice; changes tiles around it to shallow water; mountain
Rock of Gibraltar: Specifics currently unknown
Assumption: spawn on grassland, at least 1 coast and 1 mountain adjacent;
turn neighbours into coast)
Old Faithful: Must be adjacent to at least 3 hills and mountains; cannot be adjacent to
more than 4 mountains, and cannot be adjacent to more than 3 desert or 3 tundra tiles;
avoids oceans; becomes mountain
Cerro de Potosi: Must be adjacent to at least 1 hill; avoids oceans; becomes mountain
El Dorado: Must be next to at least 1 jungle tile; avoids oceans; becomes flatland plains
Fountain of Youth: Avoids oceans; becomes flatland plains
// G&K Natural Wonders
Mount Kailash: Must be in plains or grassland, and must be adjacent to at least 4 hills and/or mountains;
cannot be adjacent to marshes; can be adjacent to a maximum of 1 desert tile; avoids oceans; becomes mountain
Mount Sinai: Must be in plains or desert, and must be adjacent to a minimum of 3 desert tiles;
cannot be adjacent to tundra, marshes, or grassland; avoids oceans; becomes mountain
Sri Pada: Must be in a grass or plains; cannot be adjacent to desert, tundra, or marshes;
avoids the biggest landmass ; becomes mountain
Uluru: Must be in plains or desert, and must be adjacent to a minimum of 3 plains tiles;
cannot be adjacent to grassland, tundra, or marshes; avoids oceans; becomes mountain
//BNW Natural Wonders
King Solomon's Mines: Cannot be adjacent to more than 2 mountains; avoids oceans; becomes flatland plains
Lake Victoria: Avoids oceans; becomes flatland plains
Mount Kilimanjaro: Must be in plains or grassland, and must be adjacent to at least 2 hills;
cannot be adjacent to more than 2 mountains; avoids oceans; becomes mountain
*/
}

View File

@ -1,8 +1,8 @@
package com.unciv.models.ruleset.tile
enum class TerrainType {
Land,
Water,
TerrainFeature,
NaturalWonder
enum class TerrainType(val isBaseTerrain: Boolean) {
Land(true),
Water(true),
TerrainFeature(false),
NaturalWonder(false)
}

View File

@ -26,7 +26,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
/** We can't save compliance errors in the unique, since it's ruleset-dependant */
fun matches(uniqueType: UniqueType, ruleset: Ruleset) = isOfType(uniqueType)
&& uniqueType.getComplianceErrors(this, ruleset).isEmpty()
// This function will get LARGE, as it will basically check for all conditionals if they apply
// This will require a lot of parameters to be passed (attacking unit, tile, defending unit, civInfo, cityInfo, ...)
// I'm open for better ideas, but this was the first thing that I could think of that would
@ -37,7 +37,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
}
return true
}
private fun conditionalApplies(
condition: Unique,
civInfo: CivilizationInfo? = null,
@ -53,6 +53,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
else -> false
}
}
override fun toString() = if (type == null) "\"$text\"" else "$type (\"$text\")"
}

View File

@ -1,6 +1,8 @@
package com.unciv.models.ruleset.unique
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TerrainType
// parameterName values should be compliant with autogenerated values in TranslationFileWriter.generateStringsFromJSONs
// Eventually we'll merge the translation generation to take this as the source of that
@ -13,7 +15,7 @@ enum class UniqueParameterType(val parameterName:String) {
}
},
MapUnitFilter("mapUnitFilter"){
val knownValues = setOf("Wounded", "Barbarians", "City-State", "Embarked", "Non-City")
private val knownValues = setOf("Wounded", "Barbarians", "City-State", "Embarked", "Non-City")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null
@ -23,7 +25,7 @@ enum class UniqueParameterType(val parameterName:String) {
BaseUnitFilter("baseUnitFilter"){
// As you can see there is a difference between these and what's in unitTypeStrings (for translation) -
// the goal is to unify, but for now this is the "real" list
val knownValues = setOf("All", "Melee", "Ranged", "Civilian", "Military", "Land", "Water", "Air",
private val knownValues = setOf("All", "Melee", "Ranged", "Civilian", "Military", "Land", "Water", "Air",
"non-air", "Nuclear Weapon", "Great Person", "Religious")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
@ -62,6 +64,33 @@ enum class UniqueParameterType(val parameterName:String) {
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
},
TerrainFilter("terrainFilter") {
private val knownValues = setOf("All",
"Coastal", "River", "Open terrain", "Rough terrain", "Water resource",
"Foreign Land", "Foreign", "Friendly Land", "Friendly", "Enemy Land", "Enemy")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null
if (ruleset.terrains.containsKey(parameterText)) return null
if (TerrainType.values().any { parameterText == it.name }) return null
if (ruleset.tileResources.containsKey(parameterText)) return null
if (ResourceType.values().any { parameterText == it.name + " resource" }) return null
return UniqueType.UniqueComplianceErrorSeverity.WarningOnly
}
},
/** Used by NaturalWonderGenerator, only tests base terrain or a feature */
SimpleTerrain("simpleTerrain") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText == "Elevated") return null
if (ruleset.terrains.values.any {
it.name == parameterText &&
(it.type.isBaseTerrain || it.type == TerrainType.TerrainFeature)
})
return null
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
},
Unknown("param") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
@ -115,4 +144,4 @@ class UniqueComplianceError(
val parameterName: String,
val acceptableParameterTypes: List<UniqueParameterType>,
val errorSeverity: UniqueType.UniqueComplianceErrorSeverity
)
)

View File

@ -4,51 +4,51 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText
/** Buildings, units, nations, policies, religions, techs etc.
* Basically anything caught by CivInfo.getMatchingUniques. */
enum class UniqueTarget {
/** Buildings, units, nations, policies, religions, techs etc.
* Basically anything caught by CivInfo.getMatchingUniques. */
Global,
// Civilization-specific
Nation,
Era,
Tech,
Policy,
Belief,
// City-specific
Building,
Wonder,
// Unit-specific
Unit,
UnitType,
Promotion,
// Tile-specific
Terrain,
Improvement,
Resource,
Ruins,
// Other
CityState,
ModOptions,
ModOptions,
Conditional,
}
enum class UniqueType(val text:String, vararg target: UniqueTarget) {
Stats("[stats]", UniqueTarget.Global),
StatsPerCity("[stats] [cityFilter]", UniqueTarget.Global),
StatPercentBonus("[amount]% [Stat]", UniqueTarget.Global),
ConsumesResources("Consumes [amount] [resource]",
UniqueTarget.Improvement, UniqueTarget.Building, UniqueTarget.Unit), // No conditional support as of yet
ProvidesResources("Provides [amount] [resource]",
UniqueTarget.Improvement, UniqueTarget.Building),
FreeUnits("[amount] units cost no maintenance", UniqueTarget.Global),
UnitMaintenanceDiscount("[amount]% maintenance costs for [mapUnitFilter] units", UniqueTarget.Global),
@ -70,10 +70,18 @@ enum class UniqueType(val text:String, vararg target: UniqueTarget) {
CityStateHappiness("Provides [amount] Happiness", UniqueTarget.CityState),
CityStateMilitaryUnits("Provides military units every ≈[amount] turns", UniqueTarget.CityState), // No conditional support as of yet
CityStateUniqueLuxury("Provides a unique luxury", UniqueTarget.CityState), // No conditional support as of yet
NaturalWonderNeighborCount("Must be adjacent to [amount] [terrainFilter] tiles", UniqueTarget.Terrain),
NaturalWonderNeighborsRange("Must be adjacent to [amount] to [amount] [terrainFilter] tiles", UniqueTarget.Terrain),
NaturalWonderLandmass("Must not be on [amount] largest landmasses", UniqueTarget.Terrain),
NaturalWonderLatitude("Occurs on latitudes from [amount] to [amount] percent of distance equator to pole", UniqueTarget.Terrain),
NaturalWonderGroups("Occurs in groups of [amount] to [amount] tiles", UniqueTarget.Terrain),
NaturalWonderConvertNeighbors("Neighboring tiles will convert to [baseTerrain]", UniqueTarget.Terrain),
NaturalWonderConvertNeighborsExcept("Neighboring tiles except [terrainFilter] will convert to [baseTerrain]", UniqueTarget.Terrain),
///// CONDITIONALS
ConditionalWar("when at war", UniqueTarget.Conditional),
ConditionalNotWar("when not at war", UniqueTarget.Conditional),
ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional),
@ -128,4 +136,4 @@ enum class UniqueType(val text:String, vararg target: UniqueTarget) {
}
return errorList
}
}
}