From d758da4d11ddc54fecbce715cb7283b1d0e6d1ba Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Mon, 4 Sep 2023 13:36:52 +0200 Subject: [PATCH] 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 --- .../logic/map/mapgenerator/MapGenerator.kt | 57 ++++--- .../logic/map/mapgenerator/RiverGenerator.kt | 161 ++++++++++-------- .../mapeditorscreen/MapEditorScreen.kt | 11 +- .../mapeditorscreen/tabs/MapEditorEditTab.kt | 33 ++-- 4 files changed, 151 insertions(+), 111 deletions(-) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 64d6351581..b0eaee60f3 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -33,6 +33,13 @@ import kotlin.math.ulp 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) { companion object { @@ -138,7 +145,7 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc runAndMeasure("RiverGenerator") { RiverGenerator(map, randomness, ruleset).spawnRivers() } - convertTerrains(map, ruleset) + convertTerrains(map.values) // Region based map generation - not used when generating maps in map editor if (civilizations.isNotEmpty()) { @@ -182,24 +189,27 @@ class MapGenerator(val ruleset: Ruleset, private val coroutineScope: CoroutineSc randomness.seedRNG(map.mapParameters.seed) - when(step) { - MapGeneratorSteps.None -> return - MapGeneratorSteps.All -> throw IllegalArgumentException("MapGeneratorSteps.All cannot be used in generateSingleStep") - MapGeneratorSteps.Landmass -> MapLandmassGenerator(ruleset, randomness).generateLand(map) - MapGeneratorSteps.Elevation -> raiseMountainsAndHills(map) - MapGeneratorSteps.HumidityAndTemperature -> applyHumidityAndTemperature(map) - MapGeneratorSteps.LakesAndCoast -> spawnLakesAndCoasts(map) - MapGeneratorSteps.Vegetation -> spawnVegetation(map) - MapGeneratorSteps.RareFeatures -> spawnRareFeatures(map) - MapGeneratorSteps.Ice -> spawnIce(map) - MapGeneratorSteps.Continents -> map.assignContinents(TileMap.AssignContinentsMode.Reassign) - MapGeneratorSteps.NaturalWonders -> NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) - MapGeneratorSteps.Rivers -> { - RiverGenerator(map, randomness, ruleset).spawnRivers() - convertTerrains(map, ruleset) + runAndMeasure("SingleStep $step") { + when (step) { + MapGeneratorSteps.None -> Unit + MapGeneratorSteps.All -> throw IllegalArgumentException("MapGeneratorSteps.All cannot be used in generateSingleStep") + MapGeneratorSteps.Landmass -> MapLandmassGenerator(ruleset, randomness).generateLand(map) + MapGeneratorSteps.Elevation -> raiseMountainsAndHills(map) + MapGeneratorSteps.HumidityAndTemperature -> applyHumidityAndTemperature(map) + MapGeneratorSteps.LakesAndCoast -> spawnLakesAndCoasts(map) + MapGeneratorSteps.Vegetation -> spawnVegetation(map) + MapGeneratorSteps.RareFeatures -> spawnRareFeatures(map) + MapGeneratorSteps.Ice -> spawnIce(map) + MapGeneratorSteps.Continents -> map.assignContinents(TileMap.AssignContinentsMode.Reassign) + MapGeneratorSteps.NaturalWonders -> NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) + MapGeneratorSteps.Rivers -> { + val resultingTiles = mutableSetOf() + 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)) } - private fun convertTerrains(map: TileMap, ruleset: Ruleset) { - for (tile in map.values) { + fun convertTerrains(tiles: Iterable) { + for (tile in tiles) { val conversionUnique = tile.getBaseTerrain().getMatchingUniques(UniqueType.ChangesTerrain) .firstOrNull { tile.isAdjacentTo(it.params[1]) } ?: 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) - else tile.baseTerrain = terrain.name + } else + tile.baseTerrain = terrain.name tile.setTerrainTransients() } } diff --git a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt index a1e1464bb1..153f657a66 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt @@ -2,10 +2,10 @@ package com.unciv.logic.map.mapgenerator import com.badlogic.gdx.math.Vector2 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.tile.Tile import com.unciv.models.ruleset.Ruleset +import com.unciv.utils.debug import kotlin.math.roundToInt class RiverGenerator( @@ -17,7 +17,7 @@ class RiverGenerator( private val minRiverLength = ruleset.modOptions.constants.minRiverLength private val maxRiverLength = ruleset.modOptions.constants.maxRiverLength - fun spawnRivers() { + fun spawnRivers(resultingTiles: MutableSet? = null) { if (tileMap.values.none { it.isWater }) return val numberOfRivers = (tileMap.values.count { it.isLand } * riverCountMultiplier).roundToInt() @@ -33,7 +33,7 @@ class RiverGenerator( val mapRadius = tileMap.mapParameters.mapSize.radius val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius) - for (tile in riverStarts) spawnRiver(tile) + for (tile in riverStarts) spawnRiver(tile, resultingTiles) } private fun Tile.isFarEnoughFromWater(): Boolean { @@ -52,71 +52,47 @@ class RiverGenerator( return null } - private fun spawnRiver(initialPosition: Tile) { + private fun spawnRiver(initialPosition: Tile, resultingTiles: MutableSet?) { val endPosition = getClosestWaterTile(initialPosition) ?: error("No water found for river destination") - spawnRiver(initialPosition, endPosition) + spawnRiver(initialPosition, endPosition, resultingTiles) } - fun spawnRiver(initialPosition: Tile, endPosition: Tile, resultingTiles: MutableSet? = 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?) { // 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)) repeat(maxRiverLength) { // Arbitrary max on river length, otherwise this will go in circles - rarely - val riverCoordinateTile = tileMap[riverCoordinate.position] - resultingTiles?.add(riverCoordinateTile) - if (riverCoordinate.getAdjacentTiles(tileMap).any { it.isWater }) return - val possibleCoordinates = riverCoordinate.getAdjacentPositions(tileMap) + if (riverCoordinate.getAdjacentTiles().any { it.isWater }) return + val possibleCoordinates = riverCoordinate.getAdjacentPositions() if (possibleCoordinates.none()) return // end of the line val newCoordinate = possibleCoordinates .groupBy { newCoordinate -> - newCoordinate.getAdjacentTiles(tileMap).map { it.aerialDistanceTo(endPosition) } + newCoordinate.getAdjacentTiles().map { it.aerialDistanceTo(endPosition) } .minOrNull()!! } .minByOrNull { it.key }!! .component2().random(randomness.RNG) - // set new rivers in place - if (newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left - riverCoordinateTile.hasBottomRiver = true - 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 - } + // set one new river edge in place + riverCoordinate.paintTo(newCoordinate, resultingTiles) + // Move on riverCoordinate = newCoordinate } 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 * 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 { /** 7 O'Clock of the tile */ BottomLeft, @@ -125,43 +101,92 @@ class RiverGenerator( 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 * (yes some positions on the map's outer border will be included, some not) */ - fun getAdjacentPositions(tileMap: TileMap): Sequence = sequence { + fun getAdjacentPositions(): Sequence = 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 // 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) { - yield(RiverCoordinate(position, BottomRightOrLeft.BottomRight)) // same tile, other side - val myTopLeft = tileMap.getIfTileExistsOrNull(x + 1, y) + yield(RiverCoordinate(tileMap, position, BottomRightOrLeft.BottomRight)) // same tile, other side if (myTopLeft != null) - yield(RiverCoordinate(myTopLeft.position, BottomRightOrLeft.BottomRight)) // tile to MY top-left, take its bottom right corner - val myBottomLeft = tileMap.getIfTileExistsOrNull(x, y - 1) + yield(RiverCoordinate(tileMap, myTopLeft!!.position, BottomRightOrLeft.BottomRight)) // tile to MY top-left, take its bottom right corner 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 { - yield(RiverCoordinate(position, BottomRightOrLeft.BottomLeft)) // same tile, other side - val myTopRight = tileMap.getIfTileExistsOrNull(x, y + 1) + yield(RiverCoordinate(tileMap, position, BottomRightOrLeft.BottomLeft)) // same tile, other side if (myTopRight != null) - yield(RiverCoordinate(myTopRight.position, BottomRightOrLeft.BottomLeft)) // tile to MY top-right, take its bottom left - val myBottomRight = tileMap.getIfTileExistsOrNull(x - 1, y) + yield(RiverCoordinate(tileMap, myTopRight!!.position, BottomRightOrLeft.BottomLeft)) // tile to MY top-right, take its bottom left 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 */ - fun getAdjacentTiles(tileMap: TileMap): Sequence = sequence { - val x = position.x.toInt() - val y = position.y.toInt() - yield(tileMap[x, y]) - val below = tileMap.getIfTileExistsOrNull(x - 1, y - 1) // tile directly below us, - if (below != null) yield(below) - val leftOrRight = if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) - 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 getAdjacentTiles(): Sequence = sequence { + yield(myTile) + myBottomCenter?.let { yield(it) } // tile directly below us, + if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) + myBottomLeft?.let { yield(it) } // tile to our bottom-left + else + myBottomRight?.let { yield(it) } // tile to our bottom-right } + + fun paintTo(newCoordinate: RiverCoordinate, resultingTiles: MutableSet?) { + 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?) { + myTile.hasBottomRiver = true + if (resultingTiles == null) return + resultingTiles.add(myTile) + myBottomCenter?.let { resultingTiles.add(it) } + } + private fun paintBottomLeft(resultingTiles: MutableSet?) { + myTile.hasBottomLeftRiver = true + if (resultingTiles == null) return + resultingTiles.add(myTile) + myBottomLeft?.let { resultingTiles.add(it) } + } + private fun paintBottomRight(resultingTiles: MutableSet?) { + 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 } } } diff --git a/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt b/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt index 3ae832da21..f77ef49dfc 100644 --- a/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt +++ b/core/src/com/unciv/ui/screens/mapeditorscreen/MapEditorScreen.kt @@ -21,17 +21,17 @@ import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Ruleset 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.KeyShortcutDispatcherVeto 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.popups.ConfirmPopup 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.mapeditorscreen.tabs.MapEditorOptionsTab import com.unciv.ui.screens.worldscreen.ZoomButtonPair import com.unciv.utils.Concurrency import com.unciv.utils.Dispatcher @@ -47,7 +47,6 @@ import kotlinx.coroutines.Job //todo Synergy with Civilopedia for drawing loose tiles / terrain icons //todo left-align everything so a half-open drawer is more useful //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 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? diff --git a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorEditTab.kt b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorEditTab.kt index f894e87c41..37eb44be52 100644 --- a/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorEditTab.kt +++ b/core/src/com/unciv/ui/screens/mapeditorscreen/tabs/MapEditorEditTab.kt @@ -7,16 +7,17 @@ import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.logic.map.BFS 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.tile.Tile import com.unciv.models.ruleset.Ruleset import com.unciv.models.translations.tr -import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.UncivSlider 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.input.KeyCharAndCode +import com.unciv.ui.components.input.keyShortcuts import com.unciv.ui.images.ImageGetter import com.unciv.ui.popups.ToastPopup import com.unciv.ui.screens.basescreen.BaseScreen @@ -211,7 +212,7 @@ class MapEditorEditTab( riverEndTile = tile if (riverStartTile != null) return paintRiverFromTo() } - tilesToHighlight.forEach { editorScreen.highlightTile(it, Color.BLUE) } + for (tileToHighlight in tilesToHighlight) editorScreen.highlightTile(tileToHighlight, Color.BLUE) } private fun paintRiverFromTo() { val resultingTiles = mutableSetOf() @@ -219,6 +220,7 @@ class MapEditorEditTab( try { val riverGenerator = RiverGenerator(editorScreen.tileMap, randomness, ruleset) riverGenerator.spawnRiver(riverStartTile!!, riverEndTile!!, resultingTiles) + MapGenerator(ruleset).convertTerrains(resultingTiles) } catch (ex: Exception) { Log.error("Exception while generating rivers", ex) ToastPopup("River generation failed!", editorScreen) @@ -226,7 +228,7 @@ class MapEditorEditTab( riverStartTile = null riverEndTile = null editorScreen.isDirty = true - resultingTiles.forEach { editorScreen.updateAndHighlight(it, Color.SKY) } + for (tile in resultingTiles) editorScreen.updateAndHighlight(tile, Color.SKY) } internal fun paintTilesWithBrush(tile: Tile) { @@ -238,12 +240,12 @@ class MapEditorEditTab( } else { tile.getTilesInDistance(brushSize - 1) } - tiles.forEach { + for (tileToPaint in tiles) { when (brushHandlerType) { - BrushHandlerType.Direct -> directPaintTile(it) - BrushHandlerType.Tile -> paintTile(it) - BrushHandlerType.Road -> roadPaintTile(it) - BrushHandlerType.River -> riverPaintTile(it) + BrushHandlerType.Direct -> directPaintTile(tileToPaint) + BrushHandlerType.Tile -> paintTile(tileToPaint) + BrushHandlerType.Road -> roadPaintTile(tileToPaint) + BrushHandlerType.River -> riverPaintTile(tileToPaint) else -> {} // other cases can't reach here } } @@ -256,19 +258,22 @@ class MapEditorEditTab( 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) { directPaintTile(tile) - tile.neighbors.forEach { - if (it.position.x > tile.position.x || it.position.y > tile.position.y) - editorScreen.updateTile(it) + for (neighbor in tile.neighbors) { + if (neighbor.position.x > tile.position.x || neighbor.position.y > tile.position.y) + editorScreen.updateTile(neighbor) } } // Used for roads - same as paintTile but all neighbors need TileGroup.update too private fun roadPaintTile(tile: Tile) { if (!paintTile(tile)) return - tile.neighbors.forEach { editorScreen.updateTile(it) } + for (neighbor in tile.neighbors) editorScreen.updateTile(neighbor) } /** apply brush to a single tile */