mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 09:18:43 +07:00
Moddable Ice generation (any impassable feature on Water possible) (#6329)
Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
@ -270,7 +270,8 @@
|
|||||||
"impassable": true,
|
"impassable": true,
|
||||||
"overrideStats": true,
|
"overrideStats": true,
|
||||||
"occursOn": ["Ocean", "Coast"],
|
"occursOn": ["Ocean", "Coast"],
|
||||||
"uniques": ["[-1] to Fertility for Map Generation",
|
"uniques": ["Occurs at temperature between [-1] and [-0.8] and humidity between [0] and [1]",
|
||||||
|
"[-1] to Fertility for Map Generation",
|
||||||
"Considered [Undesirable] when determining start locations"]
|
"Considered [Undesirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -272,7 +272,8 @@
|
|||||||
"impassable": true,
|
"impassable": true,
|
||||||
"overrideStats": true,
|
"overrideStats": true,
|
||||||
"occursOn": ["Ocean", "Coast"],
|
"occursOn": ["Ocean", "Coast"],
|
||||||
"uniques": ["[-1] to Fertility for Map Generation",
|
"uniques": ["Occurs at temperature between [-1] and [-0.8] and humidity between [0] and [1]",
|
||||||
|
"[-1] to Fertility for Map Generation",
|
||||||
"Considered [Undesirable] when determining start locations"]
|
"Considered [Undesirable] when determining start locations"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,6 @@ object Constants {
|
|||||||
const val jungle = "Jungle"
|
const val jungle = "Jungle"
|
||||||
const val ice = "Ice"
|
const val ice = "Ice"
|
||||||
val vegetation = arrayOf(forest, jungle)
|
val vegetation = arrayOf(forest, jungle)
|
||||||
val sea = arrayOf(ocean, coast)
|
|
||||||
|
|
||||||
// Note the difference in case. **Not** interchangeable!
|
// Note the difference in case. **Not** interchangeable!
|
||||||
/** The "Fresh water" terrain _unique_ */
|
/** The "Fresh water" terrain _unique_ */
|
||||||
|
@ -10,6 +10,7 @@ import com.unciv.models.ruleset.Ruleset
|
|||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.tile.Terrain
|
import com.unciv.models.ruleset.tile.Terrain
|
||||||
import com.unciv.models.ruleset.tile.TerrainType
|
import com.unciv.models.ruleset.tile.TerrainType
|
||||||
|
import com.unciv.models.ruleset.unique.Unique
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -25,6 +26,27 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
private var randomness = MapGenerationRandomness()
|
private var randomness = MapGenerationRandomness()
|
||||||
private val firstLandTerrain = ruleset.terrains.values.first { it.type==TerrainType.Land }
|
private val firstLandTerrain = ruleset.terrains.values.first { it.type==TerrainType.Land }
|
||||||
|
|
||||||
|
/** Associates [terrain] with a range of temperatures and a range of humidities (both open to closed) */
|
||||||
|
private class TerrainOccursRange(
|
||||||
|
val terrain: Terrain,
|
||||||
|
val tempFrom: Float, val tempTo: Float,
|
||||||
|
val humidFrom: Float, val humidTo: Float
|
||||||
|
) {
|
||||||
|
/** builds a [TerrainOccursRange] for [terrain] from a [unique] (type [UniqueType.TileGenerationConditions]) */
|
||||||
|
constructor(terrain: Terrain, unique: Unique)
|
||||||
|
: this(terrain,
|
||||||
|
unique.params[0].toFloat(), unique.params[1].toFloat(),
|
||||||
|
unique.params[2].toFloat(), unique.params[3].toFloat())
|
||||||
|
/** checks if both [temperature] and [humidity] satisfy their ranges (>From, <=To) */
|
||||||
|
// Yes this does implicit conversions Float/Double
|
||||||
|
fun matches(temperature: Double, humidity: Double) =
|
||||||
|
tempFrom < temperature && temperature <= tempTo &&
|
||||||
|
humidFrom < humidity && humidity <= humidTo
|
||||||
|
}
|
||||||
|
private fun Terrain.getGenerationConditions() =
|
||||||
|
getMatchingUniques(UniqueType.TileGenerationConditions)
|
||||||
|
.map { unique -> TerrainOccursRange(this, unique) }
|
||||||
|
|
||||||
fun generateMap(mapParameters: MapParameters, civilizations: List<CivilizationInfo> = emptyList()): TileMap {
|
fun generateMap(mapParameters: MapParameters, civilizations: List<CivilizationInfo> = emptyList()): TileMap {
|
||||||
val mapSize = mapParameters.mapSize
|
val mapSize = mapParameters.mapSize
|
||||||
val mapType = mapParameters.type
|
val mapType = mapParameters.type
|
||||||
@ -408,20 +430,10 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
val scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble()
|
val scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble()
|
||||||
val temperatureExtremeness = tileMap.mapParameters.temperatureExtremeness
|
val temperatureExtremeness = tileMap.mapParameters.temperatureExtremeness
|
||||||
|
|
||||||
class TerrainOccursRange(
|
|
||||||
val terrain: Terrain,
|
|
||||||
val tempFrom: Float, val tempTo: Float,
|
|
||||||
val humidFrom: Float, val humidTo: Float
|
|
||||||
)
|
|
||||||
// List is OK here as it's only sequentially scanned
|
// List is OK here as it's only sequentially scanned
|
||||||
val limitsMap: List<TerrainOccursRange> =
|
val limitsMap: List<TerrainOccursRange> =
|
||||||
ruleset.terrains.values.flatMap { terrain ->
|
ruleset.terrains.values.flatMap {
|
||||||
terrain.getMatchingUniques(UniqueType.TileGenerationConditions)
|
it.getGenerationConditions()
|
||||||
.map { unique ->
|
|
||||||
TerrainOccursRange(terrain,
|
|
||||||
unique.params[0].toFloat(), unique.params[1].toFloat(),
|
|
||||||
unique.params[2].toFloat(), unique.params[3].toFloat())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val noTerrainUniques = limitsMap.isEmpty()
|
val noTerrainUniques = limitsMap.isEmpty()
|
||||||
val elevationTerrains = arrayOf(Constants.mountain, Constants.hill)
|
val elevationTerrains = arrayOf(Constants.mountain, Constants.hill)
|
||||||
@ -453,10 +465,7 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val matchingTerrain = limitsMap.firstOrNull {
|
val matchingTerrain = limitsMap.firstOrNull { it.matches(temperature, humidity) }
|
||||||
it.tempFrom < temperature && temperature <= it.tempTo
|
|
||||||
&& it.humidFrom < humidity && humidity <= it.humidTo
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchingTerrain != null) tile.baseTerrain = matchingTerrain.terrain.name
|
if (matchingTerrain != null) tile.baseTerrain = matchingTerrain.terrain.name
|
||||||
else {
|
else {
|
||||||
@ -505,19 +514,43 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
* [MapParameters.temperatureExtremeness] as in [applyHumidityAndTemperature]
|
* [MapParameters.temperatureExtremeness] as in [applyHumidityAndTemperature]
|
||||||
*/
|
*/
|
||||||
private fun spawnIce(tileMap: TileMap) {
|
private fun spawnIce(tileMap: TileMap) {
|
||||||
if (!ruleset.terrains.containsKey(Constants.ice)) return // I can't think of how to make this nicely moddable
|
val waterTerrain: Set<String> =
|
||||||
|
ruleset.terrains.values.asSequence()
|
||||||
|
.filter { it.type == TerrainType.Water }
|
||||||
|
.map { it.name }.toSet()
|
||||||
|
val iceEquivalents: List<TerrainOccursRange> =
|
||||||
|
ruleset.terrains.values.asSequence()
|
||||||
|
.filter { terrain ->
|
||||||
|
terrain.type == TerrainType.TerrainFeature &&
|
||||||
|
terrain.impassable &&
|
||||||
|
terrain.occursOn.all { it in waterTerrain }
|
||||||
|
}.flatMap { terrain ->
|
||||||
|
val conditions = terrain.getGenerationConditions()
|
||||||
|
if (conditions.any()) conditions
|
||||||
|
else sequenceOf(TerrainOccursRange(terrain, -1f, -0.8f, 0f, 1f))
|
||||||
|
}.toList()
|
||||||
|
if (iceEquivalents.isEmpty()) return
|
||||||
|
|
||||||
tileMap.setTransients(ruleset)
|
tileMap.setTransients(ruleset)
|
||||||
val temperatureSeed = randomness.RNG.nextInt().toDouble()
|
val temperatureSeed = randomness.RNG.nextInt().toDouble()
|
||||||
for (tile in tileMap.values) {
|
for (tile in tileMap.values) {
|
||||||
if (tile.baseTerrain !in Constants.sea || tile.terrainFeatures.isNotEmpty())
|
if (tile.baseTerrain !in waterTerrain || tile.terrainFeatures.isNotEmpty())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
val randomTemperature = randomness.getPerlinNoise(tile, temperatureSeed, scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble(), nOctaves = 1)
|
val randomTemperature = randomness.getPerlinNoise(tile, temperatureSeed, scale = tileMap.mapParameters.tilesPerBiomeArea.toDouble(), nOctaves = 1)
|
||||||
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
val latitudeTemperature = 1.0 - 2.0 * abs(tile.latitude) / tileMap.maxLatitude
|
||||||
var temperature = ((latitudeTemperature + randomTemperature) / 2.0)
|
var temperature = ((latitudeTemperature + randomTemperature) / 2.0)
|
||||||
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
temperature = abs(temperature).pow(1.0 - tileMap.mapParameters.temperatureExtremeness) * temperature.sign
|
||||||
if (temperature < -0.8f)
|
|
||||||
tile.addTerrainFeature(Constants.ice)
|
val candidates = iceEquivalents
|
||||||
|
.filter {
|
||||||
|
it.matches(temperature, 1.0) &&
|
||||||
|
tile.getLastTerrain().name in it.terrain.occursOn
|
||||||
|
}.map { it.terrain.name }
|
||||||
|
when (candidates.size) {
|
||||||
|
1 -> tile.addTerrainFeature(candidates.first())
|
||||||
|
!in 0..1 -> tile.addTerrainFeature(candidates.random(randomness.RNG))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,9 @@ Each terrain entry can have the following properties:
|
|||||||
| movementCost | Integer | Default 1 | base movement cost |
|
| movementCost | Integer | Default 1 | base movement cost |
|
||||||
| defenceBonus | Float | Default 0 | combat bonus for units being attacked here |
|
| defenceBonus | Float | Default 0 | combat bonus for units being attacked here |
|
||||||
| RGB | List Integer * 3 | Default 'Gold' | RGB color for 'Default' tileset display |
|
| RGB | List Integer * 3 | Default 'Gold' | RGB color for 'Default' tileset display |
|
||||||
| uniques | List | Default empty | List of effects, [see here](../Modders/Unique-parameter-types.md#terrain-uniques) |
|
| uniques | List | Default empty | List of effects, [see here](../Modders/uniques.md#terrain-uniques) |
|
||||||
| civilopediaText | List | Default empty | see [civilopediaText chapter](Miscellaneous-JSON-files.md#civilopedia-text) |
|
| civilopediaText | List | Default empty | see [civilopediaText chapter](Miscellaneous-JSON-files.md#civilopedia-text) |
|
||||||
|
|
||||||
Note that many Natural Wonders have hardcoded routines for their placement and are recognized by name (e.g. Great Barrier Reef being more than one tile).
|
|
||||||
|
|
||||||
|
|
||||||
## TileImprovements.json
|
## TileImprovements.json
|
||||||
This file lists the improvements that can be constructed or created on a map tile by a unit (any unit having the appropriate unique).
|
This file lists the improvements that can be constructed or created on a map tile by a unit (any unit having the appropriate unique).
|
||||||
|
Reference in New Issue
Block a user