Map generation simplified, rewritten, and now much MUCH more readable!
This commit is contained in:
parent
df4ca861c3
commit
2ec628a0da
10 changed files with 415 additions and 623 deletions
|
@ -23,7 +23,7 @@ class UnCivGame(val version: String) : Game() {
|
|||
* This exists so that when debugging we can see the entire map.
|
||||
* Remember to turn this to false before commit and upload!
|
||||
*/
|
||||
var viewEntireMapForDebug = true
|
||||
var viewEntireMapForDebug = false
|
||||
|
||||
/** For when you need to test something in an advanced game and don't have time to faff around */
|
||||
val superchargedForDebug = false
|
||||
|
|
|
@ -3,9 +3,7 @@ package com.unciv.logic
|
|||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.BFS
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.models.gamebasics.GameBasics
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import java.util.*
|
||||
|
@ -18,7 +16,13 @@ class GameStarter{
|
|||
val gameInfo = GameInfo()
|
||||
|
||||
gameInfo.gameParameters = newGameParameters
|
||||
gameInfo.tileMap = TileMap(newGameParameters)
|
||||
|
||||
if(newGameParameters.mapType==MapType.file)
|
||||
gameInfo.tileMap = MapSaver().loadMap(newGameParameters.mapFileName!!)
|
||||
else gameInfo.tileMap = MapGenerator().generateMap(newGameParameters)
|
||||
|
||||
gameInfo.tileMap.setTransients()
|
||||
|
||||
gameInfo.tileMap.gameInfo = gameInfo // need to set this transient before placing units in the map
|
||||
gameInfo.difficulty = newGameParameters.difficulty
|
||||
|
||||
|
@ -74,7 +78,7 @@ class GameStarter{
|
|||
.map { it.improvement!!.replace("StartingLocation ", "") }
|
||||
|
||||
val availableCityStatesNames = Stack<String>()
|
||||
// since we shuffle and then order by, we end up with all the city states with starting locations first in a random order,
|
||||
// since we shuffle and then order by, we end up with all the city states with starting tiles first in a random order,
|
||||
// and then all the other city states in a random order! Because the sortedBy function is stable!
|
||||
availableCityStatesNames.addAll(GameBasics.Nations.filter { it.value.isCityState() }.keys
|
||||
.shuffled().sortedByDescending { it in cityStatesWithStartingLocations })
|
||||
|
@ -154,7 +158,7 @@ class GameStarter{
|
|||
val presetStartingLocation = tilesWithStartingLocations.firstOrNull { it.improvement=="StartingLocation "+civ.civName }
|
||||
if(presetStartingLocation!=null) startingLocation = presetStartingLocation
|
||||
else {
|
||||
if (freeTiles.isEmpty()) break // we failed to get all the starting locations with this minimum distance
|
||||
if (freeTiles.isEmpty()) break // we failed to get all the starting tiles with this minimum distance
|
||||
var preferredTiles = freeTiles.toList()
|
||||
|
||||
for (startBias in civ.nation.startBias) {
|
||||
|
@ -175,7 +179,7 @@ class GameStarter{
|
|||
for(tile in tilesWithStartingLocations) tile.improvement=null // get rid of the starting location improvements
|
||||
return startingLocations
|
||||
}
|
||||
throw Exception("Didn't manage to get starting locations even with distance of 1?")
|
||||
throw Exception("Didn't manage to get starting tiles even with distance of 1?")
|
||||
}
|
||||
|
||||
private fun vectorIsAtLeastNTilesAwayFromEdge(vector: Vector2, n:Int, tileMap: TileMap): Boolean {
|
||||
|
|
|
@ -23,7 +23,7 @@ interface NotificationAction {
|
|||
fun execute(worldScreen: WorldScreen)
|
||||
}
|
||||
|
||||
/** cycle through locations */
|
||||
/** cycle through tiles */
|
||||
data class LocationAction(var locations: ArrayList<Vector2> = ArrayList()) : NotificationAction {
|
||||
|
||||
constructor(locations: List<Vector2>): this(ArrayList(locations))
|
||||
|
@ -31,7 +31,7 @@ data class LocationAction(var locations: ArrayList<Vector2> = ArrayList()) : Not
|
|||
override fun execute(worldScreen: WorldScreen) {
|
||||
if (locations.isNotEmpty()) {
|
||||
var index = locations.indexOf(worldScreen.tileMapHolder.selectedTile?.position)
|
||||
index = ++index % locations.size // cycle through locations
|
||||
index = ++index % locations.size // cycle through tiles
|
||||
worldScreen.tileMapHolder.setCenterPosition(locations[index], selectUnit = false)
|
||||
}
|
||||
}
|
||||
|
|
389
core/src/com/unciv/logic/map/MapGenerator.kt
Normal file
389
core/src/com/unciv/logic/map/MapGenerator.kt
Normal file
|
@ -0,0 +1,389 @@
|
|||
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.gamebasics.GameBasics
|
||||
import com.unciv.models.gamebasics.tile.ResourceType
|
||||
import com.unciv.models.gamebasics.tile.TerrainType
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
|
||||
// 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 file = "File"
|
||||
}
|
||||
}
|
||||
|
||||
class MapGenerator() {
|
||||
|
||||
fun generateMap(gameParameters: GameParameters): TileMap {
|
||||
val mapRadius = gameParameters.mapRadius
|
||||
val mapType = gameParameters.mapType
|
||||
|
||||
val map = TileMap(mapRadius)
|
||||
|
||||
// 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)
|
||||
|
||||
for(tile in map.values) tile.setTransients()
|
||||
|
||||
setWaterTiles(map)
|
||||
|
||||
for(tile in map.values) randomizeTile(tile)
|
||||
|
||||
randomizeResources(map, mapRadius)
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun setWaterTiles(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()
|
||||
tilesInArea += initialWaterTile
|
||||
tilesToCheck += initialWaterTile
|
||||
waterTiles -= initialWaterTile
|
||||
|
||||
while (tilesToCheck.isNotEmpty()) {
|
||||
val tileWeAreChecking = tilesToCheck.random()
|
||||
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 }) {
|
||||
if (tile.getTilesInDistance(2).any { it.isLand }) {
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun randomizeTile(tileInfo: TileInfo){
|
||||
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
||||
tileInfo.baseTerrain = Constants.mountain
|
||||
tileInfo.setTransients()
|
||||
}
|
||||
addRandomTerrainFeature(tileInfo)
|
||||
maybeAddAncientRuins(tileInfo)
|
||||
}
|
||||
|
||||
fun getLatitude(vector: Vector2): Float {
|
||||
return (sin(3.1416/3) * vector.y).toFloat()
|
||||
}
|
||||
|
||||
fun divideIntoBiomes(map: TileMap, averageTilesPerArea: Int, waterPercent: Float, distance: Int) {
|
||||
val areas = ArrayList<Area>()
|
||||
|
||||
val terrains = GameBasics.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
|
||||
|
||||
while(map.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 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()
|
||||
|
||||
//change grassland to desert or tundra based on y
|
||||
if (abs(getLatitude(tile.position)) < maxLatitude * 0.1) {
|
||||
if (terrain == Constants.grassland || terrain == 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) {
|
||||
terrain = Constants.tundra
|
||||
}
|
||||
} else {
|
||||
if (terrain == Constants.tundra) terrain = Constants.plains
|
||||
else if (terrain == Constants.desert) terrain = Constants.grassland
|
||||
}
|
||||
|
||||
val area = Area(terrain)
|
||||
emptyTiles -= tile
|
||||
area.addTile(tile)
|
||||
areas += area
|
||||
}
|
||||
|
||||
expandAreas(areas)
|
||||
expandAreas(areas)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun expandAreas(areas: ArrayList<Area>) {
|
||||
val expandableAreas = ArrayList<Area>(areas)
|
||||
|
||||
while (expandableAreas.isNotEmpty()) {
|
||||
val areaToExpand = expandableAreas.random()
|
||||
if(areaToExpand.tiles.size>=20){
|
||||
expandableAreas -= areaToExpand
|
||||
continue
|
||||
}
|
||||
|
||||
val availableExpansionTiles = areaToExpand.tiles
|
||||
.flatMap { it.neighbors }.distinct()
|
||||
.filter { it.baseTerrain=="" }
|
||||
|
||||
if (availableExpansionTiles.isEmpty()) expandableAreas -= areaToExpand
|
||||
else {
|
||||
val expansionTile = availableExpansionTiles.random()
|
||||
areaToExpand.addTile(expansionTile)
|
||||
|
||||
val areasToJoin = areas.filter {
|
||||
it.terrain == areaToExpand.terrain
|
||||
&& it != areaToExpand
|
||||
&& it.tiles.any { tile -> tile in expansionTile.neighbors }
|
||||
}
|
||||
for (area in areasToJoin) {
|
||||
areaToExpand.tiles += area.tiles
|
||||
areas.remove(area)
|
||||
expandableAreas.remove(area)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun addRandomTerrainFeature(tileInfo: TileInfo) {
|
||||
if (tileInfo.getBaseTerrain().canHaveOverlay && Math.random() > 0.7f) {
|
||||
val secondaryTerrains = GameBasics.Terrains.values
|
||||
.filter { it.type === TerrainType.TerrainFeature && it.occursOn!!.contains(tileInfo.baseTerrain) }
|
||||
if (secondaryTerrains.any()) tileInfo.terrainFeature = secondaryTerrains.random().name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun maybeAddAncientRuins(tile: TileInfo) {
|
||||
val baseTerrain = tile.getBaseTerrain()
|
||||
if(baseTerrain.type!=TerrainType.Water && !baseTerrain.impassable && Random().nextDouble() < 1f/100)
|
||||
tile.improvement = Constants.ancientRuins
|
||||
}
|
||||
|
||||
|
||||
fun randomizeResources(mapToReturn: TileMap, distance: Int) {
|
||||
for(tile in mapToReturn.values)
|
||||
if(tile.resource!=null)
|
||||
tile.resource=null
|
||||
|
||||
randomizeStrategicResources(mapToReturn, distance)
|
||||
randomizeResource(mapToReturn, distance, ResourceType.Luxury)
|
||||
randomizeResource(mapToReturn, 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 randomizeStrategicResources(mapToReturn: TileMap, distance: Int) {
|
||||
val resourcesOfType = GameBasics.TileResources.values.filter { it.resourceType == ResourceType.Strategic }
|
||||
for (resource in resourcesOfType) {
|
||||
val suitableTiles = mapToReturn.values
|
||||
.filter { it.resource == null && resource.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) }
|
||||
|
||||
val averageTilesPerResource = 15 * resourcesOfType.count()
|
||||
val numberOfResources = mapToReturn.values.count { it.isLand && !it.getBaseTerrain().impassable } / averageTilesPerResource
|
||||
|
||||
val locations = chooseSpreadOutLocations(numberOfResources, suitableTiles, distance)
|
||||
|
||||
for (location in locations) location.resource = resource.name
|
||||
}
|
||||
}
|
||||
|
||||
// Here, we need there to be some luxury/bonus resource - it matters less what
|
||||
private fun randomizeResource(mapToReturn: TileMap, distance: Int, resourceType: ResourceType) {
|
||||
val resourcesOfType = GameBasics.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 locations = chooseSpreadOutLocations(numberOfResources, 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
|
||||
}
|
||||
}
|
||||
|
||||
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>()
|
||||
|
||||
for(i in 1..numberOfResources){
|
||||
if(availableTiles.isEmpty()) break
|
||||
val chosenTile = availableTiles.random()
|
||||
availableTiles = availableTiles.filter { it.arialDistanceTo(chosenTile)>distanceBetweenResources }
|
||||
chosenTiles.add(chosenTile)
|
||||
}
|
||||
// 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!")
|
||||
}
|
||||
}
|
||||
|
||||
class MapLandmassGenerator(){
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getInitialTerrainCellularAutomata(tileInfo: TileInfo, mapRadius: Int, mapType: String):TerrainType {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Area(var terrain: String) {
|
||||
val tiles = ArrayList<TileInfo>()
|
||||
fun addTile(tileInfo: TileInfo) {
|
||||
tiles+=tileInfo
|
||||
tileInfo.baseTerrain = terrain
|
||||
}
|
||||
}
|
||||
|
|
@ -1,589 +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.gamebasics.GameBasics
|
||||
import com.unciv.models.gamebasics.tile.ResourceType
|
||||
import com.unciv.models.gamebasics.tile.TerrainType
|
||||
import com.unciv.models.gamebasics.tile.TileResource
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.math.*
|
||||
|
||||
enum class MapType {
|
||||
Perlin,
|
||||
Default,
|
||||
Continents,
|
||||
Pangaea,
|
||||
File
|
||||
}
|
||||
|
||||
|
||||
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.Pangaea && mapType !=MapType.Continents) {
|
||||
mapType = MapType.Default
|
||||
}
|
||||
}
|
||||
|
||||
override fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
||||
val mapVectors = HexMath().getVectorsInDistance(Vector2.Zero, distance)
|
||||
val landscape = HashMap<Vector2, TerrainType>()
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
||||
if (mapType == MapType.Continents) { //keep a ocean column in the middle
|
||||
for (y in -distance..distance) {
|
||||
landscape[Vector2((y/2).toFloat(), y.toFloat())] = TerrainType.Water
|
||||
landscape[Vector2((y/2+1).toFloat(), y.toFloat())] = TerrainType.Water
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val map = HashMap<Vector2, TileInfo>()
|
||||
for (vector in mapVectors)
|
||||
map[vector] = generateTile(vector,landscape[vector]!!)
|
||||
|
||||
divideIntoAreas2(6, 0.05f, distance, map)
|
||||
|
||||
val mapToReturn = HashMap<String, TileInfo>()
|
||||
for(tile in map) {
|
||||
tile.value.setTransients()
|
||||
mapToReturn[tile.key.toString()] = tile.value
|
||||
}
|
||||
|
||||
setWaterTiles(mapToReturn)
|
||||
|
||||
for(tile in mapToReturn.values) randomizeTile(tile,mapToReturn)
|
||||
|
||||
randomizeResources(mapToReturn,distance)
|
||||
|
||||
return mapToReturn
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
private fun generateInitTerrain(vector: Vector2, distance: Int): TerrainType {
|
||||
val 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 if (mapType == MapType.Continents) {
|
||||
val distanceWeight = min(getDistanceWeightForContinents(Vector2(distance.toFloat()/2, 0f), vector),
|
||||
getDistanceWeightForContinents(Vector2(-distance.toFloat()/2, 0f), vector))
|
||||
val distanceFactor = (distanceWeight * 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 = Constants.ocean
|
||||
return tile
|
||||
}
|
||||
|
||||
override fun setWaterTiles(map: HashMap<String, TileInfo>) {
|
||||
//define lakes
|
||||
var waterTiles = map.values.filter { it.isWater }.map { it.position }
|
||||
val tilesInArea = ArrayList<Vector2>()
|
||||
val tilesToCheck = ArrayList<Vector2>()
|
||||
while (waterTiles.isNotEmpty()) {
|
||||
val initialWaterTile = waterTiles.random()
|
||||
tilesInArea += initialWaterTile
|
||||
tilesToCheck += initialWaterTile
|
||||
waterTiles -= initialWaterTile
|
||||
|
||||
while (tilesToCheck.isNotEmpty()) {
|
||||
val tileChecking = tilesToCheck.random()
|
||||
for (vector in HexMath().getVectorsAtDistance(tileChecking,1)
|
||||
.filter { !tilesInArea.contains(it) and waterTiles.contains(it) }) {
|
||||
tilesInArea += vector
|
||||
tilesToCheck += vector
|
||||
waterTiles -= vector
|
||||
}
|
||||
tilesToCheck -= tileChecking
|
||||
}
|
||||
|
||||
if (tilesInArea.size <= 10) {
|
||||
for (vector in tilesInArea) {
|
||||
val tile = map[vector.toString()]!!
|
||||
tile.baseTerrain = Constants.lakes
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
tilesInArea.clear()
|
||||
}
|
||||
|
||||
//Coasts
|
||||
for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) {
|
||||
if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) {
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun randomizeTile(tileInfo: TileInfo, map: HashMap<String, TileInfo>){
|
||||
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
||||
tileInfo.baseTerrain = Constants.mountain
|
||||
tileInfo.setTransients()
|
||||
}
|
||||
addRandomTerrainFeature(tileInfo)
|
||||
addRandomResourceToTile(tileInfo)
|
||||
maybeAddAncientRuins(tileInfo)
|
||||
}
|
||||
|
||||
fun getLatitude(vector: Vector2): Float {
|
||||
return (sin(3.1416/3) * vector.y).toFloat()
|
||||
}
|
||||
|
||||
fun divideIntoAreas2(averageTilesPerArea: Int, waterPercent: Float, distance: Int, map: HashMap<Vector2, TileInfo>) {
|
||||
val areas = ArrayList<Area>()
|
||||
|
||||
val terrains = GameBasics.Terrains.values.filter { it.type === TerrainType.Land && it.name != Constants.lakes
|
||||
&& it.name != Constants.mountain}
|
||||
|
||||
while(map.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 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()
|
||||
|
||||
//change grassland to desert or tundra based on y
|
||||
if (abs(getLatitude(tile.position)) < maxLatitude * 0.1) {
|
||||
if (terrain == Constants.grassland || terrain == 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) {
|
||||
terrain = Constants.tundra
|
||||
}
|
||||
} else {
|
||||
if (terrain == Constants.tundra) terrain = Constants.plains
|
||||
else if (terrain == Constants.desert) terrain = Constants.grassland
|
||||
}
|
||||
|
||||
val area = Area(terrain)
|
||||
emptyTiles -= tile
|
||||
area.addTile(tile)
|
||||
areas += area
|
||||
}
|
||||
|
||||
expandAreas(areas, map)
|
||||
expandAreas(areas, map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class PerlinNoiseRandomMapGenerator:SeedRandomMapGenerator(){
|
||||
override fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
||||
val map = HashMap<Vector2, TileInfo>()
|
||||
val mapRandomSeed = Random().nextDouble() // without this, all the "random" maps would look the same
|
||||
for (vector in HexMath().getVectorsInDistance(Vector2.Zero, distance))
|
||||
map[vector] = generateTile(vector,mapRandomSeed)
|
||||
|
||||
divideIntoAreas(6, 0f, map)
|
||||
|
||||
val mapToReturn = HashMap<String, TileInfo>()
|
||||
for(tile in map) {
|
||||
tile.value.setTransients()
|
||||
mapToReturn[tile.key.toString()] = tile.value
|
||||
}
|
||||
|
||||
setWaterTiles(mapToReturn)
|
||||
|
||||
for(tile in mapToReturn.values) randomizeTile(tile,mapToReturn)
|
||||
|
||||
randomizeResources(mapToReturn,distance)
|
||||
|
||||
return mapToReturn
|
||||
}
|
||||
|
||||
private fun generateTile(vector: Vector2, mapRandomSeed: Double): TileInfo {
|
||||
val tile=TileInfo()
|
||||
tile.position=vector
|
||||
val ratio = 1/10.0
|
||||
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 {
|
||||
height>0.8 -> tile.baseTerrain = Constants.mountain
|
||||
height>0 -> tile.baseTerrain = "" // we'll leave this to the area division
|
||||
else -> tile.baseTerrain = Constants.ocean
|
||||
}
|
||||
return tile
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This generator uses the algorithm from the game "Alexander", outlined here:
|
||||
* http://www.cartania.com/alexander/generation.html
|
||||
*/
|
||||
class AlexanderRandomMapGenerator:RandomMapGenerator(){
|
||||
fun generateMap(distance: Int, landExpansionChance:Float): HashMap<String, TileInfo> {
|
||||
val map = HashMap<Vector2, TileInfo?>()
|
||||
|
||||
for (vector in HexMath().getVectorsInDistance(Vector2.Zero, distance))
|
||||
map[vector] = null
|
||||
|
||||
val sparkList = ArrayList<Vector2>()
|
||||
for(i in 0..distance*distance/6){
|
||||
val location = map.filter { it.value==null }.map { it.key }.random()
|
||||
map[location] = TileInfo().apply { baseTerrain= Constants.grassland}
|
||||
sparkList.add(location)
|
||||
}
|
||||
|
||||
while(sparkList.any()){
|
||||
val currentSpark = sparkList.random()
|
||||
val emptyTilesAroundSpark = HexMath().getAdjacentVectors(currentSpark)
|
||||
.filter { map.containsKey(it) && map[it]==null }
|
||||
if(map[currentSpark]!!.baseTerrain==Constants.grassland){
|
||||
for(tile in emptyTilesAroundSpark){
|
||||
if(Math.random()<landExpansionChance) map[tile]=TileInfo().apply { baseTerrain=Constants.grassland }
|
||||
else map[tile]=TileInfo().apply { baseTerrain=Constants.ocean }
|
||||
}
|
||||
}
|
||||
else{
|
||||
for(tile in emptyTilesAroundSpark)
|
||||
map[tile]=TileInfo().apply { baseTerrain=Constants.ocean }
|
||||
}
|
||||
sparkList.remove(currentSpark)
|
||||
sparkList.addAll(emptyTilesAroundSpark)
|
||||
}
|
||||
|
||||
val newmap = HashMap<String,TileInfo>()
|
||||
for(entry in map){
|
||||
entry.value!!.position = entry.key
|
||||
if(entry.value!!.baseTerrain==Constants.ocean
|
||||
&& HexMath().getAdjacentVectors(entry.key).all { !map.containsKey(it) || map[it]!!.baseTerrain==Constants.grassland })
|
||||
entry.value!!.baseTerrain=Constants.grassland
|
||||
|
||||
newmap[entry.key.toString()] = entry.value!!
|
||||
}
|
||||
|
||||
setWaterTiles(newmap)
|
||||
|
||||
return newmap
|
||||
// now that we've divided them into land and not-land, stage 2 - seeding areas the way we did with the seed generator!
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Area(var terrain: String) {
|
||||
val locations = ArrayList<Vector2>()
|
||||
fun addTile(tileInfo: TileInfo) {
|
||||
locations+=tileInfo.position
|
||||
tileInfo.baseTerrain = terrain
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This generator works by creating a number of seeds of different terrain types in random places,
|
||||
* and choosing a random one each time to expand in a random direction, until the map is filled.
|
||||
* With water, this creates canal-like structures.
|
||||
*/
|
||||
open class SeedRandomMapGenerator : RandomMapGenerator() {
|
||||
|
||||
fun generateMap(distance: Int, waterPercent:Float): HashMap<String, TileInfo> {
|
||||
|
||||
val map = HashMap<Vector2, TileInfo>()
|
||||
|
||||
for (vector in HexMath().getVectorsInDistance(Vector2.Zero, distance))
|
||||
map[vector] = TileInfo().apply { position=vector; baseTerrain="" }
|
||||
|
||||
|
||||
divideIntoAreas(6, waterPercent, map)
|
||||
|
||||
val mapToReturn = HashMap<String,TileInfo>()
|
||||
|
||||
for (entry in map) mapToReturn[entry.key.toString()] = entry.value
|
||||
for (entry in map) randomizeTile(entry.value, mapToReturn)
|
||||
|
||||
setWaterTiles(mapToReturn)
|
||||
randomizeResources(mapToReturn,distance)
|
||||
return mapToReturn
|
||||
}
|
||||
|
||||
open fun divideIntoAreas(averageTilesPerArea: Int, waterPercent: Float, map: HashMap<Vector2, TileInfo>) {
|
||||
val areas = ArrayList<Area>()
|
||||
|
||||
val terrains = GameBasics.Terrains.values
|
||||
.filter { it.type === TerrainType.Land && it.name != Constants.lakes && it.name != Constants.mountain }
|
||||
|
||||
while(map.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 numberOfSeeds = ceil(emptyTiles.size / averageTilesPerArea.toFloat()).toInt()
|
||||
|
||||
for (i in 0 until numberOfSeeds) {
|
||||
val terrain = if (Math.random() > waterPercent) terrains.random().name
|
||||
else Constants.ocean
|
||||
val area = Area(terrain)
|
||||
val tile = emptyTiles.random()
|
||||
emptyTiles -= tile
|
||||
area.addTile(tile)
|
||||
areas += area
|
||||
}
|
||||
|
||||
expandAreas(areas, map)
|
||||
expandAreas(areas, map)
|
||||
}
|
||||
|
||||
|
||||
for (area in areas.filter { it.terrain == Constants.ocean && it.locations.size <= 10 }) {
|
||||
// areas with 10 or less tiles are lakes.
|
||||
for (location in area.locations)
|
||||
map[location]!!.baseTerrain = Constants.lakes
|
||||
}
|
||||
}
|
||||
|
||||
fun expandAreas(areas: ArrayList<Area>, map: HashMap<Vector2, TileInfo>) {
|
||||
val expandableAreas = ArrayList<Area>(areas)
|
||||
while (expandableAreas.isNotEmpty()) {
|
||||
val areaToExpand = expandableAreas.random()
|
||||
if(areaToExpand.locations.size>=20){
|
||||
expandableAreas -= areaToExpand
|
||||
continue
|
||||
}
|
||||
val availableExpansionVectors = areaToExpand.locations
|
||||
.flatMap { HexMath().getAdjacentVectors(it) }.asSequence().distinct()
|
||||
.filter { map.containsKey(it) && map[it]!!.baseTerrain=="" }.toList()
|
||||
if (availableExpansionVectors.isEmpty()) expandableAreas -= areaToExpand
|
||||
else {
|
||||
val expansionVector = availableExpansionVectors.random()
|
||||
areaToExpand.addTile(map[expansionVector]!!)
|
||||
|
||||
val neighbors = HexMath().getAdjacentVectors(expansionVector)
|
||||
val areasToJoin = areas.filter {
|
||||
it.terrain == areaToExpand.terrain
|
||||
&& it != areaToExpand
|
||||
&& it.locations.any { location -> location in neighbors }
|
||||
}
|
||||
for (area in areasToJoin) {
|
||||
areaToExpand.locations += area.locations
|
||||
areas.remove(area)
|
||||
expandableAreas.remove(area)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This contains the basic randomizing tasks (add random terrain feature/resource)
|
||||
* and a basic map generator where every single tile is individually randomized.
|
||||
* Doesn't look very good TBH.
|
||||
*/
|
||||
open class RandomMapGenerator {
|
||||
|
||||
private fun addRandomTile(position: Vector2): TileInfo {
|
||||
val tileInfo = TileInfo()
|
||||
tileInfo.position = position
|
||||
val terrains = GameBasics.Terrains.values
|
||||
|
||||
val baseTerrain = terrains.filter { it.type === TerrainType.Land }.random()
|
||||
tileInfo.baseTerrain = baseTerrain.name
|
||||
|
||||
addRandomTerrainFeature(tileInfo)
|
||||
addRandomResourceToTile(tileInfo)
|
||||
|
||||
return tileInfo
|
||||
}
|
||||
|
||||
fun addRandomTerrainFeature(tileInfo: TileInfo) {
|
||||
if (tileInfo.getBaseTerrain().canHaveOverlay && Math.random() > 0.7f) {
|
||||
val secondaryTerrains = GameBasics.Terrains.values
|
||||
.filter { it.type === TerrainType.TerrainFeature && it.occursOn!!.contains(tileInfo.baseTerrain) }
|
||||
if (secondaryTerrains.any()) tileInfo.terrainFeature = secondaryTerrains.random().name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun addRandomResourceToTile(tileInfo: TileInfo) {
|
||||
|
||||
var tileResources = GameBasics.TileResources.values.toList()
|
||||
|
||||
// Resources are placed according to TerrainFeature, if exists, otherwise according to BaseLayer.
|
||||
tileResources = tileResources.filter { it.terrainsCanBeFoundOn.contains(tileInfo.getLastTerrain().name) }
|
||||
|
||||
var resource: TileResource? = null
|
||||
when {
|
||||
Math.random() < 1 / 15f -> resource = getRandomResource(tileResources, ResourceType.Bonus)
|
||||
Math.random() < 1 / 15f -> resource = getRandomResource(tileResources, ResourceType.Strategic)
|
||||
Math.random() < 1 / 15f -> resource = getRandomResource(tileResources, ResourceType.Luxury)
|
||||
}
|
||||
if (resource != null) tileInfo.resource = resource.name
|
||||
}
|
||||
|
||||
private fun getRandomResource(resources: List<TileResource>, resourceType: ResourceType): TileResource? {
|
||||
val filtered = resources.filter { it.resourceType == resourceType }
|
||||
if (filtered.isEmpty()) return null
|
||||
else return filtered.random()
|
||||
}
|
||||
|
||||
open fun generateMap(distance: Int): HashMap<String, TileInfo> {
|
||||
val map = HashMap<String, TileInfo>()
|
||||
for (vector in HexMath().getVectorsInDistance(Vector2.Zero, distance))
|
||||
map[vector.toString()] = addRandomTile(vector)
|
||||
return map
|
||||
}
|
||||
|
||||
fun maybeAddAncientRuins(tile: TileInfo) {
|
||||
val baseTerrain = tile.getBaseTerrain()
|
||||
if(baseTerrain.type!=TerrainType.Water && !baseTerrain.impassable && Random().nextDouble() < 1f/100)
|
||||
tile.improvement = Constants.ancientRuins
|
||||
}
|
||||
|
||||
|
||||
fun hasLandTile(map: HashMap<String, TileInfo>, vector: Vector2): Boolean {
|
||||
return map.containsKey(vector.toString()) && map[vector.toString()]!!.getBaseTerrain().type == TerrainType.Land
|
||||
}
|
||||
|
||||
open fun setWaterTiles(map: HashMap<String, TileInfo>) {
|
||||
for (tile in map.values.filter { it.baseTerrain == Constants.ocean }) {
|
||||
if (HexMath().getVectorsInDistance(tile.position,2).any { hasLandTile(map,it) }) {
|
||||
tile.baseTerrain = Constants.coast
|
||||
tile.setTransients()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun randomizeTile(tileInfo: TileInfo, map: HashMap<String, TileInfo>){
|
||||
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f){
|
||||
tileInfo.baseTerrain = Constants.mountain
|
||||
tileInfo.setTransients()
|
||||
}
|
||||
if(tileInfo.getBaseTerrain().type==TerrainType.Land && Math.random()<0.05f
|
||||
&& HexMath().getVectorsInDistance(tileInfo.position,1).all { hasLandTile(map,it) }){
|
||||
tileInfo.baseTerrain = Constants.lakes
|
||||
tileInfo.setTransients()
|
||||
}
|
||||
addRandomTerrainFeature(tileInfo)
|
||||
addRandomResourceToTile(tileInfo)
|
||||
maybeAddAncientRuins(tileInfo)
|
||||
}
|
||||
|
||||
|
||||
fun randomizeResources(mapToReturn: HashMap<String, TileInfo>, distance: Int) {
|
||||
for(tile in mapToReturn.values)
|
||||
if(tile.resource!=null)
|
||||
tile.resource=null
|
||||
|
||||
randomizeStrategicResources(mapToReturn, distance, ResourceType.Strategic)
|
||||
randomizeResource(mapToReturn, distance, ResourceType.Luxury)
|
||||
randomizeResource(mapToReturn, 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 randomizeStrategicResources(mapToReturn: HashMap<String, TileInfo>, distance: Int, resourceType: ResourceType) {
|
||||
val resourcesOfType = GameBasics.TileResources.values.filter { it.resourceType == resourceType }
|
||||
for (resource in resourcesOfType) {
|
||||
val suitableTiles = mapToReturn.values
|
||||
.filter { it.resource == null && resource.terrainsCanBeFoundOn.contains(it.getLastTerrain().name) }
|
||||
|
||||
val averageTilesPerResource = 15 * resourcesOfType.count()
|
||||
val numberOfResources = mapToReturn.values.count { it.isLand && !it.getBaseTerrain().impassable } / averageTilesPerResource
|
||||
|
||||
val locations = chooseSpreadOutLocations(numberOfResources, suitableTiles, distance)
|
||||
|
||||
for (location in locations) location.resource = resource.name
|
||||
}
|
||||
}
|
||||
|
||||
// Here, we need there to be some luxury/bonus resource - it matters less what
|
||||
private fun randomizeResource(mapToReturn: HashMap<String, TileInfo>, distance: Int, resourceType: ResourceType) {
|
||||
val resourcesOfType = GameBasics.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 locations = chooseSpreadOutLocations(numberOfResources, 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
|
||||
}
|
||||
}
|
||||
|
||||
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>()
|
||||
|
||||
for(i in 1..numberOfResources){
|
||||
if(availableTiles.isEmpty()) break
|
||||
val chosenTile = availableTiles.random()
|
||||
availableTiles = availableTiles.filter { it.arialDistanceTo(chosenTile)>distanceBetweenResources }
|
||||
chosenTiles.add(chosenTile)
|
||||
}
|
||||
if(chosenTiles.size == numberOfResources) return chosenTiles
|
||||
}
|
||||
throw Exception("ArgleBargle")
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
package com.unciv.logic.map
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.HexMath
|
||||
import com.unciv.logic.MapSaver
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.models.gamebasics.GameBasics
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
|
||||
class TileMap {
|
||||
|
||||
|
@ -32,18 +31,10 @@ class TileMap {
|
|||
get() = tileList
|
||||
|
||||
|
||||
constructor(newGameParameters: GameParameters) {
|
||||
val mapValues:Collection<TileInfo>
|
||||
|
||||
if(newGameParameters.mapType == MapType.File)
|
||||
mapValues = MapSaver().loadMap(newGameParameters.mapFileName!!).values
|
||||
else if(newGameParameters.mapType==MapType.Perlin)
|
||||
mapValues = PerlinNoiseRandomMapGenerator().generateMap(newGameParameters.mapRadius).values
|
||||
else
|
||||
mapValues = CelluarAutomataRandomMapGenerator(newGameParameters.mapType).generateMap(newGameParameters.mapRadius).values
|
||||
|
||||
tileList.addAll(mapValues)
|
||||
|
||||
constructor(radius:Int){
|
||||
for(vector in HexMath().getVectorsInDistance(Vector2.Zero, radius))
|
||||
tileList.add(TileInfo().apply { position = vector; baseTerrain= Constants.grassland })
|
||||
setTransients()
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class GameParameters { // Default values are the default new game
|
|||
for (i in 1..3) add(Player())
|
||||
}
|
||||
var numberOfCityStates = 0
|
||||
var mapType = MapType.Perlin
|
||||
var mapType = MapType.pangaea
|
||||
var noBarbarians = false
|
||||
var mapFileName: String? = null
|
||||
var victoryTypes: ArrayList<VictoryType> = VictoryType.values().toCollection(ArrayList()) // By default, all victory types
|
||||
|
|
|
@ -6,7 +6,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
|||
import com.unciv.logic.MapSaver
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.gamebasics.tr
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.ui.tilegroups.TileGroup
|
||||
import com.unciv.ui.tilegroups.TileSetStrings
|
||||
import com.unciv.ui.utils.CameraStageBaseScreen
|
||||
|
@ -15,7 +14,7 @@ import com.unciv.ui.utils.setFontSize
|
|||
import com.unciv.ui.worldscreen.TileGroupMap
|
||||
|
||||
class MapEditorScreen(): CameraStageBaseScreen(){
|
||||
var tileMap = TileMap(GameParameters())
|
||||
var tileMap = TileMap()
|
||||
var mapName = "My first map"
|
||||
lateinit var mapHolder: TileGroupMap<TileGroup>
|
||||
private val tileEditorOptions = TileEditorOptionsTable(this)
|
||||
|
|
|
@ -85,6 +85,7 @@ class NewGameScreen: PickerScreen(){
|
|||
cantMakeThatMapPopup.addGoodSizedLabel("Maybe you put too many players into too small a map?".tr()).row()
|
||||
cantMakeThatMapPopup.addCloseButton()
|
||||
cantMakeThatMapPopup.open()
|
||||
Gdx.input.inputProcessor = stage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,26 +60,23 @@ class NewGameScreenOptionsTable(val newGameParameters: GameParameters, val onMul
|
|||
|
||||
private fun addMapTypeSizeAndFile() {
|
||||
add("{Map type}:".tr())
|
||||
val mapTypes = LinkedHashMap<String, MapType>()
|
||||
for (type in MapType.values()) {
|
||||
if (type == MapType.File && MapSaver().getMaps().isEmpty()) continue
|
||||
mapTypes[type.toString()] = type
|
||||
}
|
||||
val mapTypes = arrayListOf(MapType.default,MapType.continents,MapType.perlin,MapType.pangaea)
|
||||
if(MapSaver().getMaps().isNotEmpty()) mapTypes.add(MapType.file)
|
||||
|
||||
val mapFileLabel = "{Map file}:".toLabel()
|
||||
val mapFileSelectBox = getMapFileSelectBox()
|
||||
mapFileLabel.isVisible = false
|
||||
mapFileSelectBox.isVisible = false
|
||||
|
||||
val mapTypeSelectBox = TranslatedSelectBox(mapTypes.keys, newGameParameters.mapType.toString(), CameraStageBaseScreen.skin)
|
||||
val mapTypeSelectBox = TranslatedSelectBox(mapTypes, newGameParameters.mapType, CameraStageBaseScreen.skin)
|
||||
|
||||
val worldSizeSelectBox = getWorldSizeSelectBox()
|
||||
val worldSizeLabel = "{World size}:".toLabel()
|
||||
|
||||
mapTypeSelectBox.addListener(object : ChangeListener() {
|
||||
override fun changed(event: ChangeEvent?, actor: Actor?) {
|
||||
newGameParameters.mapType = mapTypes[mapTypeSelectBox.selected.value]!!
|
||||
if (newGameParameters.mapType == MapType.File) {
|
||||
newGameParameters.mapType = mapTypeSelectBox.selected.value
|
||||
if (newGameParameters.mapType == MapType.file) {
|
||||
worldSizeSelectBox.isVisible = false
|
||||
worldSizeLabel.isVisible = false
|
||||
mapFileSelectBox.isVisible = true
|
||||
|
|
Loading…
Reference in a new issue