Separated Landmass and Natural Wonder generation from the MapGenerator class - now it's much easier to understand what's going on where.
This commit is contained in:
parent
844cdcb821
commit
60aeebd3bb
7 changed files with 796 additions and 771 deletions
|
@ -9,11 +9,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
|
|||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.GameStarter
|
||||
import com.unciv.logic.map.MapGenerator
|
||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.logic.map.MapSize
|
||||
import com.unciv.logic.map.MapType
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.ui.MultiplayerScreen
|
||||
import com.unciv.ui.mapeditor.EditorMapHolder
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.badlogic.gdx.math.Vector2
|
|||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
|
|
|
@ -1,768 +0,0 @@
|
|||
package com.unciv.logic.map
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.Terrain
|
||||
import com.unciv.models.ruleset.tile.TerrainType
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class MapGenerator(val ruleset: Ruleset) {
|
||||
|
||||
fun generateMap(mapParameters: MapParameters, seed: Long = System.currentTimeMillis()): TileMap {
|
||||
val mapRadius = mapParameters.size.radius
|
||||
val mapType = mapParameters.type
|
||||
val map: TileMap
|
||||
|
||||
if (mapParameters.shape == MapShape.rectangular) {
|
||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
||||
map = TileMap(size.x.toInt(), size.y.toInt(), ruleset)
|
||||
}
|
||||
else
|
||||
map = TileMap(mapRadius, ruleset)
|
||||
|
||||
map.mapParameters = mapParameters
|
||||
map.mapParameters.seed = seed
|
||||
|
||||
if (mapType == MapType.empty)
|
||||
return map
|
||||
|
||||
seedRNG(seed)
|
||||
generateLand(map,ruleset)
|
||||
raiseMountainsAndHills(map)
|
||||
applyHumidityAndTemperature(map)
|
||||
spawnLakesAndCoasts(map)
|
||||
spawnVegetation(map)
|
||||
spawnRareFeatures(map)
|
||||
spawnIce(map)
|
||||
spreadResources(map)
|
||||
spreadAncientRuins(map)
|
||||
spawnNaturalWonders(map)
|
||||
return map
|
||||
}
|
||||
|
||||
private fun seedRNG(seed: Long) {
|
||||
RNG = Random(seed)
|
||||
println("RNG seeded with $seed")
|
||||
}
|
||||
|
||||
private fun spawnLakesAndCoasts(map: TileMap) {
|
||||
|
||||
//define lakes
|
||||
var waterTiles = map.values.filter { it.isWater }
|
||||
|
||||
val tilesInArea = ArrayList<TileInfo>()
|
||||
val tilesToCheck = ArrayList<TileInfo>()
|
||||
|
||||
while (waterTiles.isNotEmpty()) {
|
||||
val initialWaterTile = waterTiles.random(RNG)
|
||||
tilesInArea += initialWaterTile
|
||||
tilesToCheck += initialWaterTile
|
||||
waterTiles -= initialWaterTile
|
||||
|
||||
// Floodfill to cluster water tiles
|
||||
while (tilesToCheck.isNotEmpty()) {
|
||||
val tileWeAreChecking = tilesToCheck.random(RNG)
|
||||
for (vector in tileWeAreChecking.neighbors
|
||||
.filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) {
|
||||
tilesInArea += vector
|
||||
tilesToCheck += vector
|
||||
waterTiles -= vector
|
||||
}
|
||||
tilesToCheck -= tileWeAreChecking
|
||||
}
|
||||
|
||||
if (tilesInArea.size <= 10) {
|
||||
for (tile in tilesInArea) {
|
||||
tile.baseTerrain = Constants.lakes
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
tilesInArea.clear()
|
||||
}
|
||||
|
||||
//Coasts
|
||||
for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) {
|
||||
val coastLength = max(1, RNG.nextInt(max(1, map.mapParameters.maxCoastExtension)))
|
||||
if (tile.getTilesInDistance(coastLength).any { it.isLand }) {
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun spreadAncientRuins(map: TileMap) {
|
||||
if(map.mapParameters.noRuins)
|
||||
return
|
||||
val suitableTiles = map.values.filter { it.isLand && !it.getBaseTerrain().impassable }
|
||||
val locations = chooseSpreadOutLocations(suitableTiles.size/100,
|
||||
suitableTiles, 10)
|
||||
for(tile in locations)
|
||||
tile.improvement = Constants.ancientRuins
|
||||
}
|
||||
|
||||
private fun spreadResources(tileMap: TileMap) {
|
||||
val distance = tileMap.mapParameters.size.radius
|
||||
for (tile in tileMap.values)
|
||||
if (tile.resource != null)
|
||||
tile.resource = null
|
||||
|
||||
spreadStrategicResources(tileMap, distance)
|
||||
spreadResources(tileMap, distance, ResourceType.Luxury)
|
||||
spreadResources(tileMap, distance, ResourceType.Bonus)
|
||||
}
|
||||
|
||||
//region natural-wonders
|
||||
|
||||
/*
|
||||
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/
|
||||
*/
|
||||
private fun spawnNaturalWonders(tileMap: TileMap) {
|
||||
if (tileMap.mapParameters.noNaturalWonders)
|
||||
return
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
// 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 allNaturalWonders = ruleset.terrains.values
|
||||
.filter { it.type == TerrainType.NaturalWonder }.toMutableList()
|
||||
|
||||
while (allNaturalWonders.isNotEmpty() && toBeSpawned.size < numberToSpawn) {
|
||||
val totalWeight = allNaturalWonders.map { it.weight }.sum().toFloat()
|
||||
val random = RNG.nextDouble()
|
||||
var sum = 0f
|
||||
for (wonder in allNaturalWonders) {
|
||||
sum += wonder.weight/totalWeight
|
||||
if (random <= sum) {
|
||||
toBeSpawned.add(wonder)
|
||||
allNaturalWonders.remove(wonder)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun trySpawnOnSuitableLocation(suitableLocations: List<TileInfo>, wonder: Terrain): TileInfo? {
|
||||
if (suitableLocations.isNotEmpty()) {
|
||||
val location = suitableLocations.random()
|
||||
location.naturalWonder = wonder.name
|
||||
location.baseTerrain = wonder.turnsInto!!
|
||||
location.terrainFeature = null
|
||||
return location
|
||||
}
|
||||
|
||||
println("No suitable location for ${wonder.name}")
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.mountain || neighbor.getBaseTerrain().name == Constants.hill} <= 4
|
||||
}
|
||||
|
||||
trySpawnOnSuitableLocation(suitableLocations, wonder)
|
||||
}
|
||||
|
||||
/*
|
||||
Mt. Fuji: Must be in grass or plains; cannot be adjacent to tundra, desert, marsh, or mountains;
|
||||
can be adjacent to a maximum of 2 hills; becomes mountain
|
||||
*/
|
||||
private fun spawnMountFuji(tileMap: TileMap) {
|
||||
val wonder = ruleset.terrains[Constants.mountFuji]!!
|
||||
val suitableLocations = tileMap.values.filter { it.resource == null && it.improvement == 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.mountain }
|
||||
&& it.neighbors.none { neighbor -> neighbor.getLastTerrain().name == Constants.marsh }
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.hill } <= 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.hill } >= 2
|
||||
&& 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 not water; 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& abs(it.latitude) > tileMap.maxLatitude * 0.1
|
||||
&& abs(it.latitude) < tileMap.maxLatitude * 0.7
|
||||
&& 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 location2 = location.neighbors
|
||||
.filter { it.resource == null && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.all{ it.isWater } }
|
||||
.toList().random()
|
||||
|
||||
location2.naturalWonder = wonder.name
|
||||
location2.baseTerrain = wonder.turnsInto!!
|
||||
location2.terrainFeature = null
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == 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
|
||||
tile.terrainFeature = null
|
||||
tile.resource = null
|
||||
tile.improvement = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == 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
|
||||
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.terrainFeature = null
|
||||
tile.resource = null
|
||||
tile.improvement = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 4
|
||||
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain ||
|
||||
neighbor.getBaseTerrain().name == Constants.hill} >= 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.any { neighbor -> neighbor.getBaseTerrain().name == Constants.hill }
|
||||
}
|
||||
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name) }
|
||||
|
||||
trySpawnOnSuitableLocation(suitableLocations, wonder)
|
||||
}
|
||||
//endregion
|
||||
|
||||
// Here, we need each specific resource to be spread over the map - it matters less if specific resources are near each other
|
||||
private fun spreadStrategicResources(tileMap: TileMap, distance: Int) {
|
||||
val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
|
||||
// passable land tiles (no mountains, no wonders) without resources yet
|
||||
val candidateTiles = tileMap.values.filter { it.resource == null && it.isLand && !it.getLastTerrain().impassable }
|
||||
val totalNumberOfResources = candidateTiles.size * tileMap.mapParameters.resourceRichness
|
||||
val resourcesPerType = (totalNumberOfResources/strategicResources.size).toInt()
|
||||
for (resource in strategicResources) {
|
||||
// remove the tiles where previous resources have been placed
|
||||
val suitableTiles = candidateTiles
|
||||
.filter { it.resource == null
|
||||
&& resource.terrainsCanBeFoundOn.contains(it.getBaseTerrain().name)
|
||||
&& (it.terrainFeature==null || ruleset.tileImprovements.containsKey("Remove "+it.terrainFeature)) }
|
||||
|
||||
val locations = chooseSpreadOutLocations(resourcesPerType, suitableTiles, distance)
|
||||
|
||||
for (location in locations) location.resource = resource.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreads resources of type [resourceType] picking locations at [distance] from each other.
|
||||
* [MapParameters.resourceRichness] used to control how many resources to spawn.
|
||||
*/
|
||||
private fun spreadResources(tileMap: TileMap, distance: Int, resourceType: ResourceType) {
|
||||
val resourcesOfType = ruleset.tileResources.values.filter { it.resourceType == resourceType }
|
||||
|
||||
val suitableTiles = tileMap.values
|
||||
.filter { it.resource == null && resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) } }
|
||||
val numberOfResources = tileMap.values.count { it.isLand && !it.getBaseTerrain().impassable } *
|
||||
tileMap.mapParameters.resourceRichness
|
||||
val locations = chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, distance)
|
||||
|
||||
val resourceToNumber = Counter<String>()
|
||||
|
||||
for (tile in locations) {
|
||||
val possibleResources = resourcesOfType
|
||||
.filter { it.terrainsCanBeFoundOn.contains(tile.getLastTerrain().name) }
|
||||
.map { it.name }
|
||||
if (possibleResources.isEmpty()) continue
|
||||
val resourceWithLeastAssignments = possibleResources.minBy { resourceToNumber[it]!! }!!
|
||||
resourceToNumber.add(resourceWithLeastAssignments, 1)
|
||||
tile.resource = resourceWithLeastAssignments
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseSpreadOutLocations(numberOfResources: Int, suitableTiles: List<TileInfo>, initialDistance: Int): ArrayList<TileInfo> {
|
||||
|
||||
for (distanceBetweenResources in initialDistance downTo 1) {
|
||||
var availableTiles = suitableTiles.toList()
|
||||
val chosenTiles = ArrayList<TileInfo>()
|
||||
|
||||
// If possible, we want to equalize the base terrains upon which
|
||||
// the resources are found, so we save how many have been
|
||||
// found for each base terrain and try to get one from the lowerst
|
||||
val baseTerrainsToChosenTiles = HashMap<String, Int>()
|
||||
for(tileInfo in availableTiles){
|
||||
if(tileInfo.baseTerrain !in baseTerrainsToChosenTiles)
|
||||
baseTerrainsToChosenTiles[tileInfo.baseTerrain] = 0
|
||||
}
|
||||
|
||||
for (i in 1..numberOfResources) {
|
||||
if (availableTiles.isEmpty()) break
|
||||
val orderedKeys = baseTerrainsToChosenTiles.entries
|
||||
.sortedBy { it.value }.map { it.key }
|
||||
val firstKeyWithTilesLeft = orderedKeys
|
||||
.first { availableTiles.any { tile -> tile.baseTerrain== it} }
|
||||
val chosenTile = availableTiles.filter { it.baseTerrain==firstKeyWithTilesLeft }.random()
|
||||
availableTiles = availableTiles.filter { it.aerialDistanceTo(chosenTile) > distanceBetweenResources }
|
||||
chosenTiles.add(chosenTile)
|
||||
baseTerrainsToChosenTiles[firstKeyWithTilesLeft] = baseTerrainsToChosenTiles[firstKeyWithTilesLeft]!!+1
|
||||
}
|
||||
// Either we got them all, or we're not going to get anything better
|
||||
if (chosenTiles.size == numberOfResources || distanceBetweenResources == 1) return chosenTiles
|
||||
}
|
||||
throw Exception("Couldn't choose suitable tiles for $numberOfResources resources!")
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.elevationExponent] favors high elevation
|
||||
*/
|
||||
private fun raiseMountainsAndHills(tileMap: TileMap) {
|
||||
val elevationSeed = RNG.nextInt().toDouble()
|
||||
tileMap.setTransients(ruleset)
|
||||
for (tile in tileMap.values.filter { !it.isWater }) {
|
||||
var elevation = getPerlinNoise(tile, elevationSeed, scale = 2.0)
|
||||
elevation = abs(elevation).pow(1.0 - tileMap.mapParameters.elevationExponent.toDouble()) * elevation.sign
|
||||
|
||||
if (elevation <= 0.5) tile.baseTerrain = Constants.plains
|
||||
else if (elevation <= 0.7) tile.baseTerrain = Constants.hill
|
||||
else if (elevation <= 1.0) tile.baseTerrain = Constants.mountain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.tilesPerBiomeArea] to set biomes size
|
||||
* [MapParameters.temperatureExtremeness] to favor very high and very low temperatures
|
||||
*/
|
||||
private fun applyHumidityAndTemperature(tileMap: TileMap) {
|
||||
val humiditySeed = RNG.nextInt().toDouble()
|
||||
val temperatureSeed = RNG.nextInt().toDouble()
|
||||
|
||||
tileMap.setTransients(ruleset)
|
||||
|
||||
val scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble()
|
||||
|
||||
for (tile in tileMap.values) {
|
||||
if (tile.isWater || tile.baseTerrain in arrayOf(Constants.mountain, Constants.hill))
|
||||
continue
|
||||
|
||||
val humidity = (getPerlinNoise(tile, humiditySeed, scale = scale, nOctaves = 1) + 1.0) / 2.0
|
||||
|
||||
val randomTemperature = getPerlinNoise(tile, temperatureSeed, scale = scale, nOctaves = 1)
|
||||
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
||||
var temperature = ((5.0 * latitudeTemperature + randomTemperature) / 6.0)
|
||||
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
||||
|
||||
tile.baseTerrain = when {
|
||||
temperature < -0.4 -> {
|
||||
when {
|
||||
humidity < 0.5 -> Constants.snow
|
||||
else -> Constants.tundra
|
||||
}
|
||||
}
|
||||
temperature < 0.8 -> {
|
||||
when {
|
||||
humidity < 0.5 -> Constants.plains
|
||||
else -> Constants.grassland
|
||||
}
|
||||
}
|
||||
temperature <= 1.0 -> {
|
||||
when {
|
||||
humidity < 0.7 -> Constants.desert
|
||||
else -> Constants.plains
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
println(temperature)
|
||||
Constants.lakes
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.vegetationOccurrance] is the threshold for vegetation spawn
|
||||
*/
|
||||
private fun spawnVegetation(tileMap: TileMap) {
|
||||
val vegetationSeed = RNG.nextInt().toDouble()
|
||||
val candidateTerrains = Constants.vegetation.flatMap{ ruleset.terrains[it]!!.occursOn!! }
|
||||
for (tile in tileMap.values.asSequence().filter { it.baseTerrain in candidateTerrains && it.terrainFeature == null}) {
|
||||
val vegetation = (getPerlinNoise(tile, vegetationSeed, scale = 3.0, nOctaves = 1) + 1.0) / 2.0
|
||||
|
||||
if (vegetation <= tileMap.mapParameters.vegetationRichness)
|
||||
tile.terrainFeature = Constants.vegetation.filter { ruleset.terrains[it]!!.occursOn!!.contains(tile.baseTerrain) }.random(RNG)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* [MapParameters.rareFeaturesProbability] is the probability of spawning a rare feature
|
||||
*/
|
||||
private fun spawnRareFeatures(tileMap: TileMap) {
|
||||
val rareFeatures = ruleset.terrains.values.filter {
|
||||
it.type == TerrainType.TerrainFeature &&
|
||||
it.name !in Constants.vegetation &&
|
||||
it.name != Constants.ice
|
||||
}
|
||||
for (tile in tileMap.values.asSequence().filter { it.terrainFeature == null }) {
|
||||
if (RNG.nextDouble() <= tileMap.mapParameters.rareFeaturesRichness) {
|
||||
val possibleFeatures = rareFeatures.filter { it.occursOn != null && it.occursOn.contains(tile.baseTerrain) }
|
||||
if (possibleFeatures.any())
|
||||
tile.terrainFeature = possibleFeatures.random(RNG).name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.temperatureExtremeness] as in [applyHumidityAndTemperature]
|
||||
*/
|
||||
private fun spawnIce(tileMap: TileMap) {
|
||||
tileMap.setTransients(ruleset)
|
||||
val temperatureSeed = RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
if (tile.baseTerrain !in Constants.sea || tile.terrainFeature != null)
|
||||
continue
|
||||
|
||||
val randomTemperature = getPerlinNoise(tile, temperatureSeed, scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble(), nOctaves = 1)
|
||||
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
||||
var temperature = ((latitudeTemperature + randomTemperature) / 2.0)
|
||||
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
||||
if (temperature < -0.8)
|
||||
tile.terrainFeature = Constants.ice
|
||||
}
|
||||
}
|
||||
|
||||
companion object MapLandmassGenerator {
|
||||
var RNG = Random(42)
|
||||
|
||||
fun generateLand(tileMap: TileMap, ruleset: Ruleset) {
|
||||
if(ruleset.terrains.values.none { it.type==TerrainType.Water }) {
|
||||
for (tile in tileMap.values)
|
||||
tile.baseTerrain = Constants.grassland
|
||||
return
|
||||
}
|
||||
when (tileMap.mapParameters.type) {
|
||||
MapType.pangaea -> createPangea(tileMap)
|
||||
MapType.continents -> createTwoContinents(tileMap)
|
||||
MapType.perlin -> createPerlin(tileMap)
|
||||
MapType.archipelago -> createArchipelago(tileMap)
|
||||
MapType.default -> generateLandCellularAutomata(tileMap)
|
||||
}
|
||||
}
|
||||
private fun spawnLandOrWater(tile: TileInfo, elevation: Double, threshold: Double) {
|
||||
when {
|
||||
elevation < threshold -> tile.baseTerrain = Constants.ocean
|
||||
else -> tile.baseTerrain = Constants.grassland
|
||||
}
|
||||
}
|
||||
|
||||
private fun smooth(tileMap: TileMap) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
||||
if (RNG.nextFloat() < 0.5f)
|
||||
continue
|
||||
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = Constants.grassland
|
||||
else if (numberOfLandNeighbors < 3)
|
||||
tileInfo.baseTerrain = Constants.ocean
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPerlin(tileMap: TileMap) {
|
||||
val elevationSeed = RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = getPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createArchipelago(tileMap: TileMap) {
|
||||
val elevationSeed = RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = getRidgedPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, 0.25 + tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPangea(tileMap: TileMap) {
|
||||
val elevationSeed = RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getCircularNoise(tile, tileMap) ) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTwoContinents(tileMap: TileMap) {
|
||||
val elevationSeed = RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getTwoContinentsTransform(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCircularNoise(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val randomScale = RNG.nextDouble()
|
||||
val distanceFactor = percentualDistanceToCenter(tileInfo, tileMap)
|
||||
|
||||
return min(0.3, 1.0 - (5.0 * distanceFactor * distanceFactor + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
private fun getTwoContinentsTransform(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val randomScale = RNG.nextDouble()
|
||||
val longitudeFactor = abs(tileInfo.longitude) / tileMap.maxLongitude
|
||||
|
||||
return min(0.2,-1.0 + (5.0 * longitudeFactor.pow(0.6f) + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
private fun percentualDistanceToCenter(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
if (tileMap.mapParameters.shape == MapShape.hexagonal)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble()/mapRadius
|
||||
else {
|
||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / HexMath.getDistance(Vector2.Zero, Vector2(size.x/2, size.y/2))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a perlin noise channel combining multiple octaves
|
||||
*
|
||||
* [nOctaves] is the number of octaves
|
||||
* [persistence] is the scaling factor of octave amplitudes
|
||||
* [lacunarity] is the scaling factor of octave frequencies
|
||||
* [scale] is the distance the noise is observed from
|
||||
*/
|
||||
private fun getPerlinNoise(tile: TileInfo, seed: Double,
|
||||
nOctaves: Int = 6,
|
||||
persistence: Double = 0.5,
|
||||
lacunarity: Double = 2.0,
|
||||
scale: Double = 10.0): Double {
|
||||
val worldCoords = HexMath.hex2WorldCoords(tile.position)
|
||||
return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ridged perlin noise. As for parameters see [getPerlinNoise]
|
||||
*/
|
||||
private fun getRidgedPerlinNoise(tile: TileInfo, seed: Double,
|
||||
nOctaves: Int = 10,
|
||||
persistence: Double = 0.5,
|
||||
lacunarity: Double = 2.0,
|
||||
scale: Double = 15.0): Double {
|
||||
val worldCoords = HexMath.hex2WorldCoords(tile.position)
|
||||
return Perlin.ridgedNoise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
|
||||
// region Cellular automata
|
||||
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
val mapType = tileMap.mapParameters.type
|
||||
val numSmooth = 4
|
||||
|
||||
//init
|
||||
for (tile in tileMap.values) {
|
||||
val terrainType = getInitialTerrainCellularAutomata(tile, tileMap.mapParameters)
|
||||
if (terrainType == TerrainType.Land) tile.baseTerrain = Constants.grassland
|
||||
else tile.baseTerrain = Constants.ocean
|
||||
tile.setTransients()
|
||||
}
|
||||
|
||||
//smooth
|
||||
val grassland = Constants.grassland
|
||||
val ocean = Constants.ocean
|
||||
|
||||
for (loop in 0..numSmooth) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
//if (HexMath.getDistance(Vector2.Zero, tileInfo.position) < mapRadius) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == grassland }
|
||||
if (tileInfo.baseTerrain == grassland) { // land tile
|
||||
if (numberOfLandNeighbors < 3)
|
||||
tileInfo.baseTerrain = ocean
|
||||
} else { // water tile
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = grassland
|
||||
}
|
||||
/*} else {
|
||||
tileInfo.baseTerrain = ocean
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapParameters: MapParameters): TerrainType {
|
||||
val mapRadius = mapParameters.size.radius
|
||||
|
||||
// default
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.9f * mapRadius) {
|
||||
if (RNG.nextDouble() < 0.1) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.85f * mapRadius) {
|
||||
if (RNG.nextDouble() < 0.2) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (RNG.nextDouble() < 0.55) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
||||
class RiverGenerator(){
|
||||
|
||||
public class RiverCoordinate(val position: Vector2, val bottomRightOrLeft:BottomRightOrLeft){
|
||||
enum class BottomRightOrLeft{
|
||||
BottomLeft, BottomRight
|
||||
}
|
||||
|
||||
fun getAdjacentPositions(): Sequence<RiverCoordinate> {
|
||||
// What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa
|
||||
// This means that a lot of obviously-wrong assignments are simple to spot
|
||||
if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
|
||||
return sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomRight), // same tile, other side
|
||||
RiverCoordinate(position.cpy().add(1f,0f), BottomRightOrLeft.BottomRight), // tile to MY top-left, take its bottom right corner
|
||||
RiverCoordinate(position.cpy().add(0f,-1f), BottomRightOrLeft.BottomRight) // Tile to MY bottom-left, take its bottom right
|
||||
)
|
||||
} else {
|
||||
return sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomLeft), // same tile, other side
|
||||
RiverCoordinate(position.cpy().add(0f,1f), BottomRightOrLeft.BottomLeft), // tile to MY top-right, take its bottom left
|
||||
RiverCoordinate(position.cpy().add(-1f,0f), BottomRightOrLeft.BottomLeft) // tile to MY bottom-right, take its bottom left
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
359
core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt
Normal file
359
core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt
Normal file
|
@ -0,0 +1,359 @@
|
|||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.TerrainType
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sign
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class MapGenerator(val ruleset: Ruleset) {
|
||||
var randomness = MapGenerationRandomness()
|
||||
|
||||
fun generateMap(mapParameters: MapParameters, seed: Long = System.currentTimeMillis()): TileMap {
|
||||
val mapRadius = mapParameters.size.radius
|
||||
val mapType = mapParameters.type
|
||||
val map: TileMap
|
||||
|
||||
if (mapParameters.shape == MapShape.rectangular) {
|
||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
||||
map = TileMap(size.x.toInt(), size.y.toInt(), ruleset)
|
||||
}
|
||||
else
|
||||
map = TileMap(mapRadius, ruleset)
|
||||
|
||||
map.mapParameters = mapParameters
|
||||
map.mapParameters.seed = seed
|
||||
|
||||
if (mapType == MapType.empty)
|
||||
return map
|
||||
|
||||
seedRNG(seed)
|
||||
MapLandmassGenerator(randomness).generateLand(map,ruleset)
|
||||
raiseMountainsAndHills(map)
|
||||
applyHumidityAndTemperature(map)
|
||||
spawnLakesAndCoasts(map)
|
||||
spawnVegetation(map)
|
||||
spawnRareFeatures(map)
|
||||
spawnIce(map)
|
||||
spreadResources(map)
|
||||
spreadAncientRuins(map)
|
||||
NaturalWonderGenerator(ruleset).spawnNaturalWonders(map, randomness)
|
||||
return map
|
||||
}
|
||||
|
||||
private fun seedRNG(seed: Long) {
|
||||
randomness.RNG = Random(seed)
|
||||
println("RNG seeded with $seed")
|
||||
}
|
||||
|
||||
private fun spawnLakesAndCoasts(map: TileMap) {
|
||||
|
||||
//define lakes
|
||||
var waterTiles = map.values.filter { it.isWater }
|
||||
|
||||
val tilesInArea = ArrayList<TileInfo>()
|
||||
val tilesToCheck = ArrayList<TileInfo>()
|
||||
|
||||
while (waterTiles.isNotEmpty()) {
|
||||
val initialWaterTile = waterTiles.random(randomness.RNG)
|
||||
tilesInArea += initialWaterTile
|
||||
tilesToCheck += initialWaterTile
|
||||
waterTiles -= initialWaterTile
|
||||
|
||||
// Floodfill to cluster water tiles
|
||||
while (tilesToCheck.isNotEmpty()) {
|
||||
val tileWeAreChecking = tilesToCheck.random(randomness.RNG)
|
||||
for (vector in tileWeAreChecking.neighbors
|
||||
.filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) {
|
||||
tilesInArea += vector
|
||||
tilesToCheck += vector
|
||||
waterTiles -= vector
|
||||
}
|
||||
tilesToCheck -= tileWeAreChecking
|
||||
}
|
||||
|
||||
if (tilesInArea.size <= 10) {
|
||||
for (tile in tilesInArea) {
|
||||
tile.baseTerrain = Constants.lakes
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
tilesInArea.clear()
|
||||
}
|
||||
|
||||
//Coasts
|
||||
for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) {
|
||||
val coastLength = max(1, randomness.RNG.nextInt(max(1, map.mapParameters.maxCoastExtension)))
|
||||
if (tile.getTilesInDistance(coastLength).any { it.isLand }) {
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun spreadAncientRuins(map: TileMap) {
|
||||
if(map.mapParameters.noRuins)
|
||||
return
|
||||
val suitableTiles = map.values.filter { it.isLand && !it.getBaseTerrain().impassable }
|
||||
val locations = chooseSpreadOutLocations(suitableTiles.size/100,
|
||||
suitableTiles, 10)
|
||||
for(tile in locations)
|
||||
tile.improvement = Constants.ancientRuins
|
||||
}
|
||||
|
||||
private fun spreadResources(tileMap: TileMap) {
|
||||
val distance = tileMap.mapParameters.size.radius
|
||||
for (tile in tileMap.values)
|
||||
if (tile.resource != null)
|
||||
tile.resource = null
|
||||
|
||||
spreadStrategicResources(tileMap, distance)
|
||||
spreadResources(tileMap, distance, ResourceType.Luxury)
|
||||
spreadResources(tileMap, distance, ResourceType.Bonus)
|
||||
}
|
||||
|
||||
// Here, we need each specific resource to be spread over the map - it matters less if specific resources are near each other
|
||||
private fun spreadStrategicResources(tileMap: TileMap, distance: Int) {
|
||||
val strategicResources = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
|
||||
// passable land tiles (no mountains, no wonders) without resources yet
|
||||
val candidateTiles = tileMap.values.filter { it.resource == null && it.isLand && !it.getLastTerrain().impassable }
|
||||
val totalNumberOfResources = candidateTiles.size * tileMap.mapParameters.resourceRichness
|
||||
val resourcesPerType = (totalNumberOfResources/strategicResources.size).toInt()
|
||||
for (resource in strategicResources) {
|
||||
// remove the tiles where previous resources have been placed
|
||||
val suitableTiles = candidateTiles
|
||||
.filter { it.resource == null
|
||||
&& resource.terrainsCanBeFoundOn.contains(it.getBaseTerrain().name)
|
||||
&& (it.terrainFeature==null || ruleset.tileImprovements.containsKey("Remove "+it.terrainFeature)) }
|
||||
|
||||
val locations = chooseSpreadOutLocations(resourcesPerType, suitableTiles, distance)
|
||||
|
||||
for (location in locations) location.resource = resource.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spreads resources of type [resourceType] picking locations at [distance] from each other.
|
||||
* [MapParameters.resourceRichness] used to control how many resources to spawn.
|
||||
*/
|
||||
private fun spreadResources(tileMap: TileMap, distance: Int, resourceType: ResourceType) {
|
||||
val resourcesOfType = ruleset.tileResources.values.filter { it.resourceType == resourceType }
|
||||
|
||||
val suitableTiles = tileMap.values
|
||||
.filter { it.resource == null && resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) } }
|
||||
val numberOfResources = tileMap.values.count { it.isLand && !it.getBaseTerrain().impassable } *
|
||||
tileMap.mapParameters.resourceRichness
|
||||
val locations = chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, distance)
|
||||
|
||||
val resourceToNumber = Counter<String>()
|
||||
|
||||
for (tile in locations) {
|
||||
val possibleResources = resourcesOfType
|
||||
.filter { it.terrainsCanBeFoundOn.contains(tile.getLastTerrain().name) }
|
||||
.map { it.name }
|
||||
if (possibleResources.isEmpty()) continue
|
||||
val resourceWithLeastAssignments = possibleResources.minBy { resourceToNumber[it]!! }!!
|
||||
resourceToNumber.add(resourceWithLeastAssignments, 1)
|
||||
tile.resource = resourceWithLeastAssignments
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseSpreadOutLocations(numberOfResources: Int, suitableTiles: List<TileInfo>, initialDistance: Int): ArrayList<TileInfo> {
|
||||
|
||||
for (distanceBetweenResources in initialDistance downTo 1) {
|
||||
var availableTiles = suitableTiles.toList()
|
||||
val chosenTiles = ArrayList<TileInfo>()
|
||||
|
||||
// If possible, we want to equalize the base terrains upon which
|
||||
// the resources are found, so we save how many have been
|
||||
// found for each base terrain and try to get one from the lowerst
|
||||
val baseTerrainsToChosenTiles = HashMap<String, Int>()
|
||||
for(tileInfo in availableTiles){
|
||||
if(tileInfo.baseTerrain !in baseTerrainsToChosenTiles)
|
||||
baseTerrainsToChosenTiles[tileInfo.baseTerrain] = 0
|
||||
}
|
||||
|
||||
for (i in 1..numberOfResources) {
|
||||
if (availableTiles.isEmpty()) break
|
||||
val orderedKeys = baseTerrainsToChosenTiles.entries
|
||||
.sortedBy { it.value }.map { it.key }
|
||||
val firstKeyWithTilesLeft = orderedKeys
|
||||
.first { availableTiles.any { tile -> tile.baseTerrain== it} }
|
||||
val chosenTile = availableTiles.filter { it.baseTerrain==firstKeyWithTilesLeft }.random()
|
||||
availableTiles = availableTiles.filter { it.aerialDistanceTo(chosenTile) > distanceBetweenResources }
|
||||
chosenTiles.add(chosenTile)
|
||||
baseTerrainsToChosenTiles[firstKeyWithTilesLeft] = baseTerrainsToChosenTiles[firstKeyWithTilesLeft]!!+1
|
||||
}
|
||||
// Either we got them all, or we're not going to get anything better
|
||||
if (chosenTiles.size == numberOfResources || distanceBetweenResources == 1) return chosenTiles
|
||||
}
|
||||
throw Exception("Couldn't choose suitable tiles for $numberOfResources resources!")
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.elevationExponent] favors high elevation
|
||||
*/
|
||||
private fun raiseMountainsAndHills(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
tileMap.setTransients(ruleset)
|
||||
for (tile in tileMap.values.filter { !it.isWater }) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed, scale = 2.0)
|
||||
elevation = abs(elevation).pow(1.0 - tileMap.mapParameters.elevationExponent.toDouble()) * elevation.sign
|
||||
|
||||
if (elevation <= 0.5) tile.baseTerrain = Constants.plains
|
||||
else if (elevation <= 0.7) tile.baseTerrain = Constants.hill
|
||||
else if (elevation <= 1.0) tile.baseTerrain = Constants.mountain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.tilesPerBiomeArea] to set biomes size
|
||||
* [MapParameters.temperatureExtremeness] to favor very high and very low temperatures
|
||||
*/
|
||||
private fun applyHumidityAndTemperature(tileMap: TileMap) {
|
||||
val humiditySeed = randomness.RNG.nextInt().toDouble()
|
||||
val temperatureSeed = randomness.RNG.nextInt().toDouble()
|
||||
|
||||
tileMap.setTransients(ruleset)
|
||||
|
||||
val scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble()
|
||||
|
||||
for (tile in tileMap.values) {
|
||||
if (tile.isWater || tile.baseTerrain in arrayOf(Constants.mountain, Constants.hill))
|
||||
continue
|
||||
|
||||
val humidity = (randomness.getPerlinNoise(tile, humiditySeed, scale = scale, nOctaves = 1) + 1.0) / 2.0
|
||||
|
||||
val randomTemperature = randomness.getPerlinNoise(tile, temperatureSeed, scale = scale, nOctaves = 1)
|
||||
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
||||
var temperature = ((5.0 * latitudeTemperature + randomTemperature) / 6.0)
|
||||
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
||||
|
||||
tile.baseTerrain = when {
|
||||
temperature < -0.4 -> {
|
||||
if (humidity < 0.5) Constants.snow
|
||||
else Constants.tundra
|
||||
}
|
||||
temperature < 0.8 -> {
|
||||
if (humidity < 0.5) Constants.plains
|
||||
else Constants.grassland
|
||||
}
|
||||
temperature <= 1.0 -> {
|
||||
if (humidity < 0.7) Constants.desert
|
||||
else Constants.plains
|
||||
}
|
||||
else -> {
|
||||
println(temperature)
|
||||
Constants.lakes
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.vegetationOccurrance] is the threshold for vegetation spawn
|
||||
*/
|
||||
private fun spawnVegetation(tileMap: TileMap) {
|
||||
val vegetationSeed = randomness.RNG.nextInt().toDouble()
|
||||
val candidateTerrains = Constants.vegetation.flatMap{ ruleset.terrains[it]!!.occursOn!! }
|
||||
for (tile in tileMap.values.asSequence().filter { it.baseTerrain in candidateTerrains && it.terrainFeature == null}) {
|
||||
val vegetation = (randomness.getPerlinNoise(tile, vegetationSeed, scale = 3.0, nOctaves = 1) + 1.0) / 2.0
|
||||
|
||||
if (vegetation <= tileMap.mapParameters.vegetationRichness)
|
||||
tile.terrainFeature = Constants.vegetation.filter { ruleset.terrains[it]!!.occursOn!!.contains(tile.baseTerrain) }.random(randomness.RNG)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* [MapParameters.rareFeaturesProbability] is the probability of spawning a rare feature
|
||||
*/
|
||||
private fun spawnRareFeatures(tileMap: TileMap) {
|
||||
val rareFeatures = ruleset.terrains.values.filter {
|
||||
it.type == TerrainType.TerrainFeature &&
|
||||
it.name !in Constants.vegetation &&
|
||||
it.name != Constants.ice
|
||||
}
|
||||
for (tile in tileMap.values.asSequence().filter { it.terrainFeature == null }) {
|
||||
if (randomness.RNG.nextDouble() <= tileMap.mapParameters.rareFeaturesRichness) {
|
||||
val possibleFeatures = rareFeatures.filter { it.occursOn != null && it.occursOn.contains(tile.baseTerrain) }
|
||||
if (possibleFeatures.any())
|
||||
tile.terrainFeature = possibleFeatures.random(randomness.RNG).name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [MapParameters.temperatureExtremeness] as in [applyHumidityAndTemperature]
|
||||
*/
|
||||
private fun spawnIce(tileMap: TileMap) {
|
||||
tileMap.setTransients(ruleset)
|
||||
val temperatureSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
if (tile.baseTerrain !in Constants.sea || tile.terrainFeature != null)
|
||||
continue
|
||||
|
||||
val randomTemperature = randomness.getPerlinNoise(tile, temperatureSeed, scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble(), nOctaves = 1)
|
||||
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
||||
var temperature = ((latitudeTemperature + randomTemperature) / 2.0)
|
||||
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
||||
if (temperature < -0.8)
|
||||
tile.terrainFeature = Constants.ice
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MapGenerationRandomness{
|
||||
var RNG = Random(42)
|
||||
|
||||
/**
|
||||
* Generates a perlin noise channel combining multiple octaves
|
||||
*
|
||||
* [nOctaves] is the number of octaves
|
||||
* [persistence] is the scaling factor of octave amplitudes
|
||||
* [lacunarity] is the scaling factor of octave frequencies
|
||||
* [scale] is the distance the noise is observed from
|
||||
*/
|
||||
fun getPerlinNoise(tile: TileInfo, seed: Double,
|
||||
nOctaves: Int = 6,
|
||||
persistence: Double = 0.5,
|
||||
lacunarity: Double = 2.0,
|
||||
scale: Double = 10.0): Double {
|
||||
val worldCoords = HexMath.hex2WorldCoords(tile.position)
|
||||
return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
}
|
||||
|
||||
class RiverGenerator(){
|
||||
|
||||
public class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft){
|
||||
enum class BottomRightOrLeft{
|
||||
BottomLeft, BottomRight
|
||||
}
|
||||
|
||||
fun getAdjacentPositions(): Sequence<RiverCoordinate> {
|
||||
// What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa
|
||||
// This means that a lot of obviously-wrong assignments are simple to spot
|
||||
if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
|
||||
return sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomRight), // same tile, other side
|
||||
RiverCoordinate(position.cpy().add(1f, 0f), BottomRightOrLeft.BottomRight), // tile to MY top-left, take its bottom right corner
|
||||
RiverCoordinate(position.cpy().add(0f, -1f), BottomRightOrLeft.BottomRight) // Tile to MY bottom-left, take its bottom right
|
||||
)
|
||||
} else {
|
||||
return sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomLeft), // same tile, other side
|
||||
RiverCoordinate(position.cpy().add(0f, 1f), BottomRightOrLeft.BottomLeft), // tile to MY top-right, take its bottom left
|
||||
RiverCoordinate(position.cpy().add(-1f, 0f), BottomRightOrLeft.BottomLeft) // tile to MY bottom-right, take its bottom left
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.TerrainType
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
class MapLandmassGenerator(val randomness: MapGenerationRandomness) {
|
||||
|
||||
fun generateLand(tileMap: TileMap, ruleset: Ruleset) {
|
||||
if(ruleset.terrains.values.none { it.type== TerrainType.Water }) {
|
||||
for (tile in tileMap.values)
|
||||
tile.baseTerrain = Constants.grassland
|
||||
return
|
||||
}
|
||||
when (tileMap.mapParameters.type) {
|
||||
MapType.pangaea -> createPangea(tileMap)
|
||||
MapType.continents -> createTwoContinents(tileMap)
|
||||
MapType.perlin -> createPerlin(tileMap)
|
||||
MapType.archipelago -> createArchipelago(tileMap)
|
||||
MapType.default -> generateLandCellularAutomata(tileMap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spawnLandOrWater(tile: TileInfo, elevation: Double, threshold: Double) {
|
||||
when {
|
||||
elevation < threshold -> tile.baseTerrain = Constants.ocean
|
||||
else -> tile.baseTerrain = Constants.grassland
|
||||
}
|
||||
}
|
||||
|
||||
private fun smooth(tileMap: TileMap, randomness: MapGenerationRandomness) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == Constants.grassland }
|
||||
if (randomness.RNG.nextFloat() < 0.5f)
|
||||
continue
|
||||
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = Constants.grassland
|
||||
else if (numberOfLandNeighbors < 3)
|
||||
tileInfo.baseTerrain = Constants.ocean
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPerlin(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createArchipelago(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = getRidgedPerlinNoise(tile, elevationSeed)
|
||||
spawnLandOrWater(tile, elevation, 0.25 + tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPangea(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getCircularNoise(tile, tileMap) ) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTwoContinents(tileMap: TileMap) {
|
||||
val elevationSeed = randomness.RNG.nextInt().toDouble()
|
||||
for (tile in tileMap.values) {
|
||||
var elevation = randomness.getPerlinNoise(tile, elevationSeed)
|
||||
elevation = (elevation + getTwoContinentsTransform(tile, tileMap)) / 2.0
|
||||
spawnLandOrWater(tile, elevation, tileMap.mapParameters.waterThreshold.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCircularNoise(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val randomScale = randomness.RNG.nextDouble()
|
||||
val distanceFactor = percentualDistanceToCenter(tileInfo, tileMap)
|
||||
|
||||
return min(0.3, 1.0 - (5.0 * distanceFactor * distanceFactor + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
private fun getTwoContinentsTransform(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val randomScale = randomness.RNG.nextDouble()
|
||||
val longitudeFactor = abs(tileInfo.longitude) / tileMap.maxLongitude
|
||||
|
||||
return min(0.2, -1.0 + (5.0 * longitudeFactor.pow(0.6f) + randomScale) / 3.0)
|
||||
}
|
||||
|
||||
private fun percentualDistanceToCenter(tileInfo: TileInfo, tileMap: TileMap): Double {
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
if (tileMap.mapParameters.shape == MapShape.hexagonal)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble()/mapRadius
|
||||
else {
|
||||
val size = HexMath.getEquivalentRectangularSize(mapRadius)
|
||||
return HexMath.getDistance(Vector2.Zero, tileInfo.position).toDouble() / HexMath.getDistance(Vector2.Zero, Vector2(size.x / 2, size.y / 2))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates ridged perlin noise. As for parameters see [getPerlinNoise]
|
||||
*/
|
||||
private fun getRidgedPerlinNoise(tile: TileInfo, seed: Double,
|
||||
nOctaves: Int = 10,
|
||||
persistence: Double = 0.5,
|
||||
lacunarity: Double = 2.0,
|
||||
scale: Double = 15.0): Double {
|
||||
val worldCoords = HexMath.hex2WorldCoords(tile.position)
|
||||
return Perlin.ridgedNoise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
|
||||
// region Cellular automata
|
||||
private fun generateLandCellularAutomata(tileMap: TileMap) {
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
val mapType = tileMap.mapParameters.type
|
||||
val numSmooth = 4
|
||||
|
||||
//init
|
||||
for (tile in tileMap.values) {
|
||||
val terrainType = getInitialTerrainCellularAutomata(tile, tileMap.mapParameters)
|
||||
if (terrainType == TerrainType.Land) tile.baseTerrain = Constants.grassland
|
||||
else tile.baseTerrain = Constants.ocean
|
||||
tile.setTransients()
|
||||
}
|
||||
|
||||
//smooth
|
||||
val grassland = Constants.grassland
|
||||
val ocean = Constants.ocean
|
||||
|
||||
for (loop in 0..numSmooth) {
|
||||
for (tileInfo in tileMap.values) {
|
||||
//if (HexMath.getDistance(Vector2.Zero, tileInfo.position) < mapRadius) {
|
||||
val numberOfLandNeighbors = tileInfo.neighbors.count { it.baseTerrain == grassland }
|
||||
if (tileInfo.baseTerrain == grassland) { // land tile
|
||||
if (numberOfLandNeighbors < 3)
|
||||
tileInfo.baseTerrain = ocean
|
||||
} else { // water tile
|
||||
if (numberOfLandNeighbors > 3)
|
||||
tileInfo.baseTerrain = grassland
|
||||
}
|
||||
/*} else {
|
||||
tileInfo.baseTerrain = ocean
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapParameters: MapParameters): TerrainType {
|
||||
val mapRadius = mapParameters.size.radius
|
||||
|
||||
// default
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.9f * mapRadius) {
|
||||
if (randomness.RNG.nextDouble() < 0.1) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.85f * mapRadius) {
|
||||
if (randomness.RNG.nextDouble() < 0.2) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
if (randomness.RNG.nextDouble() < 0.55) return TerrainType.Land else return TerrainType.Water
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.map.TileInfo
|
||||
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 kotlin.math.abs
|
||||
import kotlin.math.round
|
||||
|
||||
class NaturalWonderGenerator(val ruleset: Ruleset){
|
||||
|
||||
/*
|
||||
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/
|
||||
*/
|
||||
fun spawnNaturalWonders(tileMap: TileMap, randomness: MapGenerationRandomness) {
|
||||
if (tileMap.mapParameters.noNaturalWonders)
|
||||
return
|
||||
val mapRadius = tileMap.mapParameters.size.radius
|
||||
// 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 allNaturalWonders = ruleset.terrains.values
|
||||
.filter { it.type == TerrainType.NaturalWonder }.toMutableList()
|
||||
|
||||
while (allNaturalWonders.isNotEmpty() && toBeSpawned.size < numberToSpawn) {
|
||||
val totalWeight = allNaturalWonders.map { it.weight }.sum().toFloat()
|
||||
val random = randomness.RNG.nextDouble()
|
||||
var sum = 0f
|
||||
for (wonder in allNaturalWonders) {
|
||||
sum += wonder.weight/totalWeight
|
||||
if (random <= sum) {
|
||||
toBeSpawned.add(wonder)
|
||||
allNaturalWonders.remove(wonder)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun trySpawnOnSuitableLocation(suitableLocations: List<TileInfo>, wonder: Terrain): TileInfo? {
|
||||
if (suitableLocations.isNotEmpty()) {
|
||||
val location = suitableLocations.random()
|
||||
location.naturalWonder = wonder.name
|
||||
location.baseTerrain = wonder.turnsInto!!
|
||||
location.terrainFeature = null
|
||||
return location
|
||||
}
|
||||
|
||||
println("No suitable location for ${wonder.name}")
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.none { neighbor -> neighbor.getBaseTerrain().name == Constants.grassland }
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 2
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.mountain || neighbor.getBaseTerrain().name == Constants.hill } <= 4
|
||||
}
|
||||
|
||||
trySpawnOnSuitableLocation(suitableLocations, wonder)
|
||||
}
|
||||
|
||||
/*
|
||||
Mt. Fuji: Must be in grass or plains; cannot be adjacent to tundra, desert, marsh, or mountains;
|
||||
can be adjacent to a maximum of 2 hills; becomes mountain
|
||||
*/
|
||||
private fun spawnMountFuji(tileMap: TileMap) {
|
||||
val wonder = ruleset.terrains[Constants.mountFuji]!!
|
||||
val suitableLocations = tileMap.values.filter { it.resource == null && it.improvement == 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.mountain }
|
||||
&& it.neighbors.none { neighbor -> neighbor.getLastTerrain().name == Constants.marsh }
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.hill } <= 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.count{ neighbor -> neighbor.getBaseTerrain().name == Constants.hill } >= 2
|
||||
&& 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 not water; 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& abs(it.latitude) > tileMap.maxLatitude * 0.1
|
||||
&& abs(it.latitude) < tileMap.maxLatitude * 0.7
|
||||
&& 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 location2 = location.neighbors
|
||||
.filter { it.resource == null && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.all{ it.isWater } }
|
||||
.toList().random()
|
||||
|
||||
location2.naturalWonder = wonder.name
|
||||
location2.baseTerrain = wonder.turnsInto!!
|
||||
location2.terrainFeature = null
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == 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
|
||||
tile.terrainFeature = null
|
||||
tile.resource = null
|
||||
tile.improvement = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == 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
|
||||
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.terrainFeature = null
|
||||
tile.resource = null
|
||||
tile.improvement = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain } <= 4
|
||||
&& it.neighbors.count { neighbor -> neighbor.getBaseTerrain().name == Constants.mountain ||
|
||||
neighbor.getBaseTerrain().name == Constants.hill
|
||||
} >= 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& it.neighbors.any { neighbor -> neighbor.getBaseTerrain().name == Constants.hill }
|
||||
}
|
||||
|
||||
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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
|
||||
&& 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 && it.improvement == null
|
||||
&& wonder.occursOn!!.contains(it.getLastTerrain().name) }
|
||||
|
||||
trySpawnOnSuitableLocation(suitableLocations, wonder)
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import com.badlogic.gdx.Gdx
|
|||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.MainMenuScreen
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.map.MapGenerator
|
||||
import com.unciv.logic.map.mapgenerator.MapGenerator
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
|
|
Loading…
Reference in a new issue