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

@ -204,6 +204,10 @@
"gold": 1,
"science": 2,
"occursOn": ["Ocean"],
"uniques": ["Must be adjacent to [1] to [6] [Coast] tiles",
"Must be adjacent to [6] [Water] tiles",
"Occurs on latitudes from [10] to [70] percent of distance equator to pole",
"Occurs in groups of [2] to [2] tiles"],
"turnsInto": "Coast",
"impassable": true,
"unbuildable": true,
@ -215,6 +219,11 @@
"science": 2,
"happiness": 3,
"occursOn": ["Grassland","Plains","Tundra","Mountain"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] to [4] [Mountain] tiles",
"Must be adjacent to [3] to [6] [Elevated] tiles",
"Must be adjacent to [0] to [3] [Desert] tiles",
"Must be adjacent to [0] to [3] [Tundra] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -228,7 +237,9 @@
"turnsInto": "Plains",
"impassable": true,
"unbuildable": true,
"uniques": ["Grants 500 Gold to the first civilization to discover it"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [1] to [6] [Jungle] tiles",
"Grants 500 Gold to the first civilization to discover it"],
"weight": 2
},
{ // This will count as "Fresh water" in civ 6
@ -239,7 +250,8 @@
"turnsInto": "Plains",
"impassable": true,
"unbuildable": true,
"uniques": ["Grants Rejuvenation (all healing effects doubled) to adjacent military land units for the rest of the game"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Grants Rejuvenation (all healing effects doubled) to adjacent military land units for the rest of the game"],
"weight": 1
},
{
@ -248,6 +260,10 @@
"production": 2,
"gold": 3,
"occursOn": ["Plains","Desert","Tundra"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Grassland] tiles",
"Must be adjacent to [2] to [6] [Hill] tiles",
"Must be adjacent to [0] to [2] [Mountain] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -260,6 +276,13 @@
"culture": 3,
"faith": 3,
"occursOn": ["Grassland","Plains"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Tundra] tiles",
"Must be adjacent to [0] [Desert] tiles",
"Must be adjacent to [0] [Mountain] tiles",
"Must be adjacent to [0] [Marsh] tiles",
"Must be adjacent to [0] to [2] [Hill] tiles",
"Must not be on [1] largest landmasses"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -270,6 +293,9 @@
"type": "NaturalWonder",
"science": 5,
"occursOn": ["Ocean"],
"uniques": ["Must be adjacent to [1] to [6] [Coast] tiles",
"Must be adjacent to [0] [Ice] tiles",
"Neighboring tiles will convert to [Coast]"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -281,6 +307,9 @@
"food": 2,
"gold": 5,
"occursOn": ["Grassland"],
"uniques": ["Must be adjacent to [1] to [5] [Coast] tiles",
"Must be adjacent to [1] [Mountain] tiles",
"Neighboring tiles except [Mountain] will convert to [Coast]"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -291,6 +320,8 @@
"type": "NaturalWonder",
"gold": 10,
"occursOn": ["Plains","Mountain"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [1] to [6] [Hill] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -302,17 +333,26 @@
"gold": 2,
"science": 3,
"occursOn": ["Desert","Tundra"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Grassland] tiles",
"Must be adjacent to [0] to [2] [Mountain] tiles",
"Must be adjacent to [0] to [4] [Elevated] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
"weight": 10
},
// G&K Wonders
{
"name": "Mount Kailash",
"type": "NaturalWonder",
"faith": 6,
"happiness": 2,
"occursOn": ["Plains","Grassland"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Marsh] tiles",
"Must be adjacent to [0] to [1] [Desert] tiles",
"Must be adjacent to [4] to [6] [Elevated] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -323,6 +363,11 @@
"type": "NaturalWonder",
"faith": 8,
"occursOn": ["Desert","Plains"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Grassland] tiles",
"Must be adjacent to [0] [Tundra] tiles",
"Must be adjacent to [0] [Marsh] tiles",
"Must be adjacent to [3] to [6] [Desert] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -335,6 +380,10 @@
"faith": 4,
"happiness": 2,
"occursOn": ["Plains","Grassland"],
"uniques": ["Must be adjacent to [0] [Tundra] tiles",
"Must be adjacent to [0] [Desert] tiles",
"Must be adjacent to [0] [Marsh] tiles",
"Must not be on [1] largest landmasses"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -346,6 +395,11 @@
"food": 2,
"faith": 6,
"occursOn": ["Plains","Desert"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] [Grassland] tiles",
"Must be adjacent to [0] [Tundra] tiles",
"Must be adjacent to [0] [Marsh] tiles",
"Must be adjacent to [3] to [6] [Plains] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -358,6 +412,8 @@
"type": "NaturalWonder",
"production": 6,
"occursOn": ["Plains","Desert"],
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [0] to [2] [Mountain] tiles"],
"turnsInto": "Plains",
"impassable": true,
"unbuildable": true,
@ -368,6 +424,7 @@
"type": "NaturalWonder",
"food": 6,
"occursOn": ["Plains"],
"uniques": ["Must be adjacent to [0] [Coast] tiles"],
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
@ -383,7 +440,10 @@
"turnsInto": "Mountain",
"impassable": true,
"unbuildable": true,
"uniques": ["Grants Altitude Training (double movement and +10% Strength in hills) to adjacent land units for the rest of the game"], //ToDo
"uniques": ["Must be adjacent to [0] [Coast] tiles",
"Must be adjacent to [2] to [6] [Hill] tiles",
"Must be adjacent to [0] to [2] [Mountain] tiles",
"Grants Altitude Training (double movement and +10% Strength in hills) to adjacent land units for the rest of the game"], //ToDo
"weight": 10
}
*/

View File

@ -84,8 +84,8 @@ object GameStarter {
addCivStats(gameInfo)
}
runAndMeasure("assignContinents?") {
if (tileMap.continentSizes.isEmpty()) // Probably saved map without continent data
runAndMeasure("assignContinents") {
mapGen.assignContinents(tileMap)
}

View File

@ -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")
println("Natural Wonders for this game: $spawned")
}
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)
*/
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
}
}
}
private fun trySpawnOnSuitableLocation(suitableLocations: List<TileInfo>, wonder: Terrain): TileInfo? {
if (suitableLocations.isNotEmpty()) {
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 (MapGenerator.consoleOutput)
println("No suitable location for ${wonder.name}")
return null
if (list.size >= minGroupSize) {
list.forEach {
clearTile(it)
it.naturalWonder = wonder.name
it.baseTerrain = wonder.turnsInto!!
}
/*
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) {
if (convertNeighborsTo != 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)
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 = Constants.coast
}
tile.baseTerrain = convertNeighborsTo
clearTile(tile)
}
}
if (MapGenerator.consoleOutput)
println("Natural Wonder ${wonder.name} @${location.position}")
return true
}
}
/*
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
if (MapGenerator.consoleOutput)
println("No suitable location for ${wonder.name}")
return false
}
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

@ -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? {

View File

@ -4,9 +4,9 @@ import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText
enum class UniqueTarget {
/** Buildings, units, nations, policies, religions, techs etc.
/** Buildings, units, nations, policies, religions, techs etc.
* Basically anything caught by CivInfo.getMatchingUniques. */
enum class UniqueTarget {
Global,
// Civilization-specific
@ -71,6 +71,14 @@ enum class UniqueType(val text:String, vararg target: UniqueTarget) {
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