From 7118e9779c6bdc0fa86f52325bdc8b507393608b Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Mon, 6 Sep 2021 17:32:12 +0200 Subject: [PATCH] GameStarter wrap and shape aware (#5107) - Starting locations code aware of wrap and map shape - RiverGenerator wrap aware - RiverGenerator guard against endless loop --- core/src/com/unciv/logic/GameStarter.kt | 34 ++-- core/src/com/unciv/logic/HexMath.kt | 34 +++- core/src/com/unciv/logic/map/TileInfo.kt | 24 ++- core/src/com/unciv/logic/map/TileMap.kt | 2 +- .../logic/map/mapgenerator/MapGenerator.kt | 39 +---- .../logic/map/mapgenerator/RiverGenerator.kt | 162 ++++++++++++------ core/src/com/unciv/ui/tilegroups/TileGroup.kt | 1 - 7 files changed, 179 insertions(+), 117 deletions(-) diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index db9871ebae..c71133ba0a 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -1,6 +1,5 @@ package com.unciv.logic -import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.civilization.* @@ -9,12 +8,12 @@ import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.logic.map.mapgenerator.MapGenerator import com.unciv.models.metadata.GameParameters +import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Era import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache import com.unciv.models.ruleset.tile.ResourceType -import com.unciv.models.metadata.GameSetupInfo import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -167,7 +166,7 @@ object GameStarter { availableCivNames.addAll(ruleset.nations.filter { it.value.isMajorCiv() }.keys.shuffled()) availableCivNames.removeAll(newGameParameters.players.map { it.chosenCiv }) availableCivNames.remove(Constants.barbarians) - + val startingTechs = ruleset.technologies.values.filter { it.uniques.contains("Starting tech") } if (!newGameParameters.noBarbarians && ruleset.nations.containsKey(Constants.barbarians)) { @@ -256,7 +255,7 @@ object GameStarter { // An unusually bad spawning location addConsolationPrize(gameInfo, startingLocation, 45 - startingLocation.getTileStartScore().toInt()) } - + if(civ.isCityState()) addCityStateLuxury(gameInfo, startingLocation) @@ -271,19 +270,19 @@ object GameStarter { fun placeNearStartingPosition(unitName: String) { civ.placeUnitNearTile(startingLocation.position, unitName) } - + // Determine starting units based on starting era startingUnits = ruleSet.eras[startingEra]!!.getStartingUnits().toMutableList() eraUnitReplacement = ruleSet.eras[startingEra]!!.startingMilitaryUnit - + // Add extra units granted by difficulty startingUnits.addAll(when { civ.isPlayerCivilization() -> gameInfo.getDifficulty().playerBonusStartingUnits civ.isMajorCiv() -> gameInfo.getDifficulty().aiMajorCivBonusStartingUnits else -> gameInfo.getDifficulty().aiCityStateBonusStartingUnits }) - - + + fun getEquivalentUnit(civ: CivilizationInfo, unitParam: String): String? { var unit = unitParam // We want to change it and this is the easiest way to do so if (unit == Constants.eraSpecificUnit) unit = eraUnitReplacement @@ -315,11 +314,11 @@ object GameStarter { startingUnits.clear() startingUnits.add(startingSettlers.random()) } - + // One city challengers should spawn with one settler only regardless of era and difficulty if (civ.playerType == PlayerType.Human && gameInfo.gameParameters.oneCityChallenge) { val startingSettlers = startingUnits.filter { settlerLikeUnits.contains(it) } - + startingUnits.removeAll(startingSettlers) startingUnits.add(startingSettlers.random()) } @@ -358,8 +357,10 @@ object GameStarter { for (minimumDistanceBetweenStartingLocations in tileMap.tileMatrix.size / 4 downTo 0) { val freeTiles = landTilesInBigEnoughGroup - .filter { vectorIsAtLeastNTilesAwayFromEdge(it.position, (minimumDistanceBetweenStartingLocations * 2) /3, tileMap) } - .toMutableList() + .filter { + HexMath.getDistanceFromEdge(it.position, tileMap.mapParameters) >= + (minimumDistanceBetweenStartingLocations * 2) /3 + }.toMutableList() val startingLocations = HashMap() for (civ in civsOrderedByAvailableLocations) { @@ -446,13 +447,4 @@ object GameStarter { } } } - - private fun vectorIsAtLeastNTilesAwayFromEdge(vector: Vector2, n: Int, tileMap: TileMap): Boolean { - // Since all maps are HEXAGONAL, the easiest way of checking if a tile is n steps away from the - // edge is checking the distance to the CENTER POINT - // Can't believe we used a dumb way of calculating this before! - val hexagonalRadius = -tileMap.leftX - val distanceFromCenter = HexMath.getDistance(vector, Vector2.Zero) - return hexagonalRadius - distanceFromCenter >= n - } } diff --git a/core/src/com/unciv/logic/HexMath.kt b/core/src/com/unciv/logic/HexMath.kt index 85cf98f82d..2529aa4ccc 100644 --- a/core/src/com/unciv/logic/HexMath.kt +++ b/core/src/com/unciv/logic/HexMath.kt @@ -2,6 +2,8 @@ package com.unciv.logic import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector3 +import com.unciv.logic.map.MapParameters +import com.unciv.logic.map.MapShape import kotlin.math.* @Suppress("MemberVisibilityCanBePrivate", "unused") // this is a library offering optional services @@ -20,7 +22,7 @@ object HexMath { if (size < 0) return 0 return 1 + 6 * size * (size + 1) / 2 } - + /** Almost inverse of [getNumberOfTilesInHexagon] - get equivalent fractional Hexagon radius for an Area */ fun getHexagonalRadiusForArea(numberOfTiles: Int) = if (numberOfTiles < 1) 0f else ((sqrt(12f * numberOfTiles - 3) - 3) / 6) @@ -194,4 +196,34 @@ object HexMath { fun getClockDirectionToWorldVector(clockDirection: Int): Vector2 = clockToWorldVectors[clockDirection] ?: Vector2.Zero + + fun getDistanceFromEdge(vector: Vector2, mapParameters: MapParameters): Int { + val x = vector.x.toInt() + val y = vector.y.toInt() + if (mapParameters.shape == MapShape.rectangular) { + val height = mapParameters.mapSize.height + val width = mapParameters.mapSize.width + val left = if (mapParameters.worldWrap) Int.MAX_VALUE else width / 2 - (x - y) + val right = if (mapParameters.worldWrap) Int.MAX_VALUE else (width - 1) / 2 - (y - x) + val top = height / 2 - (x + y) / 2 + // kotlin's Int division rounds in different directions depending on sign! Thus 1 extra `-1` + val bottom = (x + y - 1) / 2 + (height - 1) / 2 + return min(min(left, right), min(top, bottom)) + } else { + val radius = mapParameters.mapSize.radius + if (mapParameters.worldWrap) { + // The non-wrapping method holds in the upper two and lower two 'triangles' of the hexagon + // but needs special casing for left and right 'wedges', where only distance from the + // 'choke points' counts (upper and lower hex at the 'seam' where height is smallest). + // These are at (radius,0) and (0,-radius) + if (x.sign == y.sign) return radius - getDistance(vector, Vector2.Zero) + // left wedge - the 'choke points' are not wrapped relative to us + if (x > 0) return min(getDistance(vector, Vector2(radius.toFloat(),0f)), getDistance(vector, Vector2(0f, -radius.toFloat()))) + // right wedge - compensate wrap by using a hex 1 off along the edge - same result + return min(getDistance(vector, Vector2(1f, radius.toFloat())), getDistance(vector, Vector2(-radius.toFloat(), -1f))) + } else { + return radius - getDistance(vector, Vector2.Zero) + } + } + } } diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 75a921f7e2..1e7a7add73 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -276,24 +276,27 @@ open class TileInfo { fun getTileStartScore(): Float { var sum = 0f for (tile in getTilesInDistance(2)) { - if (tile == this) - continue - sum += tile.getTileStartYield() + val tileYield = tile.getTileStartYield(tile == this) + sum += tileYield if (tile in neighbors) - sum += tile.getTileStartYield() + sum += tileYield } if (isHill()) - sum -= 2 + sum -= 2f if (isAdjacentToRiver()) - sum += 2 + sum += 2f if (neighbors.any { it.baseTerrain == Constants.mountain }) - sum += 2 + sum += 2f + if (isCoastalTile()) + sum += 3f + if (!isCoastalTile() && neighbors.any { it.isCoastalTile() }) + sum -= 7f return sum } - private fun getTileStartYield(): Float { + private fun getTileStartYield(isCenter: Boolean): Float { var stats = getBaseTerrain().clone() for (terrainFeatureBase in getTerrainFeatures()) { @@ -303,7 +306,12 @@ open class TileInfo { stats.add(terrainFeatureBase) } if (resource != null) stats.add(getTileResource()) + if (stats.production < 0) stats.production = 0f + if (isCenter) { + if (stats.food < 2) stats.food = 2f + if (stats.production < 1) stats.production = 1f + } return stats.food + stats.production + stats.gold } diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index 1e6edb7ea0..044993a2b6 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -195,7 +195,7 @@ class TileMap { }.filterNotNull() /** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */ - private fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? { + fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? { if (contains(x, y)) return get(x, y) diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index b29db6efd7..6cfa4d65ba 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -1,6 +1,5 @@ package com.unciv.logic.map.mapgenerator -import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.logic.HexMath import com.unciv.logic.map.* @@ -9,7 +8,10 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.TerrainType -import kotlin.math.* +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.pow +import kotlin.math.sign import kotlin.random.Random @@ -19,7 +21,7 @@ class MapGenerator(val ruleset: Ruleset) { const val consoleOutput = false private const val consoleTimings = false } - + private var randomness = MapGenerationRandomness() fun generateMap(mapParameters: MapParameters): TileMap { @@ -73,7 +75,7 @@ class MapGenerator(val ruleset: Ruleset) { NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map) } runAndMeasure("RiverGenerator") { - RiverGenerator(randomness).spawnRivers(map) + RiverGenerator(map, randomness).spawnRivers() } runAndMeasure("spreadResources") { spreadResources(map) @@ -459,7 +461,7 @@ class MapGenerator(val ruleset: Ruleset) { } -class MapGenerationRandomness{ +class MapGenerationRandomness { var RNG = Random(42) fun seedRNG(seed: Long = 42) { @@ -532,30 +534,3 @@ class MapGenerationRandomness{ throw Exception() } } - - -class RiverCoordinate(val position: Vector2, val bottomRightOrLeft: BottomRightOrLeft) { - enum class BottomRightOrLeft { - /** 7 O'Clock of the tile */ - BottomLeft, - - /** 5 O'Clock of the tile */ - BottomRight - } - - fun getAdjacentPositions(): 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 - return if (bottomRightOrLeft == BottomRightOrLeft.BottomLeft) { - sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomRight), // same tile, other side - RiverCoordinate(position.cpy().add(1f, 0f), BottomRightOrLeft.BottomRight), // tile to MY top-left, take its bottom right corner - RiverCoordinate(position.cpy().add(0f, -1f), BottomRightOrLeft.BottomRight) // Tile to MY bottom-left, take its bottom right - ) - } else { - sequenceOf(RiverCoordinate(position, BottomRightOrLeft.BottomLeft), // same tile, other side - RiverCoordinate(position.cpy().add(0f, 1f), BottomRightOrLeft.BottomLeft), // tile to MY top-right, take its bottom left - RiverCoordinate(position.cpy().add(-1f, 0f), BottomRightOrLeft.BottomLeft) // tile to MY bottom-right, take its bottom left - ) - } - } -} diff --git a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt index 738aca1253..79f22bff60 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/RiverGenerator.kt @@ -1,111 +1,167 @@ package com.unciv.logic.map.mapgenerator +import com.badlogic.gdx.math.Vector2 import com.unciv.Constants import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap -class RiverGenerator(val randomness: MapGenerationRandomness) { +class RiverGenerator( + private val tileMap: TileMap, + private val randomness: MapGenerationRandomness +) { + companion object{ + const val MAP_TILES_PER_RIVER = 100 + const val MIN_RIVER_LENGTH = 5 + const val MAX_RIVER_LENGTH = 666 // Do not set < max map radius + } - fun spawnRivers(map: TileMap) { - val numberOfRivers = map.values.count { it.isLand } / 100 + fun spawnRivers() { + val numberOfRivers = tileMap.values.count { it.isLand } / MAP_TILES_PER_RIVER - var optionalTiles = map.values.asSequence() - .filter { it.baseTerrain == Constants.mountain && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }.toMutableList() + var optionalTiles = tileMap.values.asSequence() + .filter { it.baseTerrain == Constants.mountain && it.isFarEnoughFromWater() }.toMutableList() if (optionalTiles.size < numberOfRivers) - optionalTiles.addAll(map.values.filter { it.isHill() && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }) + optionalTiles.addAll(tileMap.values.filter { it.isHill() && it.isFarEnoughFromWater() }) if (optionalTiles.size < numberOfRivers) - optionalTiles = map.values.filter { it.isLand && it.aerialDistanceTo(getClosestWaterTile(it)) > 4 }.toMutableList() + optionalTiles = tileMap.values.filter { it.isLand && it.isFarEnoughFromWater() }.toMutableList() - val mapRadius = map.mapParameters.mapSize.radius + val mapRadius = tileMap.mapParameters.mapSize.radius val riverStarts = randomness.chooseSpreadOutLocations(numberOfRivers, optionalTiles, mapRadius) - for (tile in riverStarts) spawnRiver(tile, map) + for (tile in riverStarts) spawnRiver(tile) - for (tile in map.values) { + for (tile in tileMap.values) { if (tile.isAdjacentToRiver()) { - if (tile.baseTerrain == Constants.desert && tile.terrainFeatures.isEmpty()) tile.terrainFeatures.add(Constants.floodPlains) - else if (tile.baseTerrain == Constants.snow) tile.baseTerrain = Constants.tundra - else if (tile.baseTerrain == Constants.tundra) tile.baseTerrain = Constants.plains + when { + tile.baseTerrain == Constants.desert && tile.terrainFeatures.isEmpty() -> + tile.terrainFeatures.add(Constants.floodPlains) + tile.baseTerrain == Constants.snow -> tile.baseTerrain = Constants.tundra + tile.baseTerrain == Constants.tundra -> tile.baseTerrain = Constants.plains + } tile.setTerrainTransients() } } } - private fun getClosestWaterTile(tile: TileInfo): TileInfo { - var distance = 1 - while (true) { - val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater } - if (waterTiles.none()) { - distance++ - continue - } - return waterTiles.toList().random(randomness.RNG) + private fun TileInfo.isFarEnoughFromWater(): Boolean { + for (distance in 1 until MIN_RIVER_LENGTH) { + if (getTilesAtDistance(distance).any { it.isWater }) return false } + return true } - private fun spawnRiver(initialPosition: TileInfo, map: TileMap) { + private fun getClosestWaterTile(tile: TileInfo): TileInfo { + for (distance in 1..MAX_RIVER_LENGTH) { + val waterTiles = tile.getTilesAtDistance(distance).filter { it.isWater } + if (waterTiles.any()) + return waterTiles.toList().random(randomness.RNG) + } + throw IllegalStateException() + } + + private fun spawnRiver(initialPosition: TileInfo) { // Recommendation: Draw a bunch of hexagons on paper before trying to understand this, it's super helpful! val endPosition = getClosestWaterTile(initialPosition) var riverCoordinate = RiverCoordinate(initialPosition.position, RiverCoordinate.BottomRightOrLeft.values().random(randomness.RNG)) - - while (getAdjacentTiles(riverCoordinate, map).none { it.isWater }) { - val possibleCoordinates = riverCoordinate.getAdjacentPositions() - .filter { map.contains(it.position) } + for (step in 1..MAX_RIVER_LENGTH) { // Arbitrary max on river length, otherwise this will go in circles - rarely + if (riverCoordinate.getAdjacentTiles(tileMap).any { it.isWater }) return + val possibleCoordinates = riverCoordinate.getAdjacentPositions(tileMap) if (possibleCoordinates.none()) return // end of the line val newCoordinate = possibleCoordinates - .groupBy { - getAdjacentTiles(it, map).map { it.aerialDistanceTo(endPosition) } + .groupBy { newCoordinate -> + newCoordinate.getAdjacentTiles(tileMap).map { it.aerialDistanceTo(endPosition) } .minOrNull()!! } .minByOrNull { it.key }!! .component2().random(randomness.RNG) // set new rivers in place - val riverCoordinateTile = map[riverCoordinate.position] + val riverCoordinateTile = tileMap[riverCoordinate.position] if (newCoordinate.position == riverCoordinate.position) // same tile, switched right-to-left riverCoordinateTile.hasBottomRiver = true else if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomRight) { - if (getAdjacentTiles(newCoordinate, map).contains(riverCoordinateTile)) // moved from our 5 O'Clock to our 3 O'Clock + 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! - map[newCoordinate.position].hasBottomLeftRiver = true + tileMap[newCoordinate.position].hasBottomLeftRiver = true } else { // riverCoordinate.bottomRightOrLeft==RiverCoordinate.BottomRightOrLeft.Left - if (getAdjacentTiles(newCoordinate, map).contains(riverCoordinateTile)) // moved from our 7 O'Clock to our 9 O'Clock + 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 - map[newCoordinate.position].hasBottomRightRiver = true + tileMap[newCoordinate.position].hasBottomRightRiver = true } riverCoordinate = newCoordinate } - + println("River reached max length!") } - fun getAdjacentTiles(riverCoordinate: RiverCoordinate, map: TileMap): Sequence { - val potentialPositions = sequenceOf( - riverCoordinate.position, - riverCoordinate.position.cpy().add(-1f, -1f), // tile directly below us, - if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) - riverCoordinate.position.cpy().add(0f, -1f) // tile to our bottom-left - else riverCoordinate.position.cpy().add(-1f, 0f) // tile to our bottom-right - ) - return potentialPositions.map { if (map.contains(it)) map[it] else null }.filterNotNull() - } - - fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate, map: TileMap): Int { +/* + fun numberOfConnectedRivers(riverCoordinate: RiverCoordinate): Int { var sum = 0 - if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRiver) sum += 1 + if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRiver) sum += 1 if (riverCoordinate.bottomRightOrLeft == RiverCoordinate.BottomRightOrLeft.BottomLeft) { - if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomLeftRiver) sum += 1 + if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomLeftRiver) sum += 1 val bottomLeftTilePosition = riverCoordinate.position.cpy().add(0f, -1f) - if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomRightRiver) sum += 1 + if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomRightRiver) sum += 1 } else { - if (map.contains(riverCoordinate.position) && map[riverCoordinate.position].hasBottomRightRiver) sum += 1 + if (tileMap.contains(riverCoordinate.position) && tileMap[riverCoordinate.position].hasBottomRightRiver) sum += 1 val bottomLeftTilePosition = riverCoordinate.position.cpy().add(-1f, 0f) - if (map.contains(bottomLeftTilePosition) && map[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1 + if (tileMap.contains(bottomLeftTilePosition) && tileMap[bottomLeftTilePosition].hasBottomLeftRiver) sum += 1 } return sum } +*/ -} \ No newline at end of file + /** 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) { + enum class BottomRightOrLeft { + /** 7 O'Clock of the tile */ + BottomLeft, + + /** 5 O'Clock of the tile */ + BottomRight + } + + /** 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 { + // 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) + 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) + if (myBottomLeft != null) + yield(RiverCoordinate(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) + 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) + if (myBottomRight != null) + yield(RiverCoordinate(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) + } + } +} diff --git a/core/src/com/unciv/ui/tilegroups/TileGroup.kt b/core/src/com/unciv/ui/tilegroups/TileGroup.kt index e853ac2c7f..3d99b29edd 100644 --- a/core/src/com/unciv/ui/tilegroups/TileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/TileGroup.kt @@ -11,7 +11,6 @@ import com.unciv.UncivGame import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.TileInfo -import com.unciv.logic.map.TileMap import com.unciv.ui.cityscreen.YieldGroup import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.center