diff --git a/android/assets/jsons/TileResources.json b/android/assets/jsons/TileResources.json index 22d9a70335..21f98f8048 100644 --- a/android/assets/jsons/TileResources.json +++ b/android/assets/jsons/TileResources.json @@ -57,7 +57,7 @@ { name:"Fish", resourceType:"Bonus", - terrainsCanBeFoundOn:["Coast"], + terrainsCanBeFoundOn:["Coast", "Lakes"], food:1, improvement:"Fishing Boats", improvementStats:{food:1}, diff --git a/core/src/com/unciv/logic/HexMath.kt b/core/src/com/unciv/logic/HexMath.kt index 04be602a7b..a9a65b2c91 100644 --- a/core/src/com/unciv/logic/HexMath.kt +++ b/core/src/com/unciv/logic/HexMath.kt @@ -74,8 +74,12 @@ class HexMath { return hexesToReturn } - fun getDistance(origin: Vector2, destination: Vector2): Int { // Yes, this is a dumb implementation. But I can't be arsed to think of a better one right now, other stuff to do. - return max(abs(origin.x-destination.x),abs(origin.y-destination.y) ).toInt() + fun getDistance(origin: Vector2, destination: Vector2): Int { + val relative_x = origin.x-destination.x + val relative_y = origin.y-destination.y + if (relative_x * relative_y >= 0) + return max(abs(relative_x),abs(relative_y)).toInt() + else + return (abs(relative_x) + abs(relative_y)).toInt() } - } diff --git a/core/src/com/unciv/logic/map/RandomMapGenerator.kt b/core/src/com/unciv/logic/map/RandomMapGenerator.kt index b7d49b003e..9cc0af2160 100644 --- a/core/src/com/unciv/logic/map/RandomMapGenerator.kt +++ b/core/src/com/unciv/logic/map/RandomMapGenerator.kt @@ -10,6 +10,148 @@ import com.unciv.ui.utils.getRandom import java.util.* import kotlin.collections.HashMap import kotlin.math.ceil +import kotlin.math.pow + +enum class MapType { + Perlin, + Default, + Pangaea, +} + +class CelluarAutomataRandomMapGenerator(): SeedRandomMapGenerator() { + var landProb = 0.55f + var numSmooth = 4 + var mapType = MapType.Default + + constructor(type: MapType): this() { + mapType = type + if (mapType < MapType.Default) { + mapType = MapType.Default + } + } + + override fun generateMap(distance: Int): HashMap { + val mapVectors = HexMath().getVectorsInDistance(Vector2.Zero, distance) + val landscape = HashMap() + + //init + for (vector in mapVectors) { + landscape[vector] = generateInitTerrain(vector, distance) + } + + //smooth + for (loop in 0..numSmooth) { + for (vector in mapVectors) { + if (HexMath().getDistance(Vector2.Zero, vector) < distance) { + val neighborLands = HexMath().getAdjacentVectors(vector).count {landscape[it] == TerrainType.Land} + if (landscape[vector] == TerrainType.Land) { + if (neighborLands < 3) + landscape[vector] = TerrainType.Water + } else { + if (neighborLands > 3) + landscape[vector] = TerrainType.Land + } + } + else { + landscape[vector] = TerrainType.Water + } + } + } + + val map = HashMap() + for (vector in mapVectors) + map[vector] = generateTile(vector,landscape[vector]!!) + + divideIntoAreas(6, 0f, map) + + val mapToReturn = HashMap() + for(tile in map) { + tile.value.setTransients() + mapToReturn[tile.key.toString()] = tile.value + } + + setWaterTiles(mapToReturn) + + for(tile in mapToReturn.values) randomizeTile(tile,mapToReturn) + + return mapToReturn + } + + private fun generateInitTerrain(vector: Vector2, distance: Int): TerrainType { + var type: TerrainType + if (mapType == MapType.Pangaea) { + val distanceFactor = (HexMath().getDistance(Vector2.Zero, vector) * 1.8 / distance).toFloat() + type = if (Random().nextDouble() < landProb.pow(distanceFactor)) TerrainType.Land else TerrainType.Water + } else { //default + if (HexMath().getDistance(Vector2.Zero, vector) > 0.9f * distance) + type = if (Random().nextDouble() < 0.1) TerrainType.Land else TerrainType.Water + else if (HexMath().getDistance(Vector2.Zero, vector) > 0.85f * distance) + type = if (Random().nextDouble() < 0.2) TerrainType.Land else TerrainType.Water + else + type = if (Random().nextDouble() < landProb) TerrainType.Land else TerrainType.Water + } + return type + } + + private fun generateTile(vector: Vector2, type: TerrainType): TileInfo { + val tile=TileInfo() + tile.position=vector + if (type == TerrainType.Land) + tile.baseTerrain = "" + else + tile.baseTerrain = "Ocean" + return tile + } + + override fun setWaterTiles(map: HashMap) { + //define lakes + var waterTiles = map.values.filter { it.isWater() }.map { it.position } + var tilesInArea = ArrayList() + var tilesToCheck = ArrayList() + while (waterTiles.isNotEmpty()) { + val tile = waterTiles.getRandom() + tilesInArea.add(tile) + tilesToCheck.add(tile) + waterTiles -= tile + + while (tilesToCheck.isNotEmpty()) { + val tileChecking = tilesToCheck.getRandom() + for (vector in HexMath().getVectorsAtDistance(tileChecking,1).filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) { + tilesInArea.add(vector) + tilesToCheck.add(vector) + waterTiles -= vector + } + tilesToCheck.remove(tileChecking) + } + + if (tilesInArea.size <= 10) { + for (vector in tilesInArea) { + map[vector.toString()]!!.baseTerrain = "Lakes" + } + } + tilesInArea.clear() + } + + //Coasts + for (tile in map.values.filter { it.baseTerrain == "Ocean" }) { + if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) { + tile.baseTerrain = "Coast" + tile.setTransients() + } + } + } + + override fun randomizeTile(tileInfo: TileInfo, map: HashMap){ + if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){ + tileInfo.baseTerrain = "Mountain" + tileInfo.setTransients() + } + addRandomTerrainFeature(tileInfo) + addRandomResourceToTile(tileInfo) + maybeAddAncientRuins(tileInfo) + } +} + class PerlinNoiseRandomMapGenerator:SeedRandomMapGenerator(){ override fun generateMap(distance: Int): HashMap { @@ -276,11 +418,11 @@ open class RandomMapGenerator { } - private fun hasLandTile(map: HashMap, vector: Vector2): Boolean { + fun hasLandTile(map: HashMap, vector: Vector2): Boolean { return map.containsKey(vector.toString()) && map[vector.toString()]!!.getBaseTerrain().type == TerrainType.Land } - fun setWaterTiles(map: HashMap) { + open fun setWaterTiles(map: HashMap) { for (tile in map.values.filter { it.baseTerrain == "Ocean" }) { if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) { tile.baseTerrain = "Coast" @@ -289,7 +431,7 @@ open class RandomMapGenerator { } } - fun randomizeTile(tileInfo: TileInfo, map: HashMap){ + open fun randomizeTile(tileInfo: TileInfo, map: HashMap){ if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){ tileInfo.baseTerrain = "Mountain" tileInfo.setTransients() diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index 8eb3bbd966..53fedff24b 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -3,6 +3,7 @@ package com.unciv.logic.map import com.badlogic.gdx.math.Vector2 import com.unciv.logic.GameInfo import com.unciv.logic.HexMath +import com.unciv.logic.map.MapType import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.gamebasics.GameBasics import com.unciv.ui.NewGameScreen @@ -31,13 +32,13 @@ class TileMap { get() = tileList - constructor(distance: Int, mapType: NewGameScreen.NewGameParameters.MapType) { + constructor(distance: Int, mapType: MapType) { val map:HashMap - if(mapType==NewGameScreen.NewGameParameters.MapType.WithWater) + if(mapType==MapType.Perlin) map = PerlinNoiseRandomMapGenerator().generateMap(distance) - - else map = SeedRandomMapGenerator().generateMap(distance,0f) + else + map = CelluarAutomataRandomMapGenerator(mapType).generateMap(distance) tileList.addAll(map.values) // tileList.addAll(AlexanderRandomMapGenerator().generateMap(distance,0.8f).values) diff --git a/core/src/com/unciv/ui/NewGameScreen.kt b/core/src/com/unciv/ui/NewGameScreen.kt index 4e347623d8..0b12922e76 100644 --- a/core/src/com/unciv/ui/NewGameScreen.kt +++ b/core/src/com/unciv/ui/NewGameScreen.kt @@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener import com.badlogic.gdx.utils.Array import com.unciv.GameStarter import com.unciv.logic.GameInfo +import com.unciv.logic.map.MapType import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.Nation import com.unciv.models.gamebasics.Translations @@ -20,15 +21,11 @@ import kotlin.concurrent.thread class NewGameScreen: PickerScreen(){ class NewGameParameters{ - enum class MapType{ - LandOnly, - WithWater - } var difficulty="Prince" var nation="Babylon" var mapRadius=20 var numberOfEnemies=3 - var mapType=MapType.WithWater + var mapType=MapType.Perlin } val newGameParameters=NewGameParameters() @@ -123,6 +120,19 @@ class NewGameScreen: PickerScreen(){ val newGameOptionsTable = Table() newGameOptionsTable.skin = skin + newGameOptionsTable.add("{Map type}:".tr()) + val mapTypes = LinkedHashMap() + for (type in MapType.values()) { + mapTypes[type.toString()] = type + } + val mapTypeSelectBox = TranslatedSelectBox(mapTypes.keys, "Perlin", skin) + mapTypeSelectBox.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + newGameParameters.mapType = mapTypes[mapTypeSelectBox.selected.value]!! + } + }) + newGameOptionsTable.add(mapTypeSelectBox).pad(10f).row() + newGameOptionsTable.add("{World size}:".tr()) val worldSizeToRadius = LinkedHashMap() worldSizeToRadius["Tiny"] = 10