Fix ChangesTerrain unique for base terrains (#10043)

* Fix UniqueType.ChangesTerrain not working for base terrain parameter

* Fix spawnRiver resultingTiles to include all affected tiles on both sides of the River

* Fix terrain conversion for rivers from Map Editor partial generation / paint from-to

* forEach linting

* Instrumentation for generateSingleStep

* forEach linting

* Remove lazies
This commit is contained in:
SomeTroglodyte 2023-09-04 13:36:52 +02:00 committed by GitHub
parent 8b9d0af4bf
commit d758da4d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 111 deletions

View File

@ -33,6 +33,13 @@ import kotlin.math.ulp
import kotlin.random.Random import kotlin.random.Random
/** Map generator, used by new game, map editor and main menu background
*
* Class instance only keeps [ruleset] and [coroutineScope] for easier access, input and output are through methods, namely [generateMap] and [generateSingleStep].
*
* @param ruleset The Ruleset supplying terrain and resource definitions
* @param coroutineScope Enables early abort if this returns `isActive == false`
*/
class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineScope? = null) { class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineScope? = null) {
companion object { companion object {
@ -138,7 +145,7 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
runAndMeasure("RiverGenerator") { runAndMeasure("RiverGenerator") {
RiverGenerator(map, randomness, ruleset).spawnRivers() RiverGenerator(map, randomness, ruleset).spawnRivers()
} }
convertTerrains(map, ruleset) convertTerrains(map.values)
// Region based map generation - not used when generating maps in map editor // Region based map generation - not used when generating maps in map editor
if (civilizations.isNotEmpty()) { if (civilizations.isNotEmpty()) {
@ -182,24 +189,27 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
randomness.seedRNG(map.mapParameters.seed) randomness.seedRNG(map.mapParameters.seed)
when(step) { runAndMeasure("SingleStep $step") {
MapGeneratorSteps.None -> return when (step) {
MapGeneratorSteps.All -> throw IllegalArgumentException("MapGeneratorSteps.All cannot be used in generateSingleStep") MapGeneratorSteps.None -> Unit
MapGeneratorSteps.Landmass -> MapLandmassGenerator(ruleset, randomness).generateLand(map) MapGeneratorSteps.All -> throw IllegalArgumentException("MapGeneratorSteps.All cannot be used in generateSingleStep")
MapGeneratorSteps.Elevation -> raiseMountainsAndHills(map) MapGeneratorSteps.Landmass -> MapLandmassGenerator(ruleset, randomness).generateLand(map)
MapGeneratorSteps.HumidityAndTemperature -> applyHumidityAndTemperature(map) MapGeneratorSteps.Elevation -> raiseMountainsAndHills(map)
MapGeneratorSteps.LakesAndCoast -> spawnLakesAndCoasts(map) MapGeneratorSteps.HumidityAndTemperature -> applyHumidityAndTemperature(map)
MapGeneratorSteps.Vegetation -> spawnVegetation(map) MapGeneratorSteps.LakesAndCoast -> spawnLakesAndCoasts(map)
MapGeneratorSteps.RareFeatures -> spawnRareFeatures(map) MapGeneratorSteps.Vegetation -> spawnVegetation(map)
MapGeneratorSteps.Ice -> spawnIce(map) MapGeneratorSteps.RareFeatures -> spawnRareFeatures(map)
MapGeneratorSteps.Continents -> map.assignContinents(TileMap.AssignContinentsMode.Reassign) MapGeneratorSteps.Ice -> spawnIce(map)
MapGeneratorSteps.NaturalWonders -> NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) MapGeneratorSteps.Continents -> map.assignContinents(TileMap.AssignContinentsMode.Reassign)
MapGeneratorSteps.Rivers -> { MapGeneratorSteps.NaturalWonders -> NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
RiverGenerator(map, randomness, ruleset).spawnRivers() MapGeneratorSteps.Rivers -> {
convertTerrains(map, ruleset) val resultingTiles = mutableSetOf<Tile>()
RiverGenerator(map, randomness, ruleset).spawnRivers(resultingTiles)
convertTerrains(resultingTiles)
}
MapGeneratorSteps.Resources -> spreadResources(map)
MapGeneratorSteps.AncientRuins -> spreadAncientRuins(map)
} }
MapGeneratorSteps.Resources -> spreadResources(map)
MapGeneratorSteps.AncientRuins -> spreadAncientRuins(map)
} }
} }
@ -213,18 +223,19 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc
debug("MapGenerator.%s took %s.%sms", text, delta/1000000L, (delta/10000L).rem(100)) debug("MapGenerator.%s took %s.%sms", text, delta/1000000L, (delta/10000L).rem(100))
} }
private fun convertTerrains(map: TileMap, ruleset: Ruleset) { fun convertTerrains(tiles: Iterable<Tile>) {
for (tile in map.values) { for (tile in tiles) {
val conversionUnique = val conversionUnique =
tile.getBaseTerrain().getMatchingUniques(UniqueType.ChangesTerrain) tile.getBaseTerrain().getMatchingUniques(UniqueType.ChangesTerrain)
.firstOrNull { tile.isAdjacentTo(it.params[1]) } .firstOrNull { tile.isAdjacentTo(it.params[1]) }
?: continue ?: continue
val terrain = ruleset.terrains[conversionUnique.params[0]] ?: continue val terrain = ruleset.terrains[conversionUnique.params[0]] ?: continue
if (!terrain.occursOn.contains(tile.lastTerrain.name)) continue
if (terrain.type == TerrainType.TerrainFeature) if (terrain.type == TerrainType.TerrainFeature) {
if (!terrain.occursOn.contains(tile.lastTerrain.name)) continue
tile.addTerrainFeature(terrain.name) tile.addTerrainFeature(terrain.name)
else tile.baseTerrain = terrain.name } else
tile.baseTerrain = terrain.name
tile.setTerrainTransients() tile.setTerrainTransients()
} }
} }

View File

@ -2,10 +2,10 @@ package com.unciv.logic.map.mapgenerator
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants import com.unciv.Constants
import com.unciv.utils.debug
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.utils.debug
import kotlin.math.roundToInt import kotlin.math.roundToInt
class RiverGenerator( class RiverGenerator(
@ -17,7 +17,7 @@ class RiverGenerator(
private val minRiverLength = ruleset.modOptions.constants.minRiverLength private val minRiverLength = ruleset.modOptions.constants.minRiverLength
private val maxRiverLength = ruleset.modOptions.constants.maxRiverLength private val maxRiverLength = ruleset.modOptions.constants.maxRiverLength
fun spawnRivers() { fun spawnRivers(resultingTiles: MutableSet<Tile>? = null) {
if (tileMap.values.none { it.isWater }) return if (tileMap.values.none { it.isWater }) return
val numberOfRivers = (tileMap.values.count { it.isLand } * riverCountMultiplier).roundToInt() val numberOfRivers = (tileMap.values.count { it.isLand } * riverCountMultiplier).roundToInt()
@ -33,7 +33,7 @@ class RiverGenerator(
val mapRadius = tileMap.mapParameters.mapSize.radius val mapRadius = tileMap.mapParameters.mapSize.radius
val riverStarts = val riverStarts =
randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius) randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius)
for (tile in riverStarts) spawnRiver(tile) for (tile in riverStarts) spawnRiver(tile, resultingTiles)
} }
private fun Tile.isFarEnoughFromWater(): Boolean { private fun Tile.isFarEnoughFromWater(): Boolean {
@ -52,71 +52,47 @@ class RiverGenerator(
return null return null
} }
private fun spawnRiver(initialPosition: Tile) { private fun spawnRiver(initialPosition: Tile, resultingTiles: MutableSet<Tile>?) {
val endPosition = getClosestWaterTile(initialPosition) val endPosition = getClosestWaterTile(initialPosition)
?: error("No water found for river destination") ?: error("No water found for river destination")
spawnRiver(initialPosition, endPosition) spawnRiver(initialPosition, endPosition, resultingTiles)
} }
fun spawnRiver(initialPosition: Tile, endPosition: Tile, resultingTiles: MutableSet<Tile>? = null) { /** Spawns a river from [initialPosition] to [endPosition].
* If [resultingTiles] is supplied, it will contain all affected tiles, for map editor. */
fun spawnRiver(initialPosition: Tile, endPosition: Tile, resultingTiles: MutableSet<Tile>?) {
// Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful! // Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful!
var riverCoordinate = RiverCoordinate(initialPosition.position, var riverCoordinate = RiverCoordinate(tileMap, initialPosition.position,
RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG)) RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG))
repeat(maxRiverLength) { // Arbitrary max on river length, otherwise this will go in circles - rarely repeat(maxRiverLength) { // Arbitrary max on river length, otherwise this will go in circles - rarely
val riverCoordinateTile = tileMap[riverCoordinate.position] if (riverCoordinate.getAdjacentTiles().any { it.isWater }) return
resultingTiles?.add(riverCoordinateTile) val possibleCoordinates = riverCoordinate.getAdjacentPositions()
if (riverCoordinate.getAdjacentTiles(tileMap).any { it.isWater }) return
val possibleCoordinates = riverCoordinate.getAdjacentPositions(tileMap)
if (possibleCoordinates.none()) return // end of the line if (possibleCoordinates.none()) return // end of the line
val newCoordinate = possibleCoordinates val newCoordinate = possibleCoordinates
.groupBy { newCoordinate -> .groupBy { newCoordinate ->
newCoordinate.getAdjacentTiles(tileMap).map { it.aerialDistanceTo(endPosition) } newCoordinate.getAdjacentTiles().map { it.aerialDistanceTo(endPosition) }
.minOrNull()!! .minOrNull()!!
} }
.minByOrNull { it.key }!! .minByOrNull { it.key }!!
.component2().random(randomness.RNG) .component2().random(randomness.RNG)
// set new rivers in place // set one new river edge in place
if (newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left riverCoordinate.paintTo(newCoordinate, resultingTiles)
riverCoordinateTile.hasBottomRiver = true // Move on
else if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomRight) {
if (newCoordinate.getAdjacentTiles(tileMap).contains(riverCoordinateTile)) // moved from our 5 O'Clock to our 3 O'Clock
riverCoordinateTile.hasBottomRightRiver = true
else // moved from our 5 O'Clock down in the 5 O'Clock direction - this is the 8 O'Clock river of the tile to our 4 O'Clock!
tileMap[newCoordinate.position].hasBottomLeftRiver = true
} else { // riverCoordinate.bottomRightOrLeft==RiverCoordinate.BottomRightOrLeft.Left
if (newCoordinate.getAdjacentTiles(tileMap).contains(riverCoordinateTile)) // moved from our 7 O'Clock to our 9 O'Clock
riverCoordinateTile.hasBottomLeftRiver = true
else // moved from our 7 O'Clock down in the 7 O'Clock direction
tileMap[newCoordinate.position].hasBottomRightRiver = true
}
riverCoordinate = newCoordinate riverCoordinate = newCoordinate
} }
debug("River reached max length!") debug("River reached max length!")
} }
/*
fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate): Int {
var sum = 0
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRiver) sum += 1
if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) {
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomLeftRiver) sum += 1
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(0f, -1f)
if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomRightRiver) sum += 1
} else {
if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRightRiver) sum += 1
val bottomLeftTilePosition = riverCoordinate.position.cpy().add(-1f, 0f)
if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1
}
return sum
}
*/
/** Describes a _Vertex_ on our hexagonal grid via a neighboring hex and clock direction, normalized /** Describes a _Vertex_ on our hexagonal grid via a neighboring hex and clock direction, normalized
* such that always the north-most hex and one of the two clock directions 5 / 7 o'clock are used. */ * such that always the north-most hex and one of the two clock directions 5 / 7 o'clock are used. */
class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft) { class RiverCoordinate(
private val tileMap: TileMap,
private val position: Vector2,
private val bottomRightOrLeft: BottomRightOrLeft
) {
enum class BottomRightOrLeft { enum class BottomRightOrLeft {
/** 7 O'Clock of the tile */ /** 7 O'Clock of the tile */
BottomLeft, BottomLeft,
@ -125,43 +101,92 @@ class RiverGenerator(
BottomRight BottomRight
} }
private val x = position.x.toInt()
private val y = position.y.toInt()
// Depending on the tile instance, some of the following will never be used. Tested with lazies: ~2% slower
private val myTile = tileMap[position]
private val myTopLeft = tileMap.getIfTileExistsOrNull(x + 1, y)
private val myBottomLeft = tileMap.getIfTileExistsOrNull(x, y - 1)
private val myTopRight = tileMap.getIfTileExistsOrNull(x, y + 1)
private val myBottomRight = tileMap.getIfTileExistsOrNull(x - 1, y)
private val myBottomCenter = tileMap.getIfTileExistsOrNull(x - 1, y - 1)
/** Lists the three neighboring vertices which have their anchor hex on the map /** Lists the three neighboring vertices which have their anchor hex on the map
* (yes some positions on the map's outer border will be included, some not) */ * (yes some positions on the map's outer border will be included, some not) */
fun getAdjacentPositions(tileMap: TileMap): Sequence<RiverCoordinate> = sequence { fun getAdjacentPositions(): Sequence<RiverCoordinate> = sequence {
// What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa // What's nice is that adjacents are always the OPPOSITE in terms of right-left - rights are adjacent to only lefts, and vice-versa
// This means that a lot of obviously-wrong assignments are simple to spot // This means that a lot of obviously-wrong assignments are simple to spot
val x = position.x.toInt()
val y = position.y.toInt()
if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) { if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
yield(RiverCoordinate(position, BottomRightOrLeft.BottomRight)) // same tile, other side yield(RiverCoordinate(tileMap, position, BottomRightOrLeft.BottomRight)) // same tile, other side
val myTopLeft = tileMap.getIfTileExistsOrNull(x + 1, y)
if (myTopLeft != null) if (myTopLeft != null)
yield(RiverCoordinate(myTopLeft.position, BottomRightOrLeft.BottomRight)) // tile to MY top-left, take its bottom right corner yield(RiverCoordinate(tileMap, myTopLeft!!.position, BottomRightOrLeft.BottomRight)) // tile to MY top-left, take its bottom right corner
val myBottomLeft = tileMap.getIfTileExistsOrNull(x, y - 1)
if (myBottomLeft != null) if (myBottomLeft != null)
yield(RiverCoordinate(myBottomLeft.position, BottomRightOrLeft.BottomRight)) // Tile to MY bottom-left, take its bottom right yield(RiverCoordinate(tileMap, myBottomLeft!!.position, BottomRightOrLeft.BottomRight)) // Tile to MY bottom-left, take its bottom right
} else { } else {
yield(RiverCoordinate(position, BottomRightOrLeft.BottomLeft)) // same tile, other side yield(RiverCoordinate(tileMap, position, BottomRightOrLeft.BottomLeft)) // same tile, other side
val myTopRight = tileMap.getIfTileExistsOrNull(x, y + 1)
if (myTopRight != null) if (myTopRight != null)
yield(RiverCoordinate(myTopRight.position, BottomRightOrLeft.BottomLeft)) // tile to MY top-right, take its bottom left yield(RiverCoordinate(tileMap, myTopRight!!.position, BottomRightOrLeft.BottomLeft)) // tile to MY top-right, take its bottom left
val myBottomRight = tileMap.getIfTileExistsOrNull(x - 1, y)
if (myBottomRight != null) if (myBottomRight != null)
yield(RiverCoordinate(myBottomRight.position, BottomRightOrLeft.BottomLeft)) // tile to MY bottom-right, take its bottom left yield(RiverCoordinate(tileMap, myBottomRight!!.position, BottomRightOrLeft.BottomLeft)) // tile to MY bottom-right, take its bottom left
} }
} }
/** Lists the three neighboring hexes to this vertex which are on the map */ /** Lists the three neighboring hexes to this vertex which are on the map */
fun getAdjacentTiles(tileMap: TileMap): Sequence<Tile> = sequence { fun getAdjacentTiles(): Sequence<Tile> = sequence {
val x = position.x.toInt() yield(myTile)
val y = position.y.toInt() myBottomCenter?.let { yield(it) } // tile directly below us,
yield(tileMap[x, y]) if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft)
val below = tileMap.getIfTileExistsOrNull(x - 1, y - 1) // tile directly below us, myBottomLeft?.let { yield(it) } // tile to our bottom-left
if (below != null) yield(below) else
val leftOrRight = if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) myBottomRight?.let { yield(it) } // tile to our bottom-right
tileMap.getIfTileExistsOrNull(x, y - 1) // tile to our bottom-left
else tileMap.getIfTileExistsOrNull(x - 1, y) // tile to our bottom-right
if (leftOrRight != null) yield(leftOrRight)
} }
fun paintTo(newCoordinate: RiverCoordinate, resultingTiles: MutableSet<Tile>?) {
if (newCoordinate.position == position) // same tile, switched right-to-left
paintBottom(resultingTiles)
else if (bottomRightOrLeft == BottomRightOrLeft.BottomRight) {
if (newCoordinate.getAdjacentTiles().contains(myTile)) // moved from our 5 O'Clock to our 3 O'Clock
paintBottomRight(resultingTiles)
else // moved from our 5 O'Clock down in the 5 O'Clock direction - this is the 8 O'Clock river of the tile to our 4 O'Clock!
newCoordinate.paintBottomLeft(resultingTiles)
} else { // bottomRightOrLeft == BottomRightOrLeft.BottomLeft
if (newCoordinate.getAdjacentTiles().contains(myTile)) // moved from our 7 O'Clock to our 9 O'Clock
paintBottomLeft(resultingTiles)
else // moved from our 7 O'Clock down in the 7 O'Clock direction
newCoordinate.paintBottomRight(resultingTiles)
}
}
private fun paintBottom(resultingTiles: MutableSet<Tile>?) {
myTile.hasBottomRiver = true
if (resultingTiles == null) return
resultingTiles.add(myTile)
myBottomCenter?.let { resultingTiles.add(it) }
}
private fun paintBottomLeft(resultingTiles: MutableSet<Tile>?) {
myTile.hasBottomLeftRiver = true
if (resultingTiles == null) return
resultingTiles.add(myTile)
myBottomLeft?.let { resultingTiles.add(it) }
}
private fun paintBottomRight(resultingTiles: MutableSet<Tile>?) {
myTile.hasBottomRightRiver = true
if (resultingTiles == null) return
resultingTiles.add(myTile)
myBottomRight?.let { resultingTiles.add(it) }
}
/** Count edges with a river from this vertex */
@Suppress("unused") // Keep as how-to just in case
fun numberOfConnectedRivers(): Int = sequence {
yield(myTile.hasBottomRiver)
if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) {
yield(myTile.hasBottomLeftRiver)
yield(myBottomLeft?.hasBottomRightRiver == true)
} else {
yield(myTile.hasBottomRightRiver)
yield(myBottomRight?.hasBottomLeftRiver == true)
}
}.count { it }
} }
} }

View File

@ -21,17 +21,17 @@ import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorOptionsTab
import com.unciv.ui.popups.ConfirmPopup
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
import com.unciv.ui.components.input.KeyboardPanningListener import com.unciv.ui.components.input.KeyboardPanningListener
import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.images.ImageWithCustomSize import com.unciv.ui.images.ImageWithCustomSize
import com.unciv.ui.popups.ConfirmPopup
import com.unciv.ui.popups.ToastPopup import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.RecreateOnResize import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorOptionsTab
import com.unciv.ui.screens.worldscreen.ZoomButtonPair import com.unciv.ui.screens.worldscreen.ZoomButtonPair
import com.unciv.utils.Concurrency import com.unciv.utils.Concurrency
import com.unciv.utils.Dispatcher import com.unciv.utils.Dispatcher
@ -47,7 +47,6 @@ import kotlinx.coroutines.Job
//todo Synergy with Civilopedia for drawing loose tiles / terrain icons //todo Synergy with Civilopedia for drawing loose tiles / terrain icons
//todo left-align everything so a half-open drawer is more useful //todo left-align everything so a half-open drawer is more useful
//todo combined brush //todo combined brush
//todo New function `convertTerrains` is auto-run after rivers the right decision for step-wise generation? Will paintRiverFromTo need the same? Will painting manually need the conversion?
//todo Tooltips for Edit items with info on placeability? Place this info as Brush description? In Expander? //todo Tooltips for Edit items with info on placeability? Place this info as Brush description? In Expander?
//todo Civilopedia links from edit items by right-click/long-tap? //todo Civilopedia links from edit items by right-click/long-tap?
//todo Mod tab change base ruleset - disableAllCheckboxes - instead some intelligence to leave those mods on that stay compatible? //todo Mod tab change base ruleset - disableAllCheckboxes - instead some intelligence to leave those mods on that stay compatible?

View File

@ -7,16 +7,17 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.map.BFS import com.unciv.logic.map.BFS
import com.unciv.logic.map.mapgenerator.MapGenerationRandomness import com.unciv.logic.map.mapgenerator.MapGenerationRandomness
import com.unciv.logic.map.mapgenerator.MapGenerator
import com.unciv.logic.map.mapgenerator.RiverGenerator import com.unciv.logic.map.mapgenerator.RiverGenerator
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.UncivSlider import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.extensions.addSeparator import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.input.keyShortcuts
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.input.keyShortcuts
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ToastPopup import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
@ -211,7 +212,7 @@ class MapEditorEditTab(
riverEndTile = tile riverEndTile = tile
if (riverStartTile != null) return paintRiverFromTo() if (riverStartTile != null) return paintRiverFromTo()
} }
tilesToHighlight.forEach { editorScreen.highlightTile(it, Color.BLUE) } for (tileToHighlight in tilesToHighlight) editorScreen.highlightTile(tileToHighlight, Color.BLUE)
} }
private fun paintRiverFromTo() { private fun paintRiverFromTo() {
val resultingTiles = mutableSetOf<Tile>() val resultingTiles = mutableSetOf<Tile>()
@ -219,6 +220,7 @@ class MapEditorEditTab(
try { try {
val riverGenerator = RiverGenerator(editorScreen.tileMap, randomness, ruleset) val riverGenerator = RiverGenerator(editorScreen.tileMap, randomness, ruleset)
riverGenerator.spawnRiver(riverStartTile!!, riverEndTile!!, resultingTiles) riverGenerator.spawnRiver(riverStartTile!!, riverEndTile!!, resultingTiles)
MapGenerator(ruleset).convertTerrains(resultingTiles)
} catch (ex: Exception) { } catch (ex: Exception) {
Log.error("Exception while generating rivers", ex) Log.error("Exception while generating rivers", ex)
ToastPopup("River generation failed!", editorScreen) ToastPopup("River generation failed!", editorScreen)
@ -226,7 +228,7 @@ class MapEditorEditTab(
riverStartTile = null riverStartTile = null
riverEndTile = null riverEndTile = null
editorScreen.isDirty = true editorScreen.isDirty = true
resultingTiles.forEach { editorScreen.updateAndHighlight(it, Color.SKY) } for (tile in resultingTiles) editorScreen.updateAndHighlight(tile, Color.SKY)
} }
internal fun paintTilesWithBrush(tile: Tile) { internal fun paintTilesWithBrush(tile: Tile) {
@ -238,12 +240,12 @@ class MapEditorEditTab(
} else { } else {
tile.getTilesInDistance(brushSize - 1) tile.getTilesInDistance(brushSize - 1)
} }
tiles.forEach { for (tileToPaint in tiles) {
when (brushHandlerType) { when (brushHandlerType) {
BrushHandlerType.Direct -> directPaintTile(it) BrushHandlerType.Direct -> directPaintTile(tileToPaint)
BrushHandlerType.Tile -> paintTile(it) BrushHandlerType.Tile -> paintTile(tileToPaint)
BrushHandlerType.Road -> roadPaintTile(it) BrushHandlerType.Road -> roadPaintTile(tileToPaint)
BrushHandlerType.River -> riverPaintTile(it) BrushHandlerType.River -> riverPaintTile(tileToPaint)
else -> {} // other cases can't reach here else -> {} // other cases can't reach here
} }
} }
@ -256,19 +258,22 @@ class MapEditorEditTab(
editorScreen.updateAndHighlight(tile) editorScreen.updateAndHighlight(tile)
} }
/** Used for rivers - same as directPaintTile but may need to update 10,12 and 2 o'clock neighbor tiles too */ /** Used for rivers - same as [directPaintTile] but may need to update 10,12 and 2 o'clock neighbor tiles too
*
* Note: Unlike [paintRiverFromTo] this does **not** call [MapGenerator.convertTerrains] to allow more freedom.
*/
private fun riverPaintTile(tile: Tile) { private fun riverPaintTile(tile: Tile) {
directPaintTile(tile) directPaintTile(tile)
tile.neighbors.forEach { for (neighbor in tile.neighbors) {
if (it.position.x > tile.position.x || it.position.y > tile.position.y) if (neighbor.position.x > tile.position.x || neighbor.position.y > tile.position.y)
editorScreen.updateTile(it) editorScreen.updateTile(neighbor)
} }
} }
// Used for roads - same as paintTile but all neighbors need TileGroup.update too // Used for roads - same as paintTile but all neighbors need TileGroup.update too
private fun roadPaintTile(tile: Tile) { private fun roadPaintTile(tile: Tile) {
if (!paintTile(tile)) return if (!paintTile(tile)) return
tile.neighbors.forEach { editorScreen.updateTile(it) } for (neighbor in tile.neighbors) editorScreen.updateTile(neighbor)
} }
/** apply brush to a single tile */ /** apply brush to a single tile */