mirror of
https://github.com/yairm210/Unciv.git
synced 2025-03-12 19:10:12 +07:00
River generation is go! =D
This commit is contained in:
parent
60aeebd3bb
commit
ba9329963f
@ -24,6 +24,7 @@ object Constants {
|
||||
const val oasis = "Oasis"
|
||||
const val atoll = "Atoll"
|
||||
const val ice = "Ice"
|
||||
const val floodPlains = "Flood plains"
|
||||
val vegetation = arrayOf(forest, jungle)
|
||||
val sea = arrayOf(ocean, coast)
|
||||
|
||||
|
@ -44,6 +44,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
spawnVegetation(map)
|
||||
spawnRareFeatures(map)
|
||||
spawnIce(map)
|
||||
RiverGenerator(randomness).spawnRivers(map)
|
||||
spreadResources(map)
|
||||
spreadAncientRuins(map)
|
||||
NaturalWonderGenerator(ruleset).spawnNaturalWonders(map, randomness)
|
||||
@ -104,7 +105,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
if(map.mapParameters.noRuins)
|
||||
return
|
||||
val suitableTiles = map.values.filter { it.isLand && !it.getBaseTerrain().impassable }
|
||||
val locations = chooseSpreadOutLocations(suitableTiles.size/100,
|
||||
val locations = randomness.chooseSpreadOutLocations(suitableTiles.size/100,
|
||||
suitableTiles, 10)
|
||||
for(tile in locations)
|
||||
tile.improvement = Constants.ancientRuins
|
||||
@ -135,7 +136,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
&& resource.terrainsCanBeFoundOn.contains(it.getBaseTerrain().name)
|
||||
&& (it.terrainFeature==null || ruleset.tileImprovements.containsKey("Remove "+it.terrainFeature)) }
|
||||
|
||||
val locations = chooseSpreadOutLocations(resourcesPerType, suitableTiles, distance)
|
||||
val locations = randomness.chooseSpreadOutLocations(resourcesPerType, suitableTiles, distance)
|
||||
|
||||
for (location in locations) location.resource = resource.name
|
||||
}
|
||||
@ -152,7 +153,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
.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 locations = randomness.chooseSpreadOutLocations(numberOfResources.toInt(), suitableTiles, distance)
|
||||
|
||||
val resourceToNumber = Counter<String>()
|
||||
|
||||
@ -167,37 +168,6 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -280,6 +250,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
||||
val rareFeatures = ruleset.terrains.values.filter {
|
||||
it.type == TerrainType.TerrainFeature &&
|
||||
it.name !in Constants.vegetation &&
|
||||
it.name != Constants.floodPlains &&
|
||||
it.name != Constants.ice
|
||||
}
|
||||
for (tile in tileMap.values.asSequence().filter { it.terrainFeature == null }) {
|
||||
@ -331,29 +302,63 @@ class MapGenerationRandomness{
|
||||
val worldCoords = HexMath.hex2WorldCoords(tile.position)
|
||||
return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale)
|
||||
}
|
||||
|
||||
|
||||
fun chooseSpreadOutLocations(number: 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..number) {
|
||||
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 == number || distanceBetweenResources == 1) return chosenTiles
|
||||
}
|
||||
throw Exception("Couldn't choose suitable tiles for $number resources!")
|
||||
}
|
||||
}
|
||||
|
||||
class RiverGenerator(){
|
||||
|
||||
public class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft){
|
||||
enum class BottomRightOrLeft{
|
||||
BottomLeft, BottomRight
|
||||
}
|
||||
class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft){
|
||||
enum class BottomRightOrLeft{
|
||||
/** 7 O'Clock of the tile */
|
||||
BottomLeft,
|
||||
/** 5 O'Clock of the tile */
|
||||
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
|
||||
)
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
110
core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt
Normal file
110
core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt
Normal file
@ -0,0 +1,110 @@
|
||||
package com.unciv.logic.map.mapgenerator
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
|
||||
class RiverGenerator(val randomness: MapGenerationRandomness){
|
||||
|
||||
fun spawnRivers(map: TileMap){
|
||||
val numberOfRivers = map.values.count { it.isLand } / 100
|
||||
|
||||
var optionalTiles = map.values
|
||||
.filter { it.baseTerrain== Constants.mountain && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }
|
||||
if(optionalTiles.size < numberOfRivers)
|
||||
optionalTiles += map.values.filter { it.baseTerrain== Constants.hill && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }
|
||||
if(optionalTiles.size < numberOfRivers)
|
||||
optionalTiles = map.values.filter { it.isLand && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }
|
||||
|
||||
|
||||
val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, 10)
|
||||
for(tile in riverStarts) spawnRiver(tile, map)
|
||||
|
||||
for(tile in map.values){
|
||||
if(tile.isAdjacentToRiver()){
|
||||
if(tile.baseTerrain== Constants.desert) tile.terrainFeature= Constants.floodPlains
|
||||
else if(tile.baseTerrain== Constants.snow) tile.baseTerrain = Constants.tundra
|
||||
else if(tile.baseTerrain== Constants.tundra) tile.baseTerrain = Constants.plains
|
||||
tile.setTerrainTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getClosestWaterTile(tile: TileInfo): TileInfo {
|
||||
var distance = 1
|
||||
while(true){
|
||||
val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater }
|
||||
if(waterTiles.none()) {
|
||||
distance++
|
||||
continue
|
||||
}
|
||||
return waterTiles.toList().random(randomness.RNG)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spawnRiver(initialPosition: TileInfo, map: TileMap) {
|
||||
// Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful!
|
||||
val endPosition = getClosestWaterTile(initialPosition)
|
||||
|
||||
var riverCoordinate = RiverCoordinate(initialPosition.position,
|
||||
RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG))
|
||||
|
||||
|
||||
while(getAdjacentTiles(riverCoordinate,map).none { it.isWater }){
|
||||
val possibleCoordinates = riverCoordinate.getAdjacentPositions()
|
||||
if(possibleCoordinates.none()) return // end of the line
|
||||
val newCoordinate = possibleCoordinates
|
||||
// .sortedBy { numberOfConnectedRivers(it,map) }
|
||||
.groupBy { getAdjacentTiles(it,map).map { it.aerialDistanceTo(endPosition) }.min()!! }
|
||||
.minBy { it.key }!!
|
||||
.component2().random(randomness.RNG)
|
||||
// .minBy { getAdjacentTiles(it,map).map { it.aerialDistanceTo(endPosition) }.min()!! }!!
|
||||
|
||||
// set new rivers in place
|
||||
val riverCoordinateTile = map[riverCoordinate.position]
|
||||
if(newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left
|
||||
riverCoordinateTile.hasBottomRiver=true
|
||||
else if(riverCoordinate.bottomRightOrLeft== RiverCoordinate.BottomRightOrLeft.BottomRight){
|
||||
if(getAdjacentTiles(newCoordinate,map).contains(riverCoordinateTile)) // moved from our 5 O'Clock to our 3 O'Clock
|
||||
riverCoordinateTile.hasBottomRightRiver = true
|
||||
else // moved from our 5 O'Clock down in the 5 O'Clock direction - this is the 8 O'Clock river of the tile to our 4 O'Clock!
|
||||
map[newCoordinate.position].hasBottomLeftRiver = true
|
||||
}
|
||||
else { // riverCoordinate.bottomRightOrLeft==RiverCoordinate.BottomRightOrLeft.Left
|
||||
if(getAdjacentTiles(newCoordinate,map).contains(riverCoordinateTile)) // moved from our 7 O'Clock to our 9 O'Clock
|
||||
riverCoordinateTile.hasBottomLeftRiver = true
|
||||
else // moved from our 7 O'Clock down in the 7 O'Clock direction
|
||||
map[newCoordinate.position].hasBottomRightRiver = true
|
||||
}
|
||||
riverCoordinate = newCoordinate
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getAdjacentTiles(riverCoordinate: RiverCoordinate, map: TileMap): Sequence<TileInfo> {
|
||||
val potentialPositions = sequenceOf(
|
||||
riverCoordinate.position,
|
||||
riverCoordinate.position.cpy().add(-1f, -1f), // tile directly below us,
|
||||
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft)
|
||||
riverCoordinate.position.cpy().add(0f, -1f) // tile to our bottom-left
|
||||
else riverCoordinate.position.cpy().add(-1f, 0f) // tile to our bottom-right
|
||||
)
|
||||
return potentialPositions.map { if (map.contains(it)) map[it] else null }.filterNotNull()
|
||||
}
|
||||
|
||||
fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate, map: TileMap): Int {
|
||||
var sum = 0
|
||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRiver) sum += 1
|
||||
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) {
|
||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomLeftRiver) sum += 1
|
||||
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(0f, -1f)
|
||||
if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomRightRiver) sum += 1
|
||||
} else {
|
||||
if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRightRiver) sum += 1
|
||||
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(-1f, 0f)
|
||||
if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user