mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 09:48:12 +07:00
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:
@ -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!
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
*/
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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\")"
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user