From 153a90623c719d3129c0817fd9bf4dd5a349d2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Dufour?= Date: Wed, 13 Dec 2023 21:37:27 +0100 Subject: [PATCH] Add new map types (#10695) * fractal map type * repeat create fractal until enough land * small changes and add map types Lakes and Small Continents * fix for removing land at edges of hexagonal maps * readability --- core/src/com/unciv/logic/map/MapParameters.kt | 3 + .../mapgenerator/MapGenerationRandomness.kt | 1 - .../map/mapgenerator/MapLandmassGenerator.kt | 97 ++++++++++++++++++- .../newgamescreen/MapParametersTable.kt | 3 + 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/core/src/com/unciv/logic/map/MapParameters.kt b/core/src/com/unciv/logic/map/MapParameters.kt index f8db742b54..f4d9bdbdd9 100644 --- a/core/src/com/unciv/logic/map/MapParameters.kt +++ b/core/src/com/unciv/logic/map/MapParameters.kt @@ -145,7 +145,10 @@ object MapType : IsPartOfGameInfoSerialization { const val threeContinents = "Three Continents" const val fourCorners = "Four Corners" const val archipelago = "Archipelago" + const val fractal = "Fractal" const val innerSea = "Inner Sea" + const val lakes = "Lakes" + const val smallContinents = "Small Continents" // All ocean tiles const val empty = "Empty" diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt index ba68fcfee9..4b3a586600 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerationRandomness.kt @@ -39,7 +39,6 @@ class MapGenerationRandomness { return Perlin.noise3d(worldCoords.x.toDouble(), worldCoords.y.toDouble(), seed, nOctaves, persistence, lacunarity, scale) } - fun chooseSpreadOutLocations(number: Int, suitableTiles: List, mapRadius: Int): ArrayList { if (number <= 0) return ArrayList(0) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt index 6d947acb59..8bb21fee82 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapLandmassGenerator.kt @@ -51,6 +51,9 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa MapType.fourCorners -> createFourCorners(tileMap) MapType.archipelago -> createArchipelago(tileMap) MapType.perlin -> createPerlin(tileMap) + MapType.fractal -> createFractal(tileMap) + MapType.lakes -> createLakes(tileMap) + MapType.smallContinents -> createSmallContinents(tileMap) } if (tileMap.mapParameters.shape === MapShape.flatEarth) { @@ -96,6 +99,48 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa } } + private fun createFractal(tileMap: TileMap) { + do { + val elevationSeed = randomness.RNG.nextInt().toDouble() + for (tile in tileMap.values) { + val maxdim = max(tileMap.maxLatitude, tileMap.maxLongitude) + var ratio = maxdim / 32.0 // change scale depending on map size so that average number of continents stay the same + if (tileMap.mapParameters.shape === MapShape.hexagonal || tileMap.mapParameters.shape === MapShape.flatEarth) { + ratio *= 0.5 // In hexagonal type map for some reason it tends to make a single continent like pangaea if we don't diminish the scale + } + + var elevation = randomness.getPerlinNoise(tile, elevationSeed, persistence=0.8, lacunarity=1.5, scale=ratio*30.0) + + elevation += getOceanEdgesTransform(tile, tileMap) + + spawnLandOrWater(tile, elevation) + } + waterThreshold -= 0.01 + } while (tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70% water + } + + private fun createLakes(tileMap: TileMap) { + val elevationSeed = randomness.RNG.nextInt().toDouble() + for (tile in tileMap.values) { + val elevation = 0.3 - getRidgedPerlinNoise(tile, elevationSeed, persistence=0.7, lacunarity=1.5) + + spawnLandOrWater(tile, elevation) + } + } + + private fun createSmallContinents(tileMap: TileMap) { + val elevationSeed = randomness.RNG.nextInt().toDouble() + waterThreshold += 0.25 + do { + for (tile in tileMap.values) { + var elevation = getRidgedPerlinNoise(tile, elevationSeed, scale = 22.0) + elevation += getOceanEdgesTransform(tile, tileMap) + spawnLandOrWater(tile, elevation) + } + waterThreshold -= 0.01 + } while (tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70% + } + private fun createArchipelago(tileMap: TileMap) { val elevationSeed = randomness.RNG.nextInt().toDouble() waterThreshold += 0.25 @@ -115,9 +160,9 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa } tileMap.assignContinents(TileMap.AssignContinentsMode.Reassign) + waterThreshold -= 0.01 } while (tileMap.continentSizes.values.count { it > 25 } != 1 // Multiple large continents || tileMap.values.count { it.baseTerrain == waterTerrainName } > tileMap.values.size * 0.7f) // Over 70% water - tileMap.assignContinents(TileMap.AssignContinentsMode.Clear) } @@ -312,6 +357,56 @@ class MapLandmassGenerator(val ruleset: Ruleset, val randomness: MapGenerationRa return 1.0 - (5.0 * shouldBeWater*shouldBeWater + randomScale) / 3.0 } + private fun getOceanEdgesTransform(tile: Tile, tileMap: TileMap): Double { + // The idea is to reduce elevation at the border of the map, so that we have mostly ocean there. + val maxX = tileMap.maxLongitude + val maxY = tileMap.maxLatitude + val x = tile.longitude + val y = tile.latitude + + var elevationOffset = 0.0 + + val xdistanceratio = abs(x) / maxX + val ydistanceratio = abs(y) / maxY + if (tileMap.mapParameters.shape === MapShape.hexagonal || tileMap.mapParameters.shape === MapShape.flatEarth) { + val startdropoffratio = 0.8 // distance from center at which we start decreasing elevation linearly + val xdrsquared = xdistanceratio * xdistanceratio + val ydrsquared = ydistanceratio * ydistanceratio + val distancefromcenter = sqrt(xdrsquared+ydrsquared) + var distanceoffset = 0.0 + if (distancefromcenter > startdropoffratio) { + val dropoffdistance = distancefromcenter - startdropoffratio + val normalizationDivisor = 1.0 - startdropoffratio // for normalizing to [0;1] range + distanceoffset = dropoffdistance / normalizationDivisor + } + elevationOffset -= distanceoffset * 0.35 + } else { + + var xoffset = 0.0 + + val xstartdropoffratio = 0.8 + if (xdistanceratio > xstartdropoffratio) { + val xdropoffdistance = xdistanceratio - xstartdropoffratio + val xnormalizationdivisor = 1.0 - xstartdropoffratio // for normalizing to [0;1] range + xoffset = xdropoffdistance / xnormalizationdivisor + } + + var yoffset = 0.0 + + val ystartdropoffratio = 0.76 // we want to have enough space at the north and south for circumnavigation + if (ydistanceratio > ystartdropoffratio) { + val ydropoffdistance = ydistanceratio - ystartdropoffratio + val ynormalizationdivisor = 1.0 - ystartdropoffratio // for normalizing to [0;1] range + yoffset = ydropoffdistance / ynormalizationdivisor + } + // these factors were just found by trial and error to be adequate for having enough space while not reducing land too much. + elevationOffset -= xoffset * 0.33 + elevationOffset -= yoffset * 0.35 + } + + return max(elevationOffset, -0.35) + } + /** * Generates ridged perlin noise. As for parameters see [MapGenerationRandomness.getPerlinNoise] */ diff --git a/core/src/com/unciv/ui/screens/newgamescreen/MapParametersTable.kt b/core/src/com/unciv/ui/screens/newgamescreen/MapParametersTable.kt index a462ee5e60..4c5b019131 100644 --- a/core/src/com/unciv/ui/screens/newgamescreen/MapParametersTable.kt +++ b/core/src/com/unciv/ui/screens/newgamescreen/MapParametersTable.kt @@ -139,6 +139,9 @@ class MapParametersTable( MapType.archipelago, MapType.innerSea, MapType.perlin, + MapType.fractal, + MapType.lakes, + MapType.smallContinents, if (forMapEditor && mapGeneratedMainType != MapGeneratedMainType.randomGenerated) MapType.empty else null )