Rectangular maps, maps generator, bugfixes (part 1) (#1843)

* TileMap Rectangular constructor

* HexMath utilities

* Perlin gradients changed & utility function to combine octaves

* NewGame & MapParameters UI

* MapParameters & MapGenerator refactor

* Advanced Map Parameters UI

* Fix Hide advanced settings
This commit is contained in:
Federico Luongo
2020-02-04 22:30:35 +01:00
committed by GitHub
parent 49086086ee
commit d43afe38e6
10 changed files with 570 additions and 272 deletions

View File

@ -11,7 +11,7 @@ import java.util.*
import kotlin.collections.ArrayList
import kotlin.math.max
class GameStarter{
class GameStarter {
fun startNewGame(newGameParameters: GameParameters, mapParameters: MapParameters): GameInfo {
val gameInfo = GameInfo()
@ -19,9 +19,9 @@ class GameStarter{
gameInfo.gameParameters = newGameParameters
val ruleset = RulesetCache.getComplexRuleset(newGameParameters.mods)
if(mapParameters.name!="")
if(mapParameters.name != "")
gameInfo.tileMap = MapSaver().loadMap(mapParameters.name)
else gameInfo.tileMap = MapGenerator().generateMap(mapParameters, ruleset)
else gameInfo.tileMap = MapGenerator(ruleset).generateMap(mapParameters)
gameInfo.tileMap.mapParameters = mapParameters
gameInfo.tileMap.setTransients(ruleset)

View File

@ -14,6 +14,32 @@ object HexMath {
return getVectorForAngle((2 * Math.PI * (hour / 12f)).toFloat())
}
/** returns the number of tiles in a hexagonal map of radius size*/
fun getNumberOfTilesInHexagon(size: Int): Int {
if (size < 0) return 0
return 1 + 6 * size * (size + 1) / 2
}
/* In our reference system latitude, i.e. how distant from equator we are is proportional to x + y*/
fun getLatitude(vector: Vector2): Float {
return vector.x + vector.y
}
fun getLongitude(vector: Vector2): Float {
return vector.x - vector.y
}
/** returns a vector containing width and height a rectangular map should have to have
* approximately the same number of tiles as an hexagonal map given a height/width ratio */
fun getEquivalentRectangularSize(size: Int, ratio: Float = 0.65f): Vector2 {
if (size < 0)
return Vector2.Zero
val nTiles = getNumberOfTilesInHexagon(size)
val width = round(sqrt(nTiles.toFloat()/ratio))
val height = round(width * ratio)
return Vector2(width, height)
}
fun getAdjacentVectors(origin: Vector2): ArrayList<Vector2> {
val vectors = arrayListOf(
Vector2(1f, 0f),
@ -59,6 +85,20 @@ object HexMath {
return Vector2(cubicCoord.y, -cubicCoord.z)
}
fun cubic2EvenQCoords(cubicCoord: Vector3): Vector2 {
return Vector2(cubicCoord.x, cubicCoord.z + (cubicCoord.x + (cubicCoord.x.toInt() and 1)) / 2)
}
fun evenQ2CubicCoords(evenQCoord: Vector2): Vector3 {
val x = evenQCoord.x
val z = evenQCoord.y - (evenQCoord.x + (evenQCoord.x.toInt() and 1)) / 2
val y = -x-z
return Vector3(x,y,z)
}
fun evenQ2HexCoords(evenQCoord: Vector2): Vector2 {
return cubic2HexCoords(evenQ2CubicCoords(evenQCoord))
}
fun roundCubicCoords(cubicCoords: Vector3): Vector3 {
var rx = round(cubicCoords.x)
var ry = round(cubicCoords.y)

View File

@ -8,77 +8,66 @@ 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 java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.*
import kotlin.random.Random
// This is no longer an Enum because there were map types that were disabled,
// and when parsing an existing map to an Enum you have to have all the options.
// So either we had to keep the old Enums forever, or change to strings.
class MapType {
companion object{
val default="Default" // Creates a cellular automata map
val perlin="Perlin"
val continents = "Continents"
val pangaea = "Pangaea"
val custom="Custom"
val empty="Empty"
}
}
class MapGenerator(val ruleset: Ruleset) {
class MapGenerator {
fun generateMap(mapParameters: MapParameters, ruleset: Ruleset): TileMap {
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)
val map = TileMap(mapRadius, ruleset)
map.mapParameters = mapParameters
map.mapParameters.seed = seed
// Is the empty map is requested, there's no need for further generation
if (mapType == MapType.empty) return map
if (mapType == MapType.empty)
return map
// Step one - separate land and water, in form of Grasslands and Oceans
if (mapType == MapType.perlin)
MapLandmassGenerator().generateLandPerlin(map)
else MapLandmassGenerator().generateLandCellularAutomata(map, mapRadius, mapType)
divideIntoBiomes(map, 6, 0.05f, mapRadius, ruleset)
for (tile in map.values) tile.setTransients()
setWaterTiles(map)
for (tile in map.values) randomizeTile(tile, mapParameters, ruleset)
spreadResources(map, mapRadius, ruleset)
if(!mapParameters.noRuins)
spreadAncientRuins(map)
if (!mapParameters.noNaturalWonders)
spawnNaturalWonders(map, mapRadius, ruleset)
seedRNG(seed)
generateLand(map)
divideIntoBiomes(map)
spawnLakesAndCoasts(map)
randomizeTiles(map)
spreadResources(map)
spreadAncientRuins(map)
spawnNaturalWonders(map)
return map
}
private fun seedRNG(seed: Long) {
RNG = Random(seed)
println("RNG seeded with $seed")
}
fun setWaterTiles(map: TileMap) {
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()
val initialWaterTile = waterTiles.random(RNG)
tilesInArea += initialWaterTile
tilesToCheck += initialWaterTile
waterTiles -= initialWaterTile
// Floodfill to cluster water tiles
while (tilesToCheck.isNotEmpty()) {
val tileWeAreChecking = tilesToCheck.random()
val tileWeAreChecking = tilesToCheck.random(RNG)
for (vector in tileWeAreChecking.neighbors
.filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) {
tilesInArea += vector
@ -99,52 +88,65 @@ class MapGenerator {
//Coasts
for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) {
if (tile.getTilesInDistance(2).any { it.isLand }) {
val coastLength = max(1, RNG.nextInt(max(1, map.mapParameters.maxCoastExtension)))
if (tile.getTilesInDistance(coastLength).any { it.isLand }) {
tile.baseTerrain = Constants.coast
tile.setTransients()
}
}
}
fun randomizeTile(tileInfo: TileInfo, mapParameters: MapParameters, ruleset: Ruleset) {
if (tileInfo.getBaseTerrain().type == TerrainType.Land && Math.random() < 0.05f) {
tileInfo.baseTerrain = Constants.mountain
tileInfo.setTransients()
private fun randomizeTiles(tileMap: TileMap) {
for (tile in tileMap.values) {
if (tile.getBaseTerrain().type == TerrainType.Land && RNG.nextDouble() < tileMap.mapParameters.mountainProbability) {
tile.baseTerrain = Constants.mountain
tile.setTransients()
}
addRandomTerrainFeature(tile, tileMap.mapParameters)
}
addRandomTerrainFeature(tileInfo, ruleset)
}
fun getLatitude(vector: Vector2): Float {
return abs(vector.x + vector.y)
}
private fun divideIntoBiomes(tileMap: TileMap) {
val averageTilesPerArea = tileMap.mapParameters.tilesPerBiomeArea
val waterPercent = tileMap.mapParameters.waterProbability
val maxLatitude = tileMap.values.map { abs(HexMath.getLatitude(it.position)) }.max()!!
fun divideIntoBiomes(map: TileMap, averageTilesPerArea: Int, waterPercent: Float, distance: Int, ruleset: Ruleset) {
val areas = ArrayList<Area>()
val terrains = ruleset.terrains.values
.filter { it.type === TerrainType.Land && it.name != Constants.lakes && it.name != Constants.mountain }
for (tile in map.values.filter { it.baseTerrain == Constants.grassland }) tile.baseTerrain = "" // So we know it's not chosen
// So we know it's not chosen
for (tile in tileMap.values.filter { it.baseTerrain == Constants.grassland })
tile.baseTerrain = ""
while (map.values.any { it.baseTerrain == "" }) // the world could be split into lots off tiny islands, and every island deserves land types
while (tileMap.values.any { it.baseTerrain == "" }) // the world could be split into lots off tiny islands, and every island deserves land types
{
val emptyTiles = map.values.filter { it.baseTerrain == "" }.toMutableList()
val emptyTiles = tileMap.values.filter { it.baseTerrain == "" }.toMutableList()
val numberOfSeeds = ceil(emptyTiles.size / averageTilesPerArea.toFloat()).toInt()
val maxLatitude = abs(getLatitude(Vector2(distance.toFloat(), distance.toFloat())))
for (i in 0 until numberOfSeeds) {
var terrain = if (Math.random() > waterPercent) terrains.random().name
else Constants.ocean
val tile = emptyTiles.random()
var terrain = if (RNG.nextDouble() < waterPercent) Constants.ocean
else terrains.random(RNG).name
//change grassland to desert or tundra based on y
if (abs(getLatitude(tile.position)) < maxLatitude * 0.1) {
if (terrain == Constants.grassland || terrain == Constants.tundra)
val tile = emptyTiles.random(RNG)
val desertBand = maxLatitude * 0.5 * tileMap.mapParameters.temperatureExtremeness
val tundraBand = maxLatitude * (1 - 0.5 * tileMap.mapParameters.temperatureExtremeness)
if (abs(HexMath.getLatitude(tile.position)) < desertBand) {
if (terrain in arrayOf(Constants.grassland, Constants.tundra))
terrain = Constants.desert
} else if (abs(getLatitude(tile.position)) > maxLatitude * 0.7) {
if (terrain == Constants.grassland || terrain == Constants.plains || terrain == Constants.desert || terrain == Constants.ocean) {
} else if (abs(HexMath.getLatitude(tile.position)) > tundraBand) {
if (terrain in arrayOf(Constants.grassland, Constants.plains, Constants.desert, Constants.ocean))
terrain = Constants.tundra
}
} else {
if (terrain == Constants.tundra) terrain = Constants.plains
else if (terrain == Constants.desert) terrain = Constants.grassland
@ -159,14 +161,16 @@ class MapGenerator {
expandAreas(areas)
expandAreas(areas)
}
for (tile in tileMap.values)
tile.setTransients()
}
fun expandAreas(areas: ArrayList<Area>) {
private fun expandAreas(areas: ArrayList<Area>) {
val expandableAreas = ArrayList<Area>(areas)
while (expandableAreas.isNotEmpty()) {
val areaToExpand = expandableAreas.random()
val areaToExpand = expandableAreas.random(RNG)
if (areaToExpand.tiles.size >= 20) {
expandableAreas -= areaToExpand
continue
@ -178,7 +182,7 @@ class MapGenerator {
if (availableExpansionTiles.isEmpty()) expandableAreas -= areaToExpand
else {
val expansionTile = availableExpansionTiles.random()
val expansionTile = availableExpansionTiles.random(RNG)
areaToExpand.addTile(expansionTile)
val areasToJoin = areas.filter {
@ -195,40 +199,48 @@ class MapGenerator {
}
}
fun addRandomTerrainFeature(tileInfo: TileInfo, ruleset: Ruleset) {
if (tileInfo.getBaseTerrain().canHaveOverlay && Math.random() > 0.7f) {
private fun addRandomTerrainFeature(tileInfo: TileInfo, mapParameters: MapParameters) {
if (tileInfo.getBaseTerrain().canHaveOverlay && RNG.nextDouble() < mapParameters.terrainFeatureRichness) {
val secondaryTerrains = ruleset.terrains.values
.filter { it.type === TerrainType.TerrainFeature && it.occursOn != null && it.occursOn.contains(tileInfo.baseTerrain) }
if (secondaryTerrains.any()) tileInfo.terrainFeature = secondaryTerrains.random().name
.filter { it.type === TerrainType.TerrainFeature &&
it.occursOn != null &&
it.occursOn.contains(tileInfo.baseTerrain) }
if (secondaryTerrains.any())
tileInfo.terrainFeature = secondaryTerrains.random(RNG).name
}
}
fun spreadAncientRuins(map: TileMap) {
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
tile.improvement = Constants.ancientRuins
}
fun spreadResources(mapToReturn: TileMap, distance: Int, ruleset: Ruleset) {
private fun spreadResources(mapToReturn: TileMap) {
val distance = mapToReturn.mapParameters.size.radius
for (tile in mapToReturn.values)
if (tile.resource != null)
tile.resource = null
spreadStrategicResources(mapToReturn, distance, ruleset)
spreadResource(mapToReturn, distance, ResourceType.Luxury, ruleset)
spreadResource(mapToReturn, distance, ResourceType.Bonus, ruleset)
spreadStrategicResources(mapToReturn, distance)
spreadResource(mapToReturn, distance, ResourceType.Luxury)
spreadResource(mapToReturn, 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/
*/
fun spawnNaturalWonders(mapToReturn: TileMap, mapRadius: Int, ruleset: Ruleset) {
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()
@ -238,7 +250,7 @@ class MapGenerator {
while (allNaturalWonders.isNotEmpty() && toBeSpawned.size < numberToSpawn) {
val totalWeight = allNaturalWonders.map { it.weight }.sum().toFloat()
val random = Random().nextDouble()
val random = RNG.nextDouble()
var sum = 0f
for (wonder in allNaturalWonders) {
sum += wonder.weight/totalWeight
@ -254,16 +266,16 @@ class MapGenerator {
for (wonder in toBeSpawned) {
when (wonder.name) {
Constants.barringerCrater -> spawnBarringerCrater(mapToReturn, ruleset)
Constants.mountFuji -> spawnMountFuji(mapToReturn, ruleset)
Constants.grandMesa -> spawnGrandMesa(mapToReturn, ruleset)
Constants.greatBarrierReef -> spawnGreatBarrierReef(mapToReturn, ruleset, mapRadius)
Constants.krakatoa -> spawnKrakatoa(mapToReturn, ruleset)
Constants.rockOfGibraltar -> spawnRockOfGibraltar(mapToReturn, ruleset)
Constants.oldFaithful -> spawnOldFaithful(mapToReturn, ruleset)
Constants.cerroDePotosi -> spawnCerroDePotosi(mapToReturn, ruleset)
Constants.elDorado -> spawnElDorado(mapToReturn, ruleset)
Constants.fountainOfYouth -> spawnFountainOfYouth(mapToReturn, ruleset)
Constants.barringerCrater -> spawnBarringerCrater(tileMap, ruleset)
Constants.mountFuji -> spawnMountFuji(tileMap, ruleset)
Constants.grandMesa -> spawnGrandMesa(tileMap, ruleset)
Constants.greatBarrierReef -> spawnGreatBarrierReef(tileMap, ruleset, mapRadius)
Constants.krakatoa -> spawnKrakatoa(tileMap, ruleset)
Constants.rockOfGibraltar -> spawnRockOfGibraltar(tileMap, ruleset)
Constants.oldFaithful -> spawnOldFaithful(tileMap, ruleset)
Constants.cerroDePotosi -> spawnCerroDePotosi(tileMap, ruleset)
Constants.elDorado -> spawnElDorado(tileMap, ruleset)
Constants.fountainOfYouth -> spawnFountainOfYouth(tileMap, ruleset)
}
}
}
@ -339,11 +351,11 @@ class MapGenerator {
*/
private fun spawnGreatBarrierReef(mapToReturn: TileMap, ruleset: Ruleset, mapRadius: Int) {
val wonder = ruleset.terrains[Constants.greatBarrierReef]!!
val maxLatitude = abs(getLatitude(Vector2(mapRadius.toFloat(), mapRadius.toFloat())))
val maxLatitude = abs(HexMath.getLatitude(Vector2(mapRadius.toFloat(), mapRadius.toFloat())))
val suitableLocations = mapToReturn.values.filter { it.resource == null && it.improvement == null
&& wonder.occursOn!!.contains(it.getLastTerrain().name)
&& abs(getLatitude(it.position)) > maxLatitude * 0.1
&& abs(getLatitude(it.position)) < maxLatitude * 0.7
&& abs(HexMath.getLatitude(it.position)) > maxLatitude * 0.1
&& abs(HexMath.getLatitude(it.position)) < maxLatitude * 0.7
&& it.neighbors.all {neighbor -> neighbor.isWater}
&& it.neighbors.any {neighbor ->
neighbor.resource == null && neighbor.improvement == null
@ -472,9 +484,10 @@ class MapGenerator {
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(mapToReturn: TileMap, distance: Int, ruleset: Ruleset) {
private fun spreadStrategicResources(mapToReturn: TileMap, distance: Int) {
val resourcesOfType = ruleset.tileResources.values.filter { it.resourceType == ResourceType.Strategic }
for (resource in resourcesOfType) {
val suitableTiles = mapToReturn.values
@ -490,12 +503,12 @@ class MapGenerator {
}
// Here, we need there to be some luxury/bonus resource - it matters less what
private fun spreadResource(mapToReturn: TileMap, distance: Int, resourceType: ResourceType, ruleset: Ruleset) {
private fun spreadResource(mapToReturn: TileMap, distance: Int, resourceType: ResourceType) {
val resourcesOfType = ruleset.tileResources.values.filter { it.resourceType == resourceType }
val suitableTiles = mapToReturn.values
.filter { it.resource == null && resourcesOfType.any { r -> r.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) } }
val numberOfResources = mapToReturn.values.count { it.isLand && !it.getBaseTerrain().impassable } / 15
val numberOfResources = (mapToReturn.values.count { it.isLand && !it.getBaseTerrain().impassable } * mapToReturn.mapParameters.resourceRichness).toInt()
val locations = chooseSpreadOutLocations(numberOfResources, suitableTiles, distance)
val resourceToNumber = Counter<String>()
@ -511,7 +524,7 @@ class MapGenerator {
}
}
fun chooseSpreadOutLocations(numberOfResources: Int, suitableTiles: List<TileInfo>, initialDistance: Int): ArrayList<TileInfo> {
private fun chooseSpreadOutLocations(numberOfResources: Int, suitableTiles: List<TileInfo>, initialDistance: Int): ArrayList<TileInfo> {
for (distanceBetweenResources in initialDistance downTo 1) {
var availableTiles = suitableTiles.toList()
@ -542,29 +555,125 @@ class MapGenerator {
}
throw Exception("Couldn't choose suitable tiles for $numberOfResources resources!")
}
}
class MapLandmassGenerator {
companion object MapLandmassGenerator {
var RNG = Random(42)
fun generateLandCellularAutomata(tileMap: TileMap, mapRadius: Int, mapType: String) {
val numSmooth = 4
//init
for (tile in tileMap.values) {
val terrainType = getInitialTerrainCellularAutomata(tile, mapRadius, mapType)
if (terrainType == TerrainType.Land) tile.baseTerrain = Constants.grassland
else tile.baseTerrain = Constants.ocean
tile.setTransients()
fun generateLand(tileMap: TileMap) {
when (tileMap.mapParameters.type) {
MapType.pangaea -> createPangea(tileMap)
MapType.continents -> createTwoContinents(tileMap)
MapType.perlin -> createPerlin(tileMap)
MapType.default -> generateLandCellularAutomata(tileMap)
}
}
//smooth
val grassland = Constants.grassland
val ocean = Constants.ocean
for (loop in 0..numSmooth) {
private fun smooth(tileMap: TileMap) {
for (tileInfo in tileMap.values) {
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) < mapRadius) {
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)
when {
elevation < 0 -> tile.baseTerrain = Constants.ocean
else -> tile.baseTerrain = Constants.grassland
}
}
}
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
when {
elevation < 0 -> tile.baseTerrain = Constants.ocean
else -> tile.baseTerrain = Constants.grassland
}
}
}
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
when {
elevation < 0 -> tile.baseTerrain = Constants.ocean
else -> tile.baseTerrain = Constants.grassland
}
}
}
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 maxLongitude = abs(tileMap.values.map { abs(HexMath.getLongitude(it.position)) }.max()!!)
val longitudeFactor = abs(HexMath.getLongitude(tileInfo.position))/maxLongitude
return min(0.0,-1.0 + (5.0 * longitudeFactor.pow(0.7f) + 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))
}
}
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)
}
// 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)
@ -573,90 +682,36 @@ class MapLandmassGenerator {
if (numberOfLandNeighbors > 3)
tileInfo.baseTerrain = grassland
}
} else {
tileInfo.baseTerrain = ocean
}
}
if (mapType == MapType.continents) { //keep a ocean column in the middle
for (y in -mapRadius..mapRadius) {
tileMap.get(Vector2((y / 2).toFloat(), y.toFloat())).baseTerrain = ocean
tileMap.get(Vector2((y / 2 + 1).toFloat(), y.toFloat())).baseTerrain = ocean
/*} else {
tileInfo.baseTerrain = ocean
}*/
}
}
}
}
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapRadius: Int, mapType: String): TerrainType {
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapParameters: MapParameters): TerrainType {
val landProbability = mapParameters.landProbability
val mapRadius = mapParameters.size.radius
val landProbability = 0.55f
if (mapType == MapType.pangaea) {
val distanceFactor = (HexMath.getDistance(Vector2.Zero, tileInfo.position) * 1.8 / mapRadius).toFloat()
if (Random().nextDouble() < landProbability.pow(distanceFactor)) return TerrainType.Land
else return TerrainType.Water
}
if (mapType == MapType.continents) {
val distanceWeight = min(getDistanceWeightForContinents(Vector2(mapRadius.toFloat() / 2, 0f), tileInfo.position),
getDistanceWeightForContinents(Vector2(-mapRadius.toFloat() / 2, 0f), tileInfo.position))
val distanceFactor = (distanceWeight * 1.8 / mapRadius).toFloat()
if (Random().nextDouble() < landProbability.pow(distanceFactor)) return TerrainType.Land
else return TerrainType.Water
}
// default
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.9f * mapRadius) {
if (Random().nextDouble() < 0.1) return TerrainType.Land else return TerrainType.Water
}
if (HexMath.getDistance(Vector2.Zero, tileInfo.position) > 0.85f * mapRadius) {
if (Random().nextDouble() < 0.2) return TerrainType.Land else return TerrainType.Water
}
if (Random().nextDouble() < landProbability) return TerrainType.Land else return TerrainType.Water
}
private fun getDistanceWeightForContinents(origin: Vector2, destination: Vector2): Float {
val relative_x = 2 * (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))
else
return (abs(relative_x) + abs(relative_y))
}
/**
* This generator simply generates Perlin noise,
* "spreads" it out according to the ratio in generateTile,
* and assigns it as the height of the various tiles.
* Tiles below a certain height threshold (determined in generateTile, currently 50%)
* are considered water tiles, the rest are land tiles
*/
fun generateLandPerlin(tileMap: TileMap) {
val mapRandomSeed = Random().nextDouble() // without this, all the "random" maps would look the same
for (tile in tileMap.values) {
val ratio = 1 / 10.0
val vector = tile.position
val height = Perlin.noise(vector.x * ratio, vector.y * ratio, mapRandomSeed)
+Perlin.noise(vector.x * ratio * 2, vector.y * ratio * 2, mapRandomSeed) / 2
+Perlin.noise(vector.x * ratio * 4, vector.y * ratio * 4, mapRandomSeed) / 4
when { // If we want to change water levels, we could raise or lower the >0
height > 0 -> tile.baseTerrain = Constants.grassland
else -> tile.baseTerrain = Constants.ocean
// 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() < landProbability) return TerrainType.Land else return TerrainType.Water
}
}
// endregion
}
}
class Area(var terrain: String) {
val tiles = ArrayList<TileInfo>()
fun addTile(tileInfo: TileInfo) {
tiles+=tileInfo
tiles += tileInfo
tileInfo.baseTerrain = terrain
}
}

View File

@ -8,10 +8,41 @@ enum class MapSize(val radius: Int) {
Huge(40)
}
object MapShape {
const val hexagonal = "Hexagonal"
const val rectangular = "Rectangular"
}
object MapType {
const val pangaea = "Pangaea"
const val continents = "Continents"
const val perlin = "Perlin"
// Cellular automata
const val default = "Default"
// Non-generated maps
const val custom = "Custom"
// All ocean tiles
const val empty = "Empty"
}
class MapParameters {
var name = ""
var type = MapType.pangaea
var shape = MapShape.hexagonal
var size: MapSize = MapSize.Medium
var noRuins = false
var noNaturalWonders = true
var noNaturalWonders = false
var seed: Long = 0
var tilesPerBiomeArea = 6
var maxCoastExtension = 2
var mountainProbability = 0.10f
var temperatureExtremeness = 0.30f
var terrainFeatureRichness = 0.30f
var resourceRichness = 0.10f
var waterProbability = 0.05f
var landProbability = 0.55f
}

View File

@ -1,5 +1,6 @@
package com.unciv.logic.map
import kotlin.math.floor
// version 1.1.3
// From https://rosettacode.org/wiki/Perlin_noise#Kotlin
@ -25,20 +26,44 @@ object Perlin {
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
)
private val grad3 = arrayOf(
intArrayOf(1,1,0), intArrayOf(-1,1,0), intArrayOf(1,-1,0), intArrayOf(-1,-1,0),
intArrayOf(1,0,1), intArrayOf(-1,0,1), intArrayOf(1,0,-1), intArrayOf(-1,0,-1),
intArrayOf(0,1,1), intArrayOf(0,-1,1), intArrayOf(0,1,-1), intArrayOf(0,-1,-1),
intArrayOf(1,0,-1), intArrayOf(-1,0,-1), intArrayOf(0,-1,1), intArrayOf(0,1,1))
private val p = IntArray(512) {
if (it < 256) permutation[it] else permutation[it - 256]
}
fun noise3d(x: Double, y: Double, z: Double,
nOctaves: Int = 3,
persistence: Double = 0.5,
lacunarity: Double = 2.0,
scale: Double = 10.0): Double {
var freq = 1.0
var amp = 1.0
var max = 0.0
var total = 0.0
for (i in 0 until nOctaves) {
total += amp * noise(x * freq / scale, y * freq / scale, z * freq / scale)
max += amp
freq *= lacunarity
amp *= persistence
}
return total/max
}
fun noise(x: Double, y: Double, z: Double): Double {
// Find unit cube that contains point
val xi = Math.floor(x).toInt() and 255
val yi = Math.floor(y).toInt() and 255
val zi = Math.floor(z).toInt() and 255
val xi = floor(x).toInt() and 255
val yi = floor(y).toInt() and 255
val zi = floor(z).toInt() and 255
// Find relative x, y, z of point in cube
val xx = x - Math.floor(x)
val yy = y - Math.floor(y)
val zz = z - Math.floor(z)
val xx = x - floor(x)
val yy = y - floor(y)
val zz = z - floor(z)
// Compute fade curves for each of xx, yy, zz
val u = fade(xx)
@ -55,26 +80,22 @@ object Perlin {
val ba = p[b] + zi
val bb = p[b + 1] + zi
return lerp(w, lerp(v, lerp(u, grad(p[aa], xx, yy, zz),
grad(p[ba], xx - 1, yy, zz)),
lerp(u, grad(p[ab], xx, yy - 1, zz),
grad(p[bb], xx - 1, yy - 1, zz))),
lerp(v, lerp(u, grad(p[aa + 1], xx, yy, zz - 1),
grad(p[ba + 1], xx - 1, yy, zz - 1)),
lerp(u, grad(p[ab + 1], xx, yy - 1, zz - 1),
grad(p[bb + 1], xx - 1, yy - 1, zz - 1))))
return lerp(w, lerp(v, lerp(u, grad3(p[aa], xx, yy, zz),
grad3(p[ba], xx - 1, yy, zz)),
lerp(u, grad3(p[ab], xx, yy - 1, zz),
grad3(p[bb], xx - 1, yy - 1, zz))),
lerp(v, lerp(u, grad3(p[aa + 1], xx, yy, zz - 1),
grad3(p[ba + 1], xx - 1, yy, zz - 1)),
lerp(u, grad3(p[ab + 1], xx, yy - 1, zz - 1),
grad3(p[bb + 1], xx - 1, yy - 1, zz - 1))))
}
private fun fade(t: Double) = t * t * t * (t * (t * 6 - 15) + 10)
private fun lerp(t: Double, a: Double, b: Double) = a + t * (b - a)
private fun grad(hash: Int, x: Double, y: Double, z: Double): Double {
// Convert low 4 bits of hash code into 12 gradient directions
val h = hash and 15
val u = if (h < 8) x else y
val v = if (h < 4) y else if (h == 12 || h == 14) x else z
return (if ((h and 1) == 0) u else -u) +
(if ((h and 2) == 0) v else -v)
private fun grad3(hash: Int, x: Double, y: Double, z: Double): Double {
val h = hash and 15;
return x * grad3[h][0] + y * grad3[h][1] + z * grad3[h][2]
}
}

View File

@ -10,14 +10,14 @@ import com.unciv.models.ruleset.Ruleset
class TileMap {
@Transient lateinit var gameInfo: GameInfo
@Transient var tileMatrix=ArrayList<ArrayList<TileInfo?>>() // this works several times faster than a hashmap, the performance difference is really astounding
@Transient var leftX=0
@Transient var bottomY=0
@Transient var tileMatrix = ArrayList<ArrayList<TileInfo?>>() // this works several times faster than a hashmap, the performance difference is really astounding
@Transient var leftX = 0
@Transient var bottomY = 0
@Deprecated("as of 2.7.10")
private var tiles = HashMap<String, TileInfo>()
var mapParameters= MapParameters()
var mapParameters = MapParameters()
private var tileList = ArrayList<TileInfo>()
constructor() // for json parsing, we need to have a default constructor
@ -33,10 +33,20 @@ class TileMap {
get() = tileList
/** generates an hexagonal map of given radius */
constructor(radius:Int, ruleset: Ruleset){
for(vector in HexMath.getVectorsInDistance(Vector2.Zero, radius))
tileList.add(TileInfo().apply { position = vector; baseTerrain= Constants.grassland })
tileList.add(TileInfo().apply { position = vector; baseTerrain = Constants.grassland })
setTransients(ruleset)
}
/** generates a rectangular map of given width and height*/
constructor(width: Int, height: Int, ruleset: Ruleset) {
for(x in -width/2..width/2)
for (y in -height/2..height/2)
tileList.add(TileInfo().apply {
position = HexMath.evenQ2HexCoords(Vector2(x.toFloat(),y.toFloat()))
baseTerrain = Constants.grassland })
setTransients(ruleset)
}
@ -230,8 +240,5 @@ class TileMap {
tileInfo.setTransients()
}
}
}

View File

@ -46,7 +46,7 @@ class NewMapScreen : PickerScreen() {
try {
// Map generation can take a while and we don't want ANRs
val ruleset = RulesetCache.getBaseRuleset()
generatedMap = MapGenerator().generateMap(mapParameters, ruleset)
generatedMap = MapGenerator(ruleset).generateMap(mapParameters)
Gdx.app.postRunnable {
UncivGame.Current.setScreen(MapEditorScreen(generatedMap!!))

View File

@ -2,13 +2,17 @@ package com.unciv.ui.newgamescreen
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Slider
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapShape
import com.unciv.logic.map.MapSize
import com.unciv.logic.map.MapType
import com.unciv.models.translations.tr
import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.toLabel
/** Table for editing [mapParameters]
@ -17,21 +21,41 @@ import com.unciv.ui.utils.toLabel
*
* @param isEmptyMapAllowed whether the [MapType.empty] option should be present. Is used by the Map Editor, but should **never** be used with the New Game
* */
class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed: Boolean = false) :
class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed: Boolean = false):
Table() {
lateinit var mapTypeSelectBox: TranslatedSelectBox
lateinit var noRuinsCheckbox: CheckBox
lateinit var noNaturalWondersCheckbox: CheckBox
init {
skin = CameraStageBaseScreen.skin
defaults().pad(5f)
addMapShapeSelectBox()
addMapTypeSelectBox()
addWorldSizeSelectBox()
addNoRuinsCheckbox()
addNoNaturalWondersCheckbox()
addAdvancedSettings()
}
private fun addMapShapeSelectBox() {
val mapShapes = listOfNotNull(
MapShape.hexagonal,
MapShape.rectangular
)
val mapShapeSelectBox =
TranslatedSelectBox(mapShapes, mapParameters.shape, skin)
mapShapeSelectBox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.shape = mapShapeSelectBox.selected.value
}
})
add ("{Map shape}:".toLabel()).left()
add(mapShapeSelectBox).fillX().row()
}
private fun addMapTypeSelectBox() {
add("{Map generation type}:".toLabel())
val mapTypes = listOfNotNull(
MapType.default,
@ -40,8 +64,8 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
MapType.perlin,
if (isEmptyMapAllowed) MapType.empty else null
)
val mapTypeSelectBox =
TranslatedSelectBox(mapTypes, mapParameters.type, CameraStageBaseScreen.skin)
mapTypeSelectBox = TranslatedSelectBox(mapTypes, mapParameters.type, skin)
mapTypeSelectBox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
@ -52,17 +76,17 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
noNaturalWondersCheckbox.isVisible = mapParameters.type != MapType.empty
}
})
add(mapTypeSelectBox).row()
add("{Map generation type}:".toLabel()).left()
add(mapTypeSelectBox).fillX().row()
}
private fun addWorldSizeSelectBox() {
val worldSizeLabel = "{World size}:".toLabel()
val worldSizeSelectBox = TranslatedSelectBox(
MapSize.values().map { it.name },
mapParameters.size.name,
CameraStageBaseScreen.skin
skin
)
worldSizeSelectBox.addListener(object : ChangeListener() {
@ -71,12 +95,12 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
}
})
add(worldSizeLabel)
add(worldSizeSelectBox).pad(10f).row()
add("{World size}:".toLabel()).left()
add(worldSizeSelectBox).fillX().row()
}
private fun addNoRuinsCheckbox() {
noRuinsCheckbox = CheckBox("No ancient ruins".tr(), CameraStageBaseScreen.skin)
noRuinsCheckbox = CheckBox("No ancient ruins".tr(), skin)
noRuinsCheckbox.isChecked = mapParameters.noRuins
noRuinsCheckbox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
@ -87,7 +111,7 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
}
private fun addNoNaturalWondersCheckbox() {
noNaturalWondersCheckbox = CheckBox("No Natural Wonders".tr(), CameraStageBaseScreen.skin)
noNaturalWondersCheckbox = CheckBox("No Natural Wonders".tr(), skin)
noNaturalWondersCheckbox.isChecked = mapParameters.noNaturalWonders
noNaturalWondersCheckbox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
@ -96,4 +120,121 @@ class MapParametersTable(val mapParameters: MapParameters, val isEmptyMapAllowed
})
add(noNaturalWondersCheckbox).colspan(2).row()
}
private fun addAdvancedSettings() {
val button = TextButton("Show advanced settings".tr(), skin)
val advancedSettingsTable = Table().apply {isVisible = false; defaults().pad(5f)}
add(button).colspan(2).row()
val advancedSettingsCell = add(Table()).colspan(2)
row()
button.onClick {
advancedSettingsTable.isVisible = !advancedSettingsTable.isVisible
if (advancedSettingsTable.isVisible) {
button.setText("Hide advanced settings".tr())
advancedSettingsCell.setActor(advancedSettingsTable)
} else {
button.setText("Show advanced settings".tr())
advancedSettingsCell.setActor(Table())
}
}
val averageHeightSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.mountainProbability = this@apply.value
}
})
}
averageHeightSlider.value = mapParameters.mountainProbability
advancedSettingsTable.add("Map Height".toLabel()).left()
advancedSettingsTable.add(averageHeightSlider).fillX().row()
val tempExtremeSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.temperatureExtremeness = this@apply.value
}
})
}
tempExtremeSlider.value = mapParameters.temperatureExtremeness
advancedSettingsTable.add("Temperature extremeness".toLabel()).left()
advancedSettingsTable.add(tempExtremeSlider).fillX().row()
val resourceRichnessSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.resourceRichness = this@apply.value
}
})
}
resourceRichnessSlider.value = mapParameters.resourceRichness
advancedSettingsTable.add("Resource richness".toLabel()).left()
advancedSettingsTable.add(resourceRichnessSlider).fillX().row()
val terrainFeatureRichnessSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.terrainFeatureRichness = this@apply.value
}
})
}
terrainFeatureRichnessSlider.value = mapParameters.terrainFeatureRichness
advancedSettingsTable.add("Terrain Features richness".toLabel()).left()
advancedSettingsTable.add(terrainFeatureRichnessSlider).fillX().row()
val maxCoastExtensionSlider = Slider(0f,5f,1f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.maxCoastExtension = this@apply.value.toInt()
}
})
}
maxCoastExtensionSlider.value = mapParameters.maxCoastExtension.toFloat()
advancedSettingsTable.add("Max Coast extension".toLabel()).left()
advancedSettingsTable.add(maxCoastExtensionSlider).fillX().row()
val tilesPerBiomeAreaSlider = Slider(0f,15f,1f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.tilesPerBiomeArea = this@apply.value.toInt()
}
})
}
tilesPerBiomeAreaSlider.value = mapParameters.tilesPerBiomeArea.toFloat()
advancedSettingsTable.add("Biome areas extension".toLabel()).left()
advancedSettingsTable.add(tilesPerBiomeAreaSlider).fillX().row()
val waterPercentSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.waterProbability = this@apply.value
}
})
}
waterPercentSlider.value = mapParameters.waterProbability
advancedSettingsTable.add("Water percent".toLabel()).left()
advancedSettingsTable.add(waterPercentSlider).fillX().row()
val landPercentSlider = Slider(0f,1f,0.01f, false, skin).apply {
addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
mapParameters.landProbability = this@apply.value
}
})
}
landPercentSlider.value = mapParameters.landProbability
advancedSettingsTable.add("Land percent".toLabel()).left()
advancedSettingsTable.add(landPercentSlider).fillX().row()
}
}

View File

@ -32,7 +32,7 @@ class NewGameScreen: PickerScreen(){
val playerPickerTable = PlayerPickerTable(this, newGameParameters)
val newGameScreenOptionsTable = NewGameScreenOptionsTable(this) { playerPickerTable.update() }
topTable.add(ScrollPane(newGameScreenOptionsTable)).height(topTable.parent.height)
topTable.add(ScrollPane(newGameScreenOptionsTable).apply{setOverscroll(false,false)}).height(topTable.parent.height)
topTable.add(playerPickerTable).pad(10f)
topTable.pack()
topTable.setFillParent(true)
@ -90,6 +90,8 @@ class NewGameScreen: PickerScreen(){
cantMakeThatMapPopup.addCloseButton()
cantMakeThatMapPopup.open()
Gdx.input.inputProcessor = stage
rightSideButton.enable()
rightSideButton.setText("Start game!".tr())
}
}
}

View File

@ -1,12 +1,14 @@
package com.unciv.ui.newgamescreen
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.utils.Array
import com.unciv.logic.MapSaver
import com.unciv.logic.map.MapType
import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.VictoryType
@ -22,8 +24,14 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
val mapParameters = newGameScreen.mapParameters
val ruleset = newGameScreen.ruleset
private var mapTypeSpecificTable = Table()
private val generatedMapOptionsTable = MapParametersTable(mapParameters)
private val savedMapOptionsTable = Table()
init {
pad(10f)
top()
defaults().pad(5f)
add("Map options".toLabel(fontSize = 24)).colspan(2).row()
addMapTypeSelection()
@ -36,8 +44,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
addBarbariansCheckbox()
addOneCityChallengeCheckbox()
addIsOnlineMultiplayerCheckbox()
addModCheckboxes()
addModCheckboxes()
pack()
}
@ -45,33 +52,31 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
private fun addMapTypeSelection() {
add("{Map type}:".toLabel())
val mapTypes = arrayListOf("Generated")
if (MapSaver().getMaps().isNotEmpty()) mapTypes.add("Existing")
val mapFileLabel = "{Map file}:".toLabel()
val mapFileSelectBox = getMapFileSelectBox()
mapFileLabel.isVisible = false
mapFileSelectBox.isVisible = false
if (MapSaver().getMaps().isNotEmpty()) mapTypes.add(MapType.custom)
val mapTypeSelectBox = TranslatedSelectBox(mapTypes, "Generated", CameraStageBaseScreen.skin)
val mapParameterTable = MapParametersTable(mapParameters)
val mapFileSelectBox = getMapFileSelectBox()
savedMapOptionsTable.defaults().pad(5f)
savedMapOptionsTable.add("{Map file}:".toLabel()).left()
// because SOME people gotta give the hugest names to their maps
savedMapOptionsTable.add(mapFileSelectBox).maxWidth(newGameScreen.stage.width / 2)
.right().row()
fun updateOnMapTypeChange() {
mapParameters.type = mapTypeSelectBox.selected.value
if (mapParameters.type == "Existing") {
mapParameterTable.isVisible = false
mapFileSelectBox.isVisible = true
mapFileLabel.isVisible = true
mapTypeSpecificTable.clear()
if (mapTypeSelectBox.selected.value == MapType.custom) {
mapParameters.type = MapType.custom
mapParameters.name = mapFileSelectBox.selected
mapTypeSpecificTable.add(savedMapOptionsTable)
} else {
mapParameterTable.isVisible = true
mapFileSelectBox.isVisible = false
mapFileLabel.isVisible = false
mapParameters.name = ""
mapParameters.type = generatedMapOptionsTable.mapTypeSelectBox.selected.value
mapTypeSpecificTable.add(generatedMapOptionsTable)
}
}
updateOnMapTypeChange() // activate once, so when we had a file map before we'll have the right things set for another one
// activate once, so when we had a file map before we'll have the right things set for another one
updateOnMapTypeChange()
mapTypeSelectBox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
@ -79,12 +84,8 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
}
})
add(mapTypeSelectBox).pad(10f).row()
add(mapParameterTable).colspan(2).row()
add(mapFileLabel)
add(mapFileSelectBox).maxWidth(newGameScreen.stage.width / 2) // because SOME people gotta give the hugest names to their maps
.pad(10f).row()
add(mapTypeSelectBox).row()
add(mapTypeSpecificTable).colspan(2).row()
}
@ -147,7 +148,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
(0..ruleset.nations.filter { it.value.isCityState() }.size).forEach { cityStatesArray.add(it) }
cityStatesSelectBox.items = cityStatesArray
cityStatesSelectBox.selected = newGameParameters.numberOfCityStates
add(cityStatesSelectBox).pad(10f).row()
add(cityStatesSelectBox).row()
cityStatesSelectBox.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
newGameParameters.numberOfCityStates = cityStatesSelectBox.selected
@ -163,7 +164,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
newGameParameters.difficulty = difficultySelectBox.selected.value
}
})
add(difficultySelectBox).pad(10f).row()
add(difficultySelectBox).fillX().row()
}
private fun addGameSpeedSelectBox() {
@ -174,7 +175,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
newGameParameters.gameSpeed = GameSpeed.valueOf(gameSpeedSelectBox.selected.value)
}
})
add(gameSpeedSelectBox).pad(10f).row()
add(gameSpeedSelectBox).fillX().row()
}
private fun addEraSelectBox() {
@ -187,16 +188,16 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
newGameParameters.startingEra = TechEra.valueOf(eraSelectBox.selected.value.replace(" era", ""))
}
})
add(eraSelectBox).pad(10f).row()
add(eraSelectBox).fillX().row()
}
private fun addVictoryTypeCheckboxes() {
add("{Victory conditions}:".tr()).colspan(2).row()
add("{Victory conditions}:".toLabel()).colspan(2).row()
// Create a checkbox for each VictoryType existing
var i = 0
val victoryConditionsTable = Table().apply { defaults().pad(10f) }
val victoryConditionsTable = Table().apply { defaults().pad(5f) }
for (victoryType in VictoryType.values()) {
if (victoryType == VictoryType.Neutral) continue
val victoryCheckbox = CheckBox(victoryType.name.tr(), CameraStageBaseScreen.skin)
@ -212,7 +213,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
}
}
})
victoryConditionsTable.add(victoryCheckbox)
victoryConditionsTable.add(victoryCheckbox).left()
if (++i % 2 == 0) victoryConditionsTable.row()
}
add(victoryConditionsTable).colspan(2).row()
@ -233,7 +234,7 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
}
add("{Mods}:".tr()).colspan(2).row()
val modCheckboxTable = Table().apply { defaults().pad(10f) }
val modCheckboxTable = Table().apply { defaults().pad(5f) }
for(mod in modRulesets){
val checkBox = CheckBox(mod.name,CameraStageBaseScreen.skin)
checkBox.addListener(object : ChangeListener() {