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
This commit is contained in:
Kentalot 2020-02-23 04:22:50 -08:00 committed by GitHub
parent 718473fb31
commit 3c2cb01169
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 309 additions and 289 deletions

View File

@ -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
}

View File

@ -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{

View File

@ -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(

View File

@ -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

View File

@ -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<TileInfo, Float>,
luxuryResourcesInCivArea: Sequence<TileResource>): 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)

View File

@ -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<TileInfo> {
return city.getCenterTile().getViewableTiles(city.range, true)
.filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
}
fun getBombardTargets(city: CityInfo): Sequence<TileInfo> =
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)
}
}

View File

@ -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 {

View File

@ -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() {

View File

@ -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()

View File

@ -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
}

View File

@ -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 }

View File

@ -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<TileInfo>?=null
val neighbors: List<TileInfo>
get(){
if(internalNeighbors==null)
internalNeighbors = getTilesAtDistance(1)
return internalNeighbors!!
}
@delegate:Transient
val neighbors: List<TileInfo> 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<TileInfo> {
return tileMap.getViewableTiles(this.position,distance,ignoreCurrentTileHeight)
}
fun getViewableTilesList(distance:Int, ignoreCurrentTileHeight: Boolean): List<TileInfo> =
tileMap.getViewableTiles(position, distance, ignoreCurrentTileHeight)
fun getTilesInDistance(distance:Int): List<TileInfo> {
return tileMap.getTilesInDistance(position,distance)
}
fun getTilesInDistance(distance: Int): Sequence<TileInfo> =
tileMap.getTilesInDistance(position,distance)
fun getTilesAtDistance(distance:Int): List<TileInfo> {
return tileMap.getTilesAtDistance(position,distance)
}
fun getTilesInDistanceRange(range: IntRange): Sequence<TileInfo> =
tileMap.getTilesInDistanceRange(position, range)
fun getTilesAtDistance(distance:Int): Sequence<TileInfo> =
tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float {
var bonus = getBaseTerrain().defenceBonus

View File

@ -73,51 +73,49 @@ class TileMap {
return get(vector.x.toInt(), vector.y.toInt())
}
fun getTilesInDistance(origin: Vector2, distance: Int): List<TileInfo> {
val tilesToReturn = mutableListOf<TileInfo>()
for (i in 0 .. distance) {
tilesToReturn += getTilesAtDistance(origin, i)
}
return tilesToReturn
}
fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
getTilesInDistanceRange(origin, 0..distance)
fun getTilesAtDistance(origin: Vector2, distance: Int): List<TileInfo> {
if(distance==0) return listOf(get(origin))
val tilesToReturn = ArrayList<TileInfo>()
fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
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<TileInfo> =
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<TileInfo> {
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance)
fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean)
: List<TileInfo> {
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance).toList()
val viewableTiles = getTilesInDistance(position, 1).toMutableList()
val currentTileHeight = get(position).getHeight()

View File

@ -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<TileInfo, ArrayList<TileInfo>> {
var tilesToCheck = ArrayList<TileInfo>()

View File

@ -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)
}

View File

@ -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<TileInfo> = 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<Vector2>) {
val attackableTiles: List<TileInfo> = 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()