From 3c2cb0116919c0e0e13efa6c4025171beabe9820 Mon Sep 17 00:00:00 2001 From: Kentalot Date: Sun, 23 Feb 2020 04:22:50 -0800 Subject: [PATCH] Earlier version with changes mostly to use Sequences (#1993) * Fixes Issue #1697 by adding information to the special production constructions. * Get rid of extra $ sign in the SpecialConstruction tooltips * Major refactor to use Sequences instead of List to try to improve logic whenever getting a list of tiles at a distance. * Get rid of extraneous parameter * get rid of extra exception. slight refactor placeUnitNearTile for readability * Fix bug of doing intersection instead of union * Add an extra method to get tiles in distance range * Update based on comments --- core/src/com/unciv/logic/GameInfo.kt | 7 +- .../com/unciv/logic/automation/Automation.kt | 48 +++--- .../logic/automation/BarbarianAutomation.kt | 4 +- .../unciv/logic/automation/BattleHelper.kt | 5 +- .../automation/SpecificUnitAutomation.kt | 77 +++++---- .../unciv/logic/automation/UnitAutomation.kt | 146 +++++++++--------- .../logic/automation/WorkerAutomation.kt | 22 +-- .../unciv/logic/city/CityExpansionManager.kt | 18 +-- core/src/com/unciv/logic/city/CityInfo.kt | 2 +- .../logic/civilization/CivilizationInfo.kt | 5 +- core/src/com/unciv/logic/map/MapUnit.kt | 43 +++--- core/src/com/unciv/logic/map/TileInfo.kt | 46 +++--- core/src/com/unciv/logic/map/TileMap.kt | 138 +++++++++-------- .../unciv/logic/map/UnitMovementAlgorithms.kt | 7 +- .../src/com/unciv/ui/cityscreen/CityScreen.kt | 2 +- .../unciv/ui/worldscreen/WorldMapHolder.kt | 28 ++-- 16 files changed, 309 insertions(+), 289 deletions(-) diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 045def2d97..cd14ddd747 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -164,11 +164,12 @@ class GameInfo { // Barbarians will only spawn in places that no one can see val allViewableTiles = civilizations.filterNot { it.isBarbarian() } .flatMap { it.viewableTiles }.toHashSet() - val tilesWithin3ofExistingEncampment = existingEncampments.flatMap { it.getTilesInDistance(3) } + val tilesWithin3ofExistingEncampment = existingEncampments.asSequence() + .flatMap { it.getTilesInDistance(3) }.toSet() val viableTiles = tileMap.values.filter { !it.getBaseTerrain().impassable && it.isLand - && it.terrainFeature==null - && it.naturalWonder==null + && it.terrainFeature == null + && it.naturalWonder == null && it !in tilesWithin3ofExistingEncampment && it !in allViewableTiles } diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 8bc6d4f0b5..c0e51368f8 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -14,14 +14,6 @@ import kotlin.math.sqrt class Automation { - internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float { - if (tile == null) return 0f - val stats = tile.getTileStats(null, civInfo) - var rank = rankStatsValue(stats, civInfo) - if (tile.improvement == null) rank += 0.5f // improvement potential! - if (tile.hasViewableResource(civInfo)) rank += 1.0f - return rank - } fun rankTileForCityWork(tile:TileInfo, city: CityInfo, foodWeight: Float = 1f): Float { val stats = tile.getTileStats(city, city.civInfo) @@ -63,20 +55,6 @@ class Automation { return rank } - fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float { - var rank = 0.0f - if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing - else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point - - if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold - else rank += stats.gold / 3 // 3 gold is much worse than 2 production - - rank += stats.production - rank += stats.science - rank += stats.culture - return rank - } - fun trainMilitaryUnit(city: CityInfo) { val name = chooseMilitaryUnit(city) city.cityConstructions.currentConstruction = name @@ -128,6 +106,32 @@ class Automation { } } + companion object { + + internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float { + if (tile == null) return 0f + val stats = tile.getTileStats(null, civInfo) + var rank = rankStatsValue(stats, civInfo) + if (tile.improvement == null) rank += 0.5f // improvement potential! + if (tile.hasViewableResource(civInfo)) rank += 1.0f + return rank + } + + @JvmStatic + fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float { + var rank = 0.0f + if (stats.food <= 2) rank += (stats.food * 1.2f) //food get more value to keep city growing + else rank += (2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point + + if (civInfo.gold < 0 && civInfo.statsForNextTurn.gold <= 0) rank += stats.gold + else rank += stats.gold / 3 // 3 gold is much worse than 2 production + + rank += stats.production + rank += stats.science + rank += stats.culture + return rank + } + } } enum class ThreatLevel{ diff --git a/core/src/com/unciv/logic/automation/BarbarianAutomation.kt b/core/src/com/unciv/logic/automation/BarbarianAutomation.kt index 4058a56625..e488aa4101 100644 --- a/core/src/com/unciv/logic/automation/BarbarianAutomation.kt +++ b/core/src/com/unciv/logic/automation/BarbarianAutomation.kt @@ -86,7 +86,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) { if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return // 6 - wander - UnitAutomation().wander(unit, unitDistanceToTiles) + UnitAutomation.wander(unit, unitDistanceToTiles) } private fun automateScout(unit: MapUnit) { @@ -114,7 +114,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) { if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return // 5 - wander - UnitAutomation().wander(unit, unitDistanceToTiles) + UnitAutomation.wander(unit, unitDistanceToTiles) } private fun findFurthestTileCanMoveTo( diff --git a/core/src/com/unciv/logic/automation/BattleHelper.kt b/core/src/com/unciv/logic/automation/BattleHelper.kt index c7637bef50..91e2cca579 100644 --- a/core/src/com/unciv/logic/automation/BattleHelper.kt +++ b/core/src/com/unciv/logic/automation/BattleHelper.kt @@ -63,9 +63,10 @@ class BattleHelper { val tilesInAttackRange = if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit()) reachableTile.getTilesInDistance(rangeOfAttack) - else reachableTile.getViewableTiles(rangeOfAttack, unit.type.isWaterUnit()) + else reachableTile.getViewableTilesList(rangeOfAttack, unit.type.isWaterUnit()) + .asSequence() - attackableTiles += tilesInAttackRange.asSequence().filter { it in tilesWithEnemies } + attackableTiles += tilesInAttackRange.filter { it in tilesWithEnemies } .map { AttackableTile(reachableTile, it) } } return attackableTiles diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 0adc86a6b1..061e898c56 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -37,7 +37,7 @@ class SpecificUnitAutomation{ return createImprovementAction() } } - else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles()) + else UnitAutomation.tryExplore(unit, unit.movement.getDistanceToTiles()) } fun automateGreatGeneral(unit: MapUnit){ @@ -72,14 +72,13 @@ class SpecificUnitAutomation{ fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map, luxuryResourcesInCivArea: Sequence): Float { val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2) - .asSequence() .sortedByDescending { nearbyTileRankings[it] }.take(2) .toList() val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer) .asSequence() .sortedByDescending { nearbyTileRankings[it] } .take(5) - var rank = top5Tiles.map { nearbyTileRankings[it]!! }.sum() + var rank = top5Tiles.map { nearbyTileRankings.getValue(it) }.sum() if (tileInfo.isCoastalTile()) rank += 5 val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null } @@ -94,9 +93,9 @@ class SpecificUnitAutomation{ fun automateSettlerActions(unit: MapUnit) { - if(unit.getTile().militaryUnit==null) return // Don't move until you're accompanied by a military unit + if (unit.getTile().militaryUnit == null) return // Don't move until you're accompanied by a military unit - val tilesNearCities = unit.civInfo.gameInfo.getCities() + val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence() .flatMap { val distanceAwayFromCity = if (unit.civInfo.knows(it.civInfo) @@ -106,34 +105,36 @@ class SpecificUnitAutomation{ else 3 it.getCenterTile().getTilesInDistance(distanceAwayFromCity) } - .toHashSet() + .toSet() // This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once. val nearbyTileRankings = unit.getTile().getTilesInDistance(7) - .associateBy ( {it},{ Automation().rankTile(it,unit.civInfo) }) + .associateBy({ it }, { Automation.rankTile(it, unit.civInfo) }) val possibleCityLocations = unit.getTile().getTilesInDistance(5) - .filter { val tileOwner=it.getOwner() - it.isLand && (tileOwner==null || tileOwner==unit.civInfo) && // don't allow settler to settle inside other civ's territory - (unit.movement.canMoveTo(it) || unit.currentTile==it) - && it !in tilesNearCities } + .filter { + val tileOwner = it.getOwner() + it.isLand && (tileOwner == null || tileOwner == unit.civInfo) && // don't allow settler to settle inside other civ's territory + (unit.movement.canMoveTo(it) || unit.currentTile == it) + && it !in tilesNearCities + } val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence() - .flatMap { it.getTiles().asSequence() }.filter { it.resource!=null } - .map { it.getTileResource() }.filter { it.resourceType==ResourceType.Luxury } + .flatMap { it.getTiles().asSequence() }.filter { it.resource != null } + .map { it.getTileResource() }.filter { it.resourceType == ResourceType.Luxury } .distinct() val bestCityLocation: TileInfo? = possibleCityLocations - .asSequence() .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) } .firstOrNull { unit.movement.canReach(it) } - if(bestCityLocation==null) { // We got a badass over here, all tiles within 5 are taken? Screw it, random walk. - if (UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles())) return // try to find new areas - UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly + if (bestCityLocation == null) { // We got a badass over here, all tiles within 5 are taken? Screw it, random walk. + val unitDistanceToTiles = unit.movement.getDistanceToTiles() + if (UnitAutomation.tryExplore(unit, unitDistanceToTiles)) return // try to find new areas + UnitAutomation.wander(unit, unitDistanceToTiles) // go around aimlessly return } - if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) + if (bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) throw Exception("City within distance") if (unit.getTile() == bestCityLocation) @@ -169,9 +170,9 @@ class SpecificUnitAutomation{ && it.isLand && !it.isCityCenter() && it.resource==null } - .sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList() - val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } - if(chosenTile==null) continue // to another city + .sortedByDescending { Automation.rankTile(it,unit.civInfo) }.toList() + val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } ?: continue // to another city + unit.movement.headTowards(chosenTile) if(unit.currentTile==chosenTile && unit.currentMovement > 0) @@ -185,35 +186,41 @@ class SpecificUnitAutomation{ fun automateFighter(unit: MapUnit) { val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val enemyAirUnitsInRange = tilesInRange - .flatMap { it.airUnits }.filter { it.civInfo.isAtWarWith(unit.civInfo) } + .flatMap { it.airUnits.asSequence() }.filter { it.civInfo.isAtWarWith(unit.civInfo) } - if(enemyAirUnitsInRange.isNotEmpty()) return // we need to be on standby in case they attack - if(battleHelper.tryAttackNearbyEnemy(unit)) return + if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack + if (battleHelper.tryAttackNearbyEnemy(unit)) return // TODO Implement consideration for landing on aircraft carrier val immediatelyReachableCities = tilesInRange - .filter { it.isCityCenter() && it.getOwner()==unit.civInfo && unit.movement.canMoveTo(it)} + .filter { it.isCityCenter() && it.getOwner() == unit.civInfo && unit.movement.canMoveTo(it) } - for(city in immediatelyReachableCities){ - if(city.getTilesInDistance(unit.getRange()) - .any { battleHelper.containsAttackableEnemy(it,MapUnitCombatant(unit)) }) { + for (city in immediatelyReachableCities) { + if (city.getTilesInDistance(unit.getRange()) + .any { battleHelper.containsAttackableEnemy(it, MapUnitCombatant(unit)) }) { unit.movement.moveToTile(city) return } } val pathsToCities = unit.movement.getArialPathsToCities() - if(pathsToCities.isEmpty()) return // can't actually move anywhere else + if (pathsToCities.isEmpty()) return // can't actually move anywhere else val citiesByNearbyAirUnits = pathsToCities.keys - .groupBy { it.getTilesInDistance(unit.getRange()) - .count{it.airUnits.size>0 && it.airUnits.first().civInfo.isAtWarWith(unit.civInfo)} } + .groupBy { key -> + key.getTilesInDistance(unit.getRange()) + .count { + val firstAirUnit = it.airUnits.firstOrNull() + firstAirUnit != null && firstAirUnit.civInfo.isAtWarWith(unit.civInfo) + } + } - if(citiesByNearbyAirUnits.keys.any { it!=0 }){ + if (citiesByNearbyAirUnits.keys.any { it != 0 }) { val citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxBy { it.key }!!.value - val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities[it]!!.size }!! // city with min path = least turns to get there - val firstStepInPath = pathsToCities[chosenCity]!!.first() + //todo: maybe groupby size and choose highest priority within the same size turns + val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities.getValue(it).size }!! // city with min path = least turns to get there + val firstStepInPath = pathsToCities.getValue(chosenCity).first() unit.movement.moveToTile(firstStepInPath) return } @@ -255,6 +262,8 @@ class SpecificUnitAutomation{ } if (citiesThatCanAttackFrom.isEmpty()) return + //todo: this logic looks similar to some parts of automateFighter, maybe pull out common code + //todo: maybe groupby size and choose highest priority within the same size turns val closestCityThatCanAttackFrom = citiesThatCanAttackFrom.minBy { pathsToCities[it]!!.size }!! val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first() airUnit.movement.moveToTile(firstStepInPath) diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index c6d867921d..a67d48e6f8 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -3,10 +3,7 @@ package com.unciv.logic.automation import com.badlogic.gdx.graphics.Color import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.battle.Battle -import com.unciv.logic.battle.BattleDamage -import com.unciv.logic.battle.CityCombatant -import com.unciv.logic.battle.MapUnitCombatant +import com.unciv.logic.battle.* import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.GreatPersonManager import com.unciv.logic.civilization.diplomacy.DiplomaticStatus @@ -23,6 +20,41 @@ class UnitAutomation { companion object { const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5 const val CLOSE_ENEMY_TURNS_AWAY_LIMIT = 3f + + internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean { + if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true + + for (tile in unit.currentTile.getTilesInDistance(10)) + if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles + && unit.movement.canReach(tile) + && (tile.getOwner() == null || !tile.getOwner()!!.isCityState())) { + unit.movement.headTowards(tile) + return true + } + return false + } + + private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean { + if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins + val tileWithRuin = unitDistanceToTiles.keys + .firstOrNull { + it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it) + } + if (tileWithRuin == null) + return false + unit.movement.moveToTile(tileWithRuin) + return true + } + + @JvmStatic + fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) { + val reachableTiles = unitDistanceToTiles + .filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) } + + val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement } + if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first) + else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.keys.random()) + } } private val battleHelper = BattleHelper() @@ -162,10 +194,9 @@ class UnitAutomation { return true } - fun getBombardTargets(city: CityInfo): List { - return city.getCenterTile().getViewableTiles(city.range, true) - .filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) } - } + fun getBombardTargets(city: CityInfo): Sequence = + city.getCenterTile().getTilesInDistance(city.range) + .filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) } /** Move towards the closest attackable enemy of the [unit]. * @@ -180,7 +211,7 @@ class UnitAutomation { var closeEnemies = battleHelper.getAttackableEnemies( unit, unitDistanceToTiles, - tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT) + tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList() ).filter { // Ignore units that would 1-shot you if you attacked BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit), @@ -246,34 +277,35 @@ class UnitAutomation { if (closestReachableEnemyCity != null) { val unitDistanceToTiles = unit.movement.getDistanceToTiles() - val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2) - val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange } + val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet() - val suitableGatheringGroundTiles = closestReachableEnemyCity.getTilesAtDistance(4) - .union(closestReachableEnemyCity.getTilesAtDistance(3)) - .filter { it.isLand } // don't head straight to the city, try to head to landing grounds - // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary. - val tileToHeadTo = suitableGatheringGroundTiles - .sortedBy { it.arialDistanceTo(unit.currentTile) } - .firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity + val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4) + .filter { it.isLand } + .sortedBy { it.arialDistanceTo(unit.currentTile) } + .firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity if (tileToHeadTo !in tilesInBombardRange) // no need to worry, keep going as the movement alg. says unit.movement.headTowards(tileToHeadTo) else { if (unit.getRange() > 2) { // should never be in a bombardable position - val tilesCanAttackFromButNotInBombardRange = - reachableTilesNotInBombardRange.filter { it.arialDistanceTo(closestReachableEnemyCity) <= unit.getRange() } + val tileToMoveTo = + unitDistanceToTiles.asSequence() + .filter { it.key !in tilesInBombardRange + && it.key.arialDistanceTo(closestReachableEnemyCity) <= + unit.getRange() } + .minBy { it.value.totalDistance }?.key // move into position far away enough that the bombard doesn't hurt - if (tilesCanAttackFromButNotInBombardRange.any()) - unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!) + if (tileToMoveTo != null) + unit.movement.headTowards(tileToMoveTo) } else { // calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once) val militaryUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(3) .filter { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo } - .map { it.militaryUnit!! } + .map { it.militaryUnit }.filterNotNull() var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!) for (militaryUnit in militaryUnitsAroundEnemyCity) { @@ -290,28 +322,29 @@ class UnitAutomation { } fun tryBombardEnemy(city: CityInfo): Boolean { - if (!city.attackedThisTurn) { - val target = chooseBombardTarget(city) - if (target == null) return false - val enemy = Battle.getMapCombatantOfTile(target)!! - Battle.attack(CityCombatant(city), enemy) - return true + return when { + city.attackedThisTurn -> false + else -> { + val enemy = chooseBombardTarget(city) ?: return false + Battle.attack(CityCombatant(city), enemy) + true + } } - return false } - private fun chooseBombardTarget(city: CityInfo): TileInfo? { - var targets = getBombardTargets(city) - if (targets.isEmpty()) return null - val siegeUnits = targets - .filter { Battle.getMapCombatantOfTile(it)!!.getUnitType() == UnitType.Siege } - if (siegeUnits.any()) targets = siegeUnits - else { - val rangedUnits = targets - .filter { Battle.getMapCombatantOfTile(it)!!.getUnitType().isRanged() } - if (rangedUnits.any()) targets = rangedUnits - } - return targets.minBy { Battle.getMapCombatantOfTile(it)!!.getHealth() } + private fun chooseBombardTarget(city: CityInfo): ICombatant? { + val mappedTargets = getBombardTargets(city).map { Battle.getMapCombatantOfTile(it)!! } + .filter { + val unitType = it.getUnitType() + unitType == UnitType.Siege || unitType.isRanged() + } + .groupByTo(LinkedHashMap()) { it.getUnitType() } + + var targets = mappedTargets[UnitType.Siege]?.asSequence() + if (targets == null) + targets = mappedTargets.values.asSequence().flatMap { it.asSequence() } + + return targets.minBy { it.getHealth() } } private fun tryGarrisoningUnit(unit: MapUnit): Boolean { @@ -352,28 +385,6 @@ class UnitAutomation { return true } - private fun tryGoToRuin(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean { - if (!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins - val tileWithRuin = unitDistanceToTiles.keys - .firstOrNull { it.improvement == Constants.ancientRuins && unit.movement.canMoveTo(it) } - if (tileWithRuin == null) return false - unit.movement.moveToTile(tileWithRuin) - return true - } - - internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean { - if (tryGoToRuin(unit, unitDistanceToTiles) && unit.currentMovement == 0f) return true - - for (tile in unit.currentTile.getTilesInDistance(10)) - if (unit.movement.canMoveTo(tile) && tile.position !in unit.civInfo.exploredTiles - && unit.movement.canReach(tile) - && (tile.getOwner()==null || !tile.getOwner()!!.isCityState())) { - unit.movement.headTowards(tile) - return true - } - return false - } - /** This is what a unit with the 'explore' action does. It also explores, but also has other functions, like healing if necessary. */ fun automatedExplore(unit: MapUnit) { @@ -383,13 +394,4 @@ class UnitAutomation { if (tryExplore(unit, unit.movement.getDistanceToTiles())) return unit.civInfo.addNotification("[${unit.name}] finished exploring.", unit.currentTile.position, Color.GRAY) } - - fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) { - val reachableTiles = unitDistanceToTiles - .filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) } - - val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement } - if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first) - else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first) - } } \ No newline at end of file diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index ddc77d2422..47398c8e61 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -14,8 +14,7 @@ class WorkerAutomation(val unit: MapUnit) { fun automateWorkerAction() { val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys .filter { - it.militaryUnit != null && it.militaryUnit!!.civInfo != unit.civInfo - && unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo) + it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo) } if (enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move. @@ -118,24 +117,25 @@ class WorkerAutomation(val unit: MapUnit) { * Returns the current tile if no tile to work was found */ private fun findTileToWork(): TileInfo { - val currentTile=unit.getTile() + val currentTile = unit.getTile() val workableTiles = currentTile.getTilesInDistance(4) .filter { - (it.civilianUnit== null || it == currentTile) - && tileCanBeImproved(it, unit.civInfo) } - .sortedByDescending { getPriority(it, unit.civInfo) }.toMutableList() + (it.civilianUnit == null || it == currentTile) + && tileCanBeImproved(it, unit.civInfo) + } + .sortedByDescending { getPriority(it, unit.civInfo) } // the tile needs to be actually reachable - more difficult than it seems, // which is why we DON'T calculate this for every possible tile in the radius, // but only for the tile that's about to be chosen. - val selectedTile = workableTiles.firstOrNull{unit.movement.canReach(it) } + val selectedTile = workableTiles.firstOrNull { unit.movement.canReach(it) } - if (selectedTile != null - && getPriority(selectedTile, unit.civInfo)>1 + return if (selectedTile != null + && getPriority(selectedTile, unit.civInfo) > 1 && (!workableTiles.contains(currentTile) || getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo))) - return selectedTile - else return currentTile + selectedTile + else currentTile } private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean { diff --git a/core/src/com/unciv/logic/city/CityExpansionManager.kt b/core/src/com/unciv/logic/city/CityExpansionManager.kt index ed9816ebc1..53659f8d5d 100644 --- a/core/src/com/unciv/logic/city/CityExpansionManager.kt +++ b/core/src/com/unciv/logic/city/CityExpansionManager.kt @@ -62,17 +62,18 @@ class CityExpansionManager { fun chooseNewTileToOwn(): TileInfo? { for (i in 2..5) { val tiles = cityInfo.getCenterTile().getTilesInDistance(i) - .filter {it.getOwner() == null && it.neighbors.any { tile->tile.getOwner()==cityInfo.civInfo }} - if (tiles.isEmpty()) continue - val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) } - return chosenTile + .filter { it.getOwner() == null + && it.neighbors.any { tile -> tile.getOwner() == cityInfo.civInfo } } + val chosenTile = tiles.maxBy { Automation.rankTile(it, cityInfo.civInfo) } + if (chosenTile != null) + return chosenTile } return null } //region state-changing functions fun reset() { - for(tile in cityInfo.getTiles()) + for (tile in cityInfo.getTiles()) relinquishOwnership(tile) // The only way to create a city inside an owned tile is if it's in your territory @@ -80,10 +81,9 @@ class CityExpansionManager { // It becomes an invisible city and weird shit starts happening takeOwnership(cityInfo.getCenterTile()) - cityInfo.getCenterTile().getTilesInDistance(1) - .filter { it.getCity()==null } // can't take ownership of owned tiles (by other cities) - .forEach { takeOwnership(it) } - + for (tile in cityInfo.getCenterTile().getTilesInDistance(1) + .filter { it.getCity() == null }) // can't take ownership of owned tiles (by other cities) + takeOwnership(tile) } private fun addNewTileWithCulture() { diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 8a13eed358..c3027b1f37 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -277,7 +277,7 @@ class CityInfo { fun setTransients() { tileMap = civInfo.gameInfo.tileMap centerTileInfo = tileMap[location] - tilesInRange = getCenterTile().getTilesInDistance( 3).toHashSet() + tilesInRange = getCenterTile().getTilesInDistance(3).toHashSet() population.cityInfo = this expansion.cityInfo = this expansion.setTransients() diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 783084e1db..c985016dcb 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -292,8 +292,9 @@ class CivilizationInfo { } fun isAtWarWith(otherCiv:CivilizationInfo): Boolean { - if(otherCiv.isBarbarian() || isBarbarian()) return true - if(!diplomacy.containsKey(otherCiv.civName)) // not encountered yet + if (otherCiv.civName == civName) return false // never at war with itself + if (otherCiv.isBarbarian() || isBarbarian()) return true + if (!diplomacy.containsKey(otherCiv.civName)) // not encountered yet return false return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War } diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 9094800a89..3630e61649 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -141,10 +141,10 @@ class MapUnit { } fun updateVisibleTiles() { - if(type.isAirUnit()){ - if(hasUnique("6 tiles in every direction always visible")) - viewableTiles = getTile().getTilesInDistance(6) // it's that simple - else viewableTiles = listOf() // bomber units don't do recon + if(type.isAirUnit()) { + viewableTiles = if (hasUnique("6 tiles in every direction always visible")) + getTile().getTilesInDistance(6).toList() // it's that simple + else listOf() // bomber units don't do recon } else { var visibilityRange = 2 @@ -161,7 +161,7 @@ class MapUnit { val tile = getTile() if (tile.baseTerrain == Constants.hill && type.isLandUnit()) visibilityRange += 1 - viewableTiles = tile.getViewableTiles(visibilityRange, type.isWaterUnit()) + viewableTiles = tile.getViewableTilesList(visibilityRange, type.isWaterUnit()) } civInfo.updateViewableTiles() // for the civ } @@ -388,9 +388,10 @@ class MapUnit { if (amountToHealBy == 0) return if (hasUnique("+10 HP when healing")) amountToHealBy += 10 - val adjacentUnits = currentTile.getTilesInDistance(1).flatMap { it.getUnits() } - if (adjacentUnits.isNotEmpty()) - amountToHealBy += adjacentUnits.map { it.adjacentHealingBonus() }.max()!! + val maxAdjacentHealingBonus = currentTile.getTilesInDistance(1) + .flatMap { it.getUnits().asSequence() }.map { it.adjacentHealingBonus() }.max() + if (maxAdjacentHealingBonus != null) + amountToHealBy += maxAdjacentHealingBonus if (hasUnique("All healing effects doubled")) amountToHealBy *= 2 healBy(amountToHealBy) @@ -441,19 +442,19 @@ class MapUnit { } } - fun startTurn(){ + fun startTurn() { currentMovement = getMaxMovement().toFloat() - attacksThisTurn=0 + attacksThisTurn = 0 due = true // Wake sleeping units if there's an enemy nearby - if(isSleeping() && currentTile.getTilesInDistance(2).any { - it.militaryUnit!=null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo) + if (isSleeping() && currentTile.getTilesInDistance(2).any { + it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo) }) - action=null + action = null val tileOwner = getTile().getOwner() - if(tileOwner!=null && !civInfo.canEnterTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it + if (tileOwner != null && !civInfo.canEnterTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it movement.teleportToClosestMoveableTile() doPreTurnAction() } @@ -520,7 +521,7 @@ class MapUnit { civInfo.addNotification("We have captured a barbarian encampment and recovered [${goldGained.toInt()}] gold!", tile.position, Color.RED) } - fun disband(){ + fun disband() { // evacuation of transported units before disbanding, if possible if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) { for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) { @@ -539,11 +540,17 @@ class MapUnit { unit.disband() } } - destroy() - if(currentTile.getOwner()==civInfo) + if (currentTile.getOwner() == civInfo) civInfo.gold += baseUnit.getDisbandGold() if (civInfo.isDefeated()) civInfo.destroy() + for (unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) { + if (unit.movement.canMoveTo(currentTile)) continue // we disbanded a carrier in a city, it can still stay in the city + val tileCanMoveTo = unit.currentTile.getTilesInDistance(unit.getRange()) + .firstOrNull { unit.movement.canMoveTo(it) } + if (tileCanMoveTo != null) unit.movement.moveToTile(tileCanMoveTo) + else unit.disband() + } } private fun getAncientRuinBonus(tile: TileInfo) { @@ -591,7 +598,7 @@ class MapUnit { // Map of the surrounding area actions.add { - val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).random(tileBasedRandom) + val revealCenter = tile.getTilesAtDistance(ANCIENT_RUIN_MAP_REVEAL_OFFSET).toList().random(tileBasedRandom) val tilesToReveal = revealCenter .getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE) .filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE } diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 907b192b89..09fdbc7b6b 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -68,9 +68,8 @@ open class TileInfo { return false } - fun containsUnique(unique: String): Boolean { - return isNaturalWonder() && getNaturalWonder().uniques.contains(unique) - } + fun containsUnique(unique: String): Boolean = + isNaturalWonder() && getNaturalWonder().uniques.contains(unique) //region pure functions /** Returns military, civilian and air units in tile */ @@ -104,13 +103,8 @@ open class TileInfo { // This is for performance - since we access the neighbors of a tile ALL THE TIME, // and the neighbors of a tile never change, it's much more efficient to save the list once and for all! - @Transient private var internalNeighbors : List?=null - val neighbors: List - get(){ - if(internalNeighbors==null) - internalNeighbors = getTilesAtDistance(1) - return internalNeighbors!! - } + @delegate:Transient + val neighbors: List by lazy { getTilesAtDistance(1).toList() } fun getHeight(): Int { if (baseTerrain == Constants.mountain) return 4 @@ -127,18 +121,15 @@ open class TileInfo { return containingCity.civInfo } - fun getTerrainFeature(): Terrain? { - return if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!] - } + fun getTerrainFeature(): Terrain? = + if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!] fun isWorked(): Boolean { val city = getCity() return city!=null && city.workedTiles.contains(position) } - fun getTileStats(observingCiv: CivilizationInfo): Stats { - return getTileStats(getCity(), observingCiv) - } + fun getTileStats(observingCiv: CivilizationInfo): Stats = getTileStats(getCity(), observingCiv) fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats { var stats = getBaseTerrain().clone() @@ -264,21 +255,20 @@ open class TileInfo { fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast } - fun hasViewableResource(civInfo: CivilizationInfo): Boolean { - return resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!)) - } + fun hasViewableResource(civInfo: CivilizationInfo): Boolean = + resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!)) - fun getViewableTiles(distance:Int, ignoreCurrentTileHeight:Boolean = false): List { - return tileMap.getViewableTiles(this.position,distance,ignoreCurrentTileHeight) - } + fun getViewableTilesList(distance:Int, ignoreCurrentTileHeight: Boolean): List = + tileMap.getViewableTiles(position, distance, ignoreCurrentTileHeight) - fun getTilesInDistance(distance:Int): List { - return tileMap.getTilesInDistance(position,distance) - } + fun getTilesInDistance(distance: Int): Sequence = + tileMap.getTilesInDistance(position,distance) - fun getTilesAtDistance(distance:Int): List { - return tileMap.getTilesAtDistance(position,distance) - } + fun getTilesInDistanceRange(range: IntRange): Sequence = + tileMap.getTilesInDistanceRange(position, range) + + fun getTilesAtDistance(distance:Int): Sequence = + tileMap.getTilesAtDistance(position, distance) fun getDefensiveBonus(): Float { var bonus = getBaseTerrain().defenceBonus diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index b95396ab6c..b4c25a9abb 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -73,51 +73,49 @@ class TileMap { return get(vector.x.toInt(), vector.y.toInt()) } - fun getTilesInDistance(origin: Vector2, distance: Int): List { - val tilesToReturn = mutableListOf() - for (i in 0 .. distance) { - tilesToReturn += getTilesAtDistance(origin, i) - } - return tilesToReturn - } + fun getTilesInDistance(origin: Vector2, distance: Int): Sequence = + getTilesInDistanceRange(origin, 0..distance) - fun getTilesAtDistance(origin: Vector2, distance: Int): List { - if(distance==0) return listOf(get(origin)) - val tilesToReturn = ArrayList() + fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence = + sequence { + for (i in range) + yield(getTilesAtDistance(origin, i)) + }.flatMap { it } - fun addIfTileExists(x:Int,y:Int){ - if(contains(x,y)) - tilesToReturn += get(x,y) - } + fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence = + if (distance <= 0) // silently take negatives. + sequenceOf(get(origin)) + else + sequence { + fun getIfTileExistsOrNull(x: Int, y: Int) = if (contains(x, y)) get(x, y) else null - val centerX = origin.x.toInt() - val centerY = origin.y.toInt() + val centerX = origin.x.toInt() + val centerY = origin.y.toInt() - // Start from 6 O'clock point which means (-distance, -distance) away from the center point - var currentX = centerX - distance - var currentY = centerY - distance + // Start from 6 O'clock point which means (-distance, -distance) away from the center point + var currentX = centerX - distance + var currentY = centerY - distance - for (i in 0 until distance) { // From 6 to 8 - addIfTileExists(currentX,currentY) - // We want to get the tile on the other side of the clock, - // so if we're at current = origin-delta we want to get to origin+delta. - // The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta - addIfTileExists(2*centerX - currentX, 2*centerY - currentY) - currentX += 1 // we're going upwards to the left, towards 8 o'clock - } - for (i in 0 until distance) { // 8 to 10 - addIfTileExists(currentX,currentY) - addIfTileExists(2*centerX - currentX, 2*centerY - currentY) - currentX += 1 - currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1 - } - for (i in 0 until distance) { // 10 to 12 - addIfTileExists(currentX,currentY) - addIfTileExists(2*centerX - currentX, 2*centerY - currentY) - currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right" - } - return tilesToReturn - } + for (i in 0 until distance) { // From 6 to 8 + yield(getIfTileExistsOrNull(currentX, currentY)) + // We want to get the tile on the other side of the clock, + // so if we're at current = origin-delta we want to get to origin+delta. + // The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta + yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY)) + currentX += 1 // we're going upwards to the left, towards 8 o'clock + } + for (i in 0 until distance) { // 8 to 10 + yield(getIfTileExistsOrNull(currentX, currentY)) + yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY)) + currentX += 1 + currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+1 + } + for (i in 0 until distance) { // 10 to 12 + yield(getIfTileExistsOrNull(currentX, currentY)) + yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY)) + currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right" + } + }.filterNotNull() /** Tries to place the [unitName] into the [TileInfo] closest to the given the [position] * @@ -134,49 +132,53 @@ class TileMap { ): MapUnit? { val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet) - fun isTileMovePotential(tileInfo:TileInfo): Boolean { - if(unit.type.isAirUnit()) return true - if(unit.type.isWaterUnit()) return tileInfo.isWater || tileInfo.isCityCenter() - else return tileInfo.isLand - } + fun isTileMovePotential(tileInfo: TileInfo): Boolean = + when { + unit.type.isAirUnit() -> true + unit.type.isWaterUnit() -> tileInfo.isWater || tileInfo.isCityCenter() + else -> tileInfo.isLand + } - val viableTilesToPlaceUnitInAtDistance1 = getTilesInDistance(position, 1).filter { isTileMovePotential(it) } + val viableTilesToPlaceUnitInAtDistance1 = getTilesAtDistance(position, 1) + .filter { isTileMovePotential(it) }.toSet() // This is so that units don't skip over non-potential tiles to go elsewhere - // e.g. a city 2 tiles away from a lake could spawn water units in the lake...Or spawn beyond a mountain range... - val viableTilesToPlaceUnitInAtDistance2 = getTilesAtDistance(position, 2) - .filter { isTileMovePotential(it) && it.neighbors.any { n->n in viableTilesToPlaceUnitInAtDistance1 } } + val viableTilesToPlaceUnitIn = getTilesAtDistance(position, 2) + .filter { + isTileMovePotential(it) + && it.neighbors.any { n -> n in viableTilesToPlaceUnitInAtDistance1 } + } + viableTilesToPlaceUnitInAtDistance1 - val viableTilesToPlaceUnitIn = viableTilesToPlaceUnitInAtDistance1.union(viableTilesToPlaceUnitInAtDistance2) - unit.assignOwner(civInfo,false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn + unit.assignOwner(civInfo, false) // both the civ name and actual civ need to be in here in order to calculate the canMoveTo...Darn val unitToPlaceTile = viableTilesToPlaceUnitIn.firstOrNull { unit.movement.canMoveTo(it) } - if(unitToPlaceTile!=null) { - // Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments) - if (removeImprovement) unitToPlaceTile.improvement = null - // only once we know the unit can be placed do we add it to the civ's unit list - unit.putInTile(unitToPlaceTile) - unit.currentMovement = unit.getMaxMovement().toFloat() - - // Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles - for(promotion in unit.baseUnit.promotions) - unit.promotions.addPromotion(promotion, true) - - // And update civ stats, since the new unit changes both unit upkeep and resource consumption - civInfo.updateStatsForNextTurn() - civInfo.updateDetailedCivResources() - } - else { + if (unitToPlaceTile == null) { civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner return null // we didn't actually create a unit... } + // Remove the tile improvement, e.g. when placing the starter units (so they don't spawn on ruins/encampments) + if (removeImprovement) unitToPlaceTile.improvement = null + // only once we know the unit can be placed do we add it to the civ's unit list + unit.putInTile(unitToPlaceTile) + unit.currentMovement = unit.getMaxMovement().toFloat() + + // Only once we add the unit to the civ we can activate addPromotion, because it will try to update civ viewable tiles + for (promotion in unit.baseUnit.promotions) + unit.promotions.addPromotion(promotion, true) + + // And update civ stats, since the new unit changes both unit upkeep and resource consumption + civInfo.updateStatsForNextTurn() + civInfo.updateDetailedCivResources() + return unit } - fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean = false): List { - if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance) + fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean) + : List { + if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance).toList() val viewableTiles = getTilesInDistance(position, 1).toMutableList() val currentTileHeight = get(position).getHeight() diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index 4019aa875a..d9113ac40b 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -43,9 +43,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) { class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float) fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { - if(unitMovement==0f) return PathsToTilesWithinTurn() - val distanceToTiles = PathsToTilesWithinTurn() + if(unitMovement==0f) return distanceToTiles + val unitTile = unit.getTile().tileMap[origin] distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f) var tilesToCheck = listOf(unitTile) @@ -344,7 +344,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) { return true } - fun getDistanceToTiles() = getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement) + fun getDistanceToTiles(): PathsToTilesWithinTurn + = getDistanceToTilesWithinTurn(unit.currentTile.position,unit.currentMovement) fun getArialPathsToCities(): HashMap> { var tilesToCheck = ArrayList() diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index 0a9da6fa2b..47f102a9a0 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -98,7 +98,7 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() { updateAnnexAndRazeCityButton() updateTileGroups() - if (city.getCenterTile().getTilesAtDistance(4).isNotEmpty()) + if (city.getCenterTile().getTilesAtDistance(4).any()) displayTutorial(Tutorial.CityRange) } diff --git a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt index beb8015269..364b6f1b0a 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt @@ -251,18 +251,20 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap tileGroups[unit.getTile()]!!.selectUnit(unit) val isAirUnit = unit.type.isAirUnit() - val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange()) - else unit.movement.getDistanceToTiles().keys + val tilesInMoveRange = + if (isAirUnit) + unit.getTile().getTilesInDistance(unit.getRange()) + else + unit.movement.getDistanceToTiles().keys.asSequence() - if(isAirUnit) - for(tile in tilesInMoveRange) - tileGroups[tile]!!.showCircle(Color.BLUE,0.3f) - - for (tile: TileInfo in tilesInMoveRange) + for (tile in tilesInMoveRange) { + val tileToColor = tileGroups.getValue(tile) + if (isAirUnit) + tileToColor.showCircle(Color.BLUE, 0.3f) if (unit.movement.canMoveTo(tile)) - tileGroups[tile]!!.showCircle(Color.WHITE, + tileToColor.showCircle(Color.WHITE, if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f) - + } val unitType = unit.type val attackableTiles: List = if (unitType.isCivilian()) listOf() @@ -281,16 +283,16 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap else 0.5f for (tile in tileGroups.values) { if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout - if (tile.icons.improvementIcon != null && tile.tileInfo.improvement!=Constants.barbarianEncampment - && tile.tileInfo.improvement!=Constants.ancientRuins) + if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment + && tile.tileInfo.improvement != Constants.ancientRuins) tile.icons.improvementIcon!!.color.a = fadeout if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout } } private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet) { - val attackableTiles: List = UnitAutomation().getBombardTargets(city) - .filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } + val attackableTiles = UnitAutomation().getBombardTargets(city) + .filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } for (attackableTile in attackableTiles) { tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57)) tileGroups[attackableTile]!!.showCrosshair()