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 // Barbarians will only spawn in places that no one can see
val allViewableTiles = civilizations.filterNot { it.isBarbarian() } val allViewableTiles = civilizations.filterNot { it.isBarbarian() }
.flatMap { it.viewableTiles }.toHashSet() .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 { val viableTiles = tileMap.values.filter {
!it.getBaseTerrain().impassable && it.isLand !it.getBaseTerrain().impassable && it.isLand
&& it.terrainFeature==null && it.terrainFeature == null
&& it.naturalWonder==null && it.naturalWonder == null
&& it !in tilesWithin3ofExistingEncampment && it !in tilesWithin3ofExistingEncampment
&& it !in allViewableTiles && it !in allViewableTiles
} }

View File

@ -14,14 +14,6 @@ import kotlin.math.sqrt
class Automation { 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 { fun rankTileForCityWork(tile:TileInfo, city: CityInfo, foodWeight: Float = 1f): Float {
val stats = tile.getTileStats(city, city.civInfo) val stats = tile.getTileStats(city, city.civInfo)
@ -63,20 +55,6 @@ class Automation {
return rank 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) { fun trainMilitaryUnit(city: CityInfo) {
val name = chooseMilitaryUnit(city) val name = chooseMilitaryUnit(city)
city.cityConstructions.currentConstruction = name 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{ enum class ThreatLevel{

View File

@ -86,7 +86,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
// 6 - wander // 6 - wander
UnitAutomation().wander(unit, unitDistanceToTiles) UnitAutomation.wander(unit, unitDistanceToTiles)
} }
private fun automateScout(unit: MapUnit) { private fun automateScout(unit: MapUnit) {
@ -114,7 +114,7 @@ class BarbarianAutomation(val civInfo: CivilizationInfo) {
if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return if (unit.health < 100 && UnitAutomation().tryHealUnit(unit, unitDistanceToTiles)) return
// 5 - wander // 5 - wander
UnitAutomation().wander(unit, unitDistanceToTiles) UnitAutomation.wander(unit, unitDistanceToTiles)
} }
private fun findFurthestTileCanMoveTo( private fun findFurthestTileCanMoveTo(

View File

@ -63,9 +63,10 @@ class BattleHelper {
val tilesInAttackRange = val tilesInAttackRange =
if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit()) if (unit.hasUnique("Ranged attacks may be performed over obstacles") || unit.type.isAirUnit())
reachableTile.getTilesInDistance(rangeOfAttack) 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) } .map { AttackableTile(reachableTile, it) }
} }
return attackableTiles return attackableTiles

View File

@ -37,7 +37,7 @@ class SpecificUnitAutomation{
return createImprovementAction() return createImprovementAction()
} }
} }
else UnitAutomation().tryExplore(unit, unit.movement.getDistanceToTiles()) else UnitAutomation.tryExplore(unit, unit.movement.getDistanceToTiles())
} }
fun automateGreatGeneral(unit: MapUnit){ fun automateGreatGeneral(unit: MapUnit){
@ -72,14 +72,13 @@ class SpecificUnitAutomation{
fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map<TileInfo, Float>, fun rankTileAsCityCenter(tileInfo: TileInfo, nearbyTileRankings: Map<TileInfo, Float>,
luxuryResourcesInCivArea: Sequence<TileResource>): Float { luxuryResourcesInCivArea: Sequence<TileResource>): Float {
val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2) val bestTilesFromOuterLayer = tileInfo.getTilesAtDistance(2)
.asSequence()
.sortedByDescending { nearbyTileRankings[it] }.take(2) .sortedByDescending { nearbyTileRankings[it] }.take(2)
.toList() .toList()
val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer) val top5Tiles = tileInfo.neighbors.union(bestTilesFromOuterLayer)
.asSequence() .asSequence()
.sortedByDescending { nearbyTileRankings[it] } .sortedByDescending { nearbyTileRankings[it] }
.take(5) .take(5)
var rank = top5Tiles.map { nearbyTileRankings[it]!! }.sum() var rank = top5Tiles.map { nearbyTileRankings.getValue(it) }.sum()
if (tileInfo.isCoastalTile()) rank += 5 if (tileInfo.isCoastalTile()) rank += 5
val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null } val luxuryResourcesInCityArea = tileInfo.getTilesAtDistance(2).filter { it.resource!=null }
@ -94,9 +93,9 @@ class SpecificUnitAutomation{
fun automateSettlerActions(unit: MapUnit) { 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 { .flatMap {
val distanceAwayFromCity = val distanceAwayFromCity =
if (unit.civInfo.knows(it.civInfo) if (unit.civInfo.knows(it.civInfo)
@ -106,34 +105,36 @@ class SpecificUnitAutomation{
else 3 else 3
it.getCenterTile().getTilesInDistance(distanceAwayFromCity) 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. // 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) 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) val possibleCityLocations = unit.getTile().getTilesInDistance(5)
.filter { val tileOwner=it.getOwner() .filter {
it.isLand && (tileOwner==null || tileOwner==unit.civInfo) && // don't allow settler to settle inside other civ's territory val tileOwner = it.getOwner()
(unit.movement.canMoveTo(it) || unit.currentTile==it) it.isLand && (tileOwner == null || tileOwner == unit.civInfo) && // don't allow settler to settle inside other civ's territory
&& it !in tilesNearCities } (unit.movement.canMoveTo(it) || unit.currentTile == it)
&& it !in tilesNearCities
}
val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence() val luxuryResourcesInCivArea = unit.civInfo.cities.asSequence()
.flatMap { it.getTiles().asSequence() }.filter { it.resource!=null } .flatMap { it.getTiles().asSequence() }.filter { it.resource != null }
.map { it.getTileResource() }.filter { it.resourceType==ResourceType.Luxury } .map { it.getTileResource() }.filter { it.resourceType == ResourceType.Luxury }
.distinct() .distinct()
val bestCityLocation: TileInfo? = possibleCityLocations val bestCityLocation: TileInfo? = possibleCityLocations
.asSequence()
.sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) } .sortedByDescending { rankTileAsCityCenter(it, nearbyTileRankings, luxuryResourcesInCivArea) }
.firstOrNull { unit.movement.canReach(it) } .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 (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 val unitDistanceToTiles = unit.movement.getDistanceToTiles()
UnitAutomation().wander(unit, unit.movement.getDistanceToTiles()) // go around aimlessly if (UnitAutomation.tryExplore(unit, unitDistanceToTiles)) return // try to find new areas
UnitAutomation.wander(unit, unitDistanceToTiles) // go around aimlessly
return return
} }
if(bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() }) if (bestCityLocation.getTilesInDistance(3).any { it.isCityCenter() })
throw Exception("City within distance") throw Exception("City within distance")
if (unit.getTile() == bestCityLocation) if (unit.getTile() == bestCityLocation)
@ -169,9 +170,9 @@ class SpecificUnitAutomation{
&& it.isLand && it.isLand
&& !it.isCityCenter() && !it.isCityCenter()
&& it.resource==null } && it.resource==null }
.sortedByDescending { Automation().rankTile(it,unit.civInfo) }.toList() .sortedByDescending { Automation.rankTile(it,unit.civInfo) }.toList()
val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } val chosenTile = tiles.firstOrNull { unit.movement.canReach(it) } ?: continue // to another city
if(chosenTile==null) continue // to another city
unit.movement.headTowards(chosenTile) unit.movement.headTowards(chosenTile)
if(unit.currentTile==chosenTile && unit.currentMovement > 0) if(unit.currentTile==chosenTile && unit.currentMovement > 0)
@ -185,35 +186,41 @@ class SpecificUnitAutomation{
fun automateFighter(unit: MapUnit) { fun automateFighter(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val enemyAirUnitsInRange = tilesInRange 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 (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
if(battleHelper.tryAttackNearbyEnemy(unit)) return if (battleHelper.tryAttackNearbyEnemy(unit)) return
// TODO Implement consideration for landing on aircraft carrier // TODO Implement consideration for landing on aircraft carrier
val immediatelyReachableCities = tilesInRange 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){ for (city in immediatelyReachableCities) {
if(city.getTilesInDistance(unit.getRange()) if (city.getTilesInDistance(unit.getRange())
.any { battleHelper.containsAttackableEnemy(it,MapUnitCombatant(unit)) }) { .any { battleHelper.containsAttackableEnemy(it, MapUnitCombatant(unit)) }) {
unit.movement.moveToTile(city) unit.movement.moveToTile(city)
return return
} }
} }
val pathsToCities = unit.movement.getArialPathsToCities() 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 val citiesByNearbyAirUnits = pathsToCities.keys
.groupBy { it.getTilesInDistance(unit.getRange()) .groupBy { key ->
.count{it.airUnits.size>0 && it.airUnits.first().civInfo.isAtWarWith(unit.civInfo)} } 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 citiesWithMostNeedOfAirUnits = citiesByNearbyAirUnits.maxBy { it.key }!!.value
val chosenCity = citiesWithMostNeedOfAirUnits.minBy { pathsToCities[it]!!.size }!! // city with min path = least turns to get there //todo: maybe groupby size and choose highest priority within the same size turns
val firstStepInPath = pathsToCities[chosenCity]!!.first() 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) unit.movement.moveToTile(firstStepInPath)
return return
} }
@ -255,6 +262,8 @@ class SpecificUnitAutomation{
} }
if (citiesThatCanAttackFrom.isEmpty()) return 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 closestCityThatCanAttackFrom = citiesThatCanAttackFrom.minBy { pathsToCities[it]!!.size }!!
val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first() val firstStepInPath = pathsToCities[closestCityThatCanAttackFrom]!!.first()
airUnit.movement.moveToTile(firstStepInPath) airUnit.movement.moveToTile(firstStepInPath)

View File

@ -3,10 +3,7 @@ package com.unciv.logic.automation
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.battle.Battle import com.unciv.logic.battle.*
import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.GreatPersonManager import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
@ -23,6 +20,41 @@ class UnitAutomation {
companion object { companion object {
const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5 const val CLOSE_ENEMY_TILES_AWAY_LIMIT = 5
const val CLOSE_ENEMY_TURNS_AWAY_LIMIT = 3f 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() private val battleHelper = BattleHelper()
@ -162,10 +194,9 @@ class UnitAutomation {
return true return true
} }
fun getBombardTargets(city: CityInfo): List<TileInfo> { fun getBombardTargets(city: CityInfo): Sequence<TileInfo> =
return city.getCenterTile().getViewableTiles(city.range, true) city.getCenterTile().getTilesInDistance(city.range)
.filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) } .filter { battleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
}
/** Move towards the closest attackable enemy of the [unit]. /** Move towards the closest attackable enemy of the [unit].
* *
@ -180,7 +211,7 @@ class UnitAutomation {
var closeEnemies = battleHelper.getAttackableEnemies( var closeEnemies = battleHelper.getAttackableEnemies(
unit, unit,
unitDistanceToTiles, unitDistanceToTiles,
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT) tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
).filter { ).filter {
// Ignore units that would 1-shot you if you attacked // Ignore units that would 1-shot you if you attacked
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit), BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
@ -246,34 +277,35 @@ class UnitAutomation {
if (closestReachableEnemyCity != null) { if (closestReachableEnemyCity != null) {
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2) val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
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 - // 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. // this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary.
val tileToHeadTo = suitableGatheringGroundTiles val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4)
.sortedBy { it.arialDistanceTo(unit.currentTile) } .filter { it.isLand }
.firstOrNull { unit.movement.canReach(it) } ?: closestReachableEnemyCity .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 if (tileToHeadTo !in tilesInBombardRange) // no need to worry, keep going as the movement alg. says
unit.movement.headTowards(tileToHeadTo) unit.movement.headTowards(tileToHeadTo)
else { else {
if (unit.getRange() > 2) { // should never be in a bombardable position if (unit.getRange() > 2) { // should never be in a bombardable position
val tilesCanAttackFromButNotInBombardRange = val tileToMoveTo =
reachableTilesNotInBombardRange.filter { it.arialDistanceTo(closestReachableEnemyCity) <= unit.getRange() } 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 // move into position far away enough that the bombard doesn't hurt
if (tilesCanAttackFromButNotInBombardRange.any()) if (tileToMoveTo != null)
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!) unit.movement.headTowards(tileToMoveTo)
} else { } else {
// calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once) // 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) val militaryUnitsAroundEnemyCity = closestReachableEnemyCity.getTilesInDistance(3)
.filter { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo } .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 var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless
val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!) val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!)
for (militaryUnit in militaryUnitsAroundEnemyCity) { for (militaryUnit in militaryUnitsAroundEnemyCity) {
@ -290,28 +322,29 @@ class UnitAutomation {
} }
fun tryBombardEnemy(city: CityInfo): Boolean { fun tryBombardEnemy(city: CityInfo): Boolean {
if (!city.attackedThisTurn) { return when {
val target = chooseBombardTarget(city) city.attackedThisTurn -> false
if (target == null) return false else -> {
val enemy = Battle.getMapCombatantOfTile(target)!! val enemy = chooseBombardTarget(city) ?: return false
Battle.attack(CityCombatant(city), enemy) Battle.attack(CityCombatant(city), enemy)
return true true
}
} }
return false
} }
private fun chooseBombardTarget(city: CityInfo): TileInfo? { private fun chooseBombardTarget(city: CityInfo): ICombatant? {
var targets = getBombardTargets(city) val mappedTargets = getBombardTargets(city).map { Battle.getMapCombatantOfTile(it)!! }
if (targets.isEmpty()) return null .filter {
val siegeUnits = targets val unitType = it.getUnitType()
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType() == UnitType.Siege } unitType == UnitType.Siege || unitType.isRanged()
if (siegeUnits.any()) targets = siegeUnits }
else { .groupByTo(LinkedHashMap()) { it.getUnitType() }
val rangedUnits = targets
.filter { Battle.getMapCombatantOfTile(it)!!.getUnitType().isRanged() } var targets = mappedTargets[UnitType.Siege]?.asSequence()
if (rangedUnits.any()) targets = rangedUnits if (targets == null)
} targets = mappedTargets.values.asSequence().flatMap { it.asSequence() }
return targets.minBy { Battle.getMapCombatantOfTile(it)!!.getHealth() }
return targets.minBy { it.getHealth() }
} }
private fun tryGarrisoningUnit(unit: MapUnit): Boolean { private fun tryGarrisoningUnit(unit: MapUnit): Boolean {
@ -352,28 +385,6 @@ class UnitAutomation {
return true 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. /** This is what a unit with the 'explore' action does.
It also explores, but also has other functions, like healing if necessary. */ It also explores, but also has other functions, like healing if necessary. */
fun automatedExplore(unit: MapUnit) { fun automatedExplore(unit: MapUnit) {
@ -383,13 +394,4 @@ class UnitAutomation {
if (tryExplore(unit, unit.movement.getDistanceToTiles())) return if (tryExplore(unit, unit.movement.getDistanceToTiles())) return
unit.civInfo.addNotification("[${unit.name}] finished exploring.", unit.currentTile.position, Color.GRAY) 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() { fun automateWorkerAction() {
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { .filter {
it.militaryUnit != null && it.militaryUnit!!.civInfo != unit.civInfo it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo)
&& unit.civInfo.isAtWarWith(it.militaryUnit!!.civInfo)
} }
if (enemyUnitsInWalkingDistance.isNotEmpty()) return // Don't you dare move. 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 * Returns the current tile if no tile to work was found
*/ */
private fun findTileToWork(): TileInfo { private fun findTileToWork(): TileInfo {
val currentTile=unit.getTile() val currentTile = unit.getTile()
val workableTiles = currentTile.getTilesInDistance(4) val workableTiles = currentTile.getTilesInDistance(4)
.filter { .filter {
(it.civilianUnit== null || it == currentTile) (it.civilianUnit == null || it == currentTile)
&& tileCanBeImproved(it, unit.civInfo) } && tileCanBeImproved(it, unit.civInfo)
.sortedByDescending { getPriority(it, unit.civInfo) }.toMutableList() }
.sortedByDescending { getPriority(it, unit.civInfo) }
// the tile needs to be actually reachable - more difficult than it seems, // 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, // 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. // 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 return if (selectedTile != null
&& getPriority(selectedTile, unit.civInfo)>1 && getPriority(selectedTile, unit.civInfo) > 1
&& (!workableTiles.contains(currentTile) && (!workableTiles.contains(currentTile)
|| getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo))) || getPriority(selectedTile, unit.civInfo) > getPriority(currentTile, unit.civInfo)))
return selectedTile selectedTile
else return currentTile else currentTile
} }
private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean { private fun tileCanBeImproved(tile: TileInfo, civInfo: CivilizationInfo): Boolean {

View File

@ -62,17 +62,18 @@ class CityExpansionManager {
fun chooseNewTileToOwn(): TileInfo? { fun chooseNewTileToOwn(): TileInfo? {
for (i in 2..5) { for (i in 2..5) {
val tiles = cityInfo.getCenterTile().getTilesInDistance(i) val tiles = cityInfo.getCenterTile().getTilesInDistance(i)
.filter {it.getOwner() == null && it.neighbors.any { tile->tile.getOwner()==cityInfo.civInfo }} .filter { it.getOwner() == null
if (tiles.isEmpty()) continue && it.neighbors.any { tile -> tile.getOwner() == cityInfo.civInfo } }
val chosenTile = tiles.maxBy { Automation().rankTile(it,cityInfo.civInfo) } val chosenTile = tiles.maxBy { Automation.rankTile(it, cityInfo.civInfo) }
return chosenTile if (chosenTile != null)
return chosenTile
} }
return null return null
} }
//region state-changing functions //region state-changing functions
fun reset() { fun reset() {
for(tile in cityInfo.getTiles()) for (tile in cityInfo.getTiles())
relinquishOwnership(tile) relinquishOwnership(tile)
// The only way to create a city inside an owned tile is if it's in your territory // 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 // It becomes an invisible city and weird shit starts happening
takeOwnership(cityInfo.getCenterTile()) takeOwnership(cityInfo.getCenterTile())
cityInfo.getCenterTile().getTilesInDistance(1) for (tile in cityInfo.getCenterTile().getTilesInDistance(1)
.filter { it.getCity()==null } // can't take ownership of owned tiles (by other cities) .filter { it.getCity() == null }) // can't take ownership of owned tiles (by other cities)
.forEach { takeOwnership(it) } takeOwnership(tile)
} }
private fun addNewTileWithCulture() { private fun addNewTileWithCulture() {

View File

@ -277,7 +277,7 @@ class CityInfo {
fun setTransients() { fun setTransients() {
tileMap = civInfo.gameInfo.tileMap tileMap = civInfo.gameInfo.tileMap
centerTileInfo = tileMap[location] centerTileInfo = tileMap[location]
tilesInRange = getCenterTile().getTilesInDistance( 3).toHashSet() tilesInRange = getCenterTile().getTilesInDistance(3).toHashSet()
population.cityInfo = this population.cityInfo = this
expansion.cityInfo = this expansion.cityInfo = this
expansion.setTransients() expansion.setTransients()

View File

@ -292,8 +292,9 @@ class CivilizationInfo {
} }
fun isAtWarWith(otherCiv:CivilizationInfo): Boolean { fun isAtWarWith(otherCiv:CivilizationInfo): Boolean {
if(otherCiv.isBarbarian() || isBarbarian()) return true if (otherCiv.civName == civName) return false // never at war with itself
if(!diplomacy.containsKey(otherCiv.civName)) // not encountered yet if (otherCiv.isBarbarian() || isBarbarian()) return true
if (!diplomacy.containsKey(otherCiv.civName)) // not encountered yet
return false return false
return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War return getDiplomacyManager(otherCiv).diplomaticStatus == DiplomaticStatus.War
} }

View File

@ -141,10 +141,10 @@ class MapUnit {
} }
fun updateVisibleTiles() { fun updateVisibleTiles() {
if(type.isAirUnit()){ if(type.isAirUnit()) {
if(hasUnique("6 tiles in every direction always visible")) viewableTiles = if (hasUnique("6 tiles in every direction always visible"))
viewableTiles = getTile().getTilesInDistance(6) // it's that simple getTile().getTilesInDistance(6).toList() // it's that simple
else viewableTiles = listOf() // bomber units don't do recon else listOf() // bomber units don't do recon
} }
else { else {
var visibilityRange = 2 var visibilityRange = 2
@ -161,7 +161,7 @@ class MapUnit {
val tile = getTile() val tile = getTile()
if (tile.baseTerrain == Constants.hill && type.isLandUnit()) visibilityRange += 1 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 civInfo.updateViewableTiles() // for the civ
} }
@ -388,9 +388,10 @@ class MapUnit {
if (amountToHealBy == 0) return if (amountToHealBy == 0) return
if (hasUnique("+10 HP when healing")) amountToHealBy += 10 if (hasUnique("+10 HP when healing")) amountToHealBy += 10
val adjacentUnits = currentTile.getTilesInDistance(1).flatMap { it.getUnits() } val maxAdjacentHealingBonus = currentTile.getTilesInDistance(1)
if (adjacentUnits.isNotEmpty()) .flatMap { it.getUnits().asSequence() }.map { it.adjacentHealingBonus() }.max()
amountToHealBy += adjacentUnits.map { it.adjacentHealingBonus() }.max()!! if (maxAdjacentHealingBonus != null)
amountToHealBy += maxAdjacentHealingBonus
if (hasUnique("All healing effects doubled")) if (hasUnique("All healing effects doubled"))
amountToHealBy *= 2 amountToHealBy *= 2
healBy(amountToHealBy) healBy(amountToHealBy)
@ -441,19 +442,19 @@ class MapUnit {
} }
} }
fun startTurn(){ fun startTurn() {
currentMovement = getMaxMovement().toFloat() currentMovement = getMaxMovement().toFloat()
attacksThisTurn=0 attacksThisTurn = 0
due = true due = true
// Wake sleeping units if there's an enemy nearby // Wake sleeping units if there's an enemy nearby
if(isSleeping() && currentTile.getTilesInDistance(2).any { if (isSleeping() && currentTile.getTilesInDistance(2).any {
it.militaryUnit!=null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo) it.militaryUnit != null && it.militaryUnit!!.civInfo.isAtWarWith(civInfo)
}) })
action=null action = null
val tileOwner = getTile().getOwner() 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() movement.teleportToClosestMoveableTile()
doPreTurnAction() doPreTurnAction()
} }
@ -520,7 +521,7 @@ class MapUnit {
civInfo.addNotification("We have captured a barbarian encampment and recovered [${goldGained.toInt()}] gold!", tile.position, Color.RED) 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 // evacuation of transported units before disbanding, if possible
if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) { if (type.isAircraftCarrierUnit() || type.isMissileCarrierUnit()) {
for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) { for(unit in currentTile.getUnits().filter { it.type.isAirUnit() && it.isTransported }) {
@ -539,11 +540,17 @@ class MapUnit {
unit.disband() unit.disband()
} }
} }
destroy() destroy()
if(currentTile.getOwner()==civInfo) if (currentTile.getOwner() == civInfo)
civInfo.gold += baseUnit.getDisbandGold() civInfo.gold += baseUnit.getDisbandGold()
if (civInfo.isDefeated()) civInfo.destroy() 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) { private fun getAncientRuinBonus(tile: TileInfo) {
@ -591,7 +598,7 @@ class MapUnit {
// Map of the surrounding area // Map of the surrounding area
actions.add { 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 val tilesToReveal = revealCenter
.getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE) .getTilesInDistance(ANCIENT_RUIN_MAP_REVEAL_RANGE)
.filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE } .filter { Random.nextFloat() < ANCIENT_RUIN_MAP_REVEAL_CHANCE }

View File

@ -68,9 +68,8 @@ open class TileInfo {
return false return false
} }
fun containsUnique(unique: String): Boolean { fun containsUnique(unique: String): Boolean =
return isNaturalWonder() && getNaturalWonder().uniques.contains(unique) isNaturalWonder() && getNaturalWonder().uniques.contains(unique)
}
//region pure functions //region pure functions
/** Returns military, civilian and air units in tile */ /** 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, // 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! // 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 @delegate:Transient
val neighbors: List<TileInfo> val neighbors: List<TileInfo> by lazy { getTilesAtDistance(1).toList() }
get(){
if(internalNeighbors==null)
internalNeighbors = getTilesAtDistance(1)
return internalNeighbors!!
}
fun getHeight(): Int { fun getHeight(): Int {
if (baseTerrain == Constants.mountain) return 4 if (baseTerrain == Constants.mountain) return 4
@ -127,18 +121,15 @@ open class TileInfo {
return containingCity.civInfo return containingCity.civInfo
} }
fun getTerrainFeature(): Terrain? { fun getTerrainFeature(): Terrain? =
return if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!] if (terrainFeature == null) null else ruleset.terrains[terrainFeature!!]
}
fun isWorked(): Boolean { fun isWorked(): Boolean {
val city = getCity() val city = getCity()
return city!=null && city.workedTiles.contains(position) return city!=null && city.workedTiles.contains(position)
} }
fun getTileStats(observingCiv: CivilizationInfo): Stats { fun getTileStats(observingCiv: CivilizationInfo): Stats = getTileStats(getCity(), observingCiv)
return getTileStats(getCity(), observingCiv)
}
fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats { fun getTileStats(city: CityInfo?, observingCiv: CivilizationInfo): Stats {
var stats = getBaseTerrain().clone() var stats = getBaseTerrain().clone()
@ -264,21 +255,20 @@ open class TileInfo {
fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast } fun isCoastalTile() = neighbors.any { it.baseTerrain==Constants.coast }
fun hasViewableResource(civInfo: CivilizationInfo): Boolean { fun hasViewableResource(civInfo: CivilizationInfo): Boolean =
return resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!)) resource != null && (getTileResource().revealedBy == null || civInfo.tech.isResearched(getTileResource().revealedBy!!))
}
fun getViewableTiles(distance:Int, ignoreCurrentTileHeight:Boolean = false): List<TileInfo> { fun getViewableTilesList(distance:Int, ignoreCurrentTileHeight: Boolean): List<TileInfo> =
return tileMap.getViewableTiles(this.position,distance,ignoreCurrentTileHeight) tileMap.getViewableTiles(position, distance, ignoreCurrentTileHeight)
}
fun getTilesInDistance(distance:Int): List<TileInfo> { fun getTilesInDistance(distance: Int): Sequence<TileInfo> =
return tileMap.getTilesInDistance(position,distance) tileMap.getTilesInDistance(position,distance)
}
fun getTilesAtDistance(distance:Int): List<TileInfo> { fun getTilesInDistanceRange(range: IntRange): Sequence<TileInfo> =
return tileMap.getTilesAtDistance(position,distance) tileMap.getTilesInDistanceRange(position, range)
}
fun getTilesAtDistance(distance:Int): Sequence<TileInfo> =
tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float { fun getDefensiveBonus(): Float {
var bonus = getBaseTerrain().defenceBonus var bonus = getBaseTerrain().defenceBonus

View File

@ -73,51 +73,49 @@ class TileMap {
return get(vector.x.toInt(), vector.y.toInt()) return get(vector.x.toInt(), vector.y.toInt())
} }
fun getTilesInDistance(origin: Vector2, distance: Int): List<TileInfo> { fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
val tilesToReturn = mutableListOf<TileInfo>() getTilesInDistanceRange(origin, 0..distance)
for (i in 0 .. distance) {
tilesToReturn += getTilesAtDistance(origin, i)
}
return tilesToReturn
}
fun getTilesAtDistance(origin: Vector2, distance: Int): List<TileInfo> { fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
if(distance==0) return listOf(get(origin)) sequence {
val tilesToReturn = ArrayList<TileInfo>() for (i in range)
yield(getTilesAtDistance(origin, i))
}.flatMap { it }
fun addIfTileExists(x:Int,y:Int){ fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
if(contains(x,y)) if (distance <= 0) // silently take negatives.
tilesToReturn += get(x,y) 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 centerX = origin.x.toInt()
val centerY = origin.y.toInt() val centerY = origin.y.toInt()
// Start from 6 O'clock point which means (-distance, -distance) away from the center point // Start from 6 O'clock point which means (-distance, -distance) away from the center point
var currentX = centerX - distance var currentX = centerX - distance
var currentY = centerY - distance var currentY = centerY - distance
for (i in 0 until distance) { // From 6 to 8 for (i in 0 until distance) { // From 6 to 8
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
// We want to get the tile on the other side of the clock, // 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. // 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 // The simplest way to do this is 2*origin - current = 2*origin- (origin - delta) = origin+delta
addIfTileExists(2*centerX - currentX, 2*centerY - currentY) yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
currentX += 1 // we're going upwards to the left, towards 8 o'clock currentX += 1 // we're going upwards to the left, towards 8 o'clock
} }
for (i in 0 until distance) { // 8 to 10 for (i in 0 until distance) { // 8 to 10
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
addIfTileExists(2*centerX - currentX, 2*centerY - currentY) yield(getIfTileExistsOrNull(2 * centerX - currentX, 2 * centerY - currentY))
currentX += 1 currentX += 1
currentY += 1 // we're going up the left side of the hexagon so we're going "up" - +1,+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 for (i in 0 until distance) { // 10 to 12
addIfTileExists(currentX,currentY) yield(getIfTileExistsOrNull(currentX, currentY))
addIfTileExists(2*centerX - currentX, 2*centerY - 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" currentY += 1 // we're going up the top left side of the hexagon so we're heading "up and to the right"
} }
return tilesToReturn }.filterNotNull()
}
/** Tries to place the [unitName] into the [TileInfo] closest to the given the [position] /** Tries to place the [unitName] into the [TileInfo] closest to the given the [position]
* *
@ -134,49 +132,53 @@ class TileMap {
): MapUnit? { ): MapUnit? {
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet) val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet)
fun isTileMovePotential(tileInfo:TileInfo): Boolean { fun isTileMovePotential(tileInfo: TileInfo): Boolean =
if(unit.type.isAirUnit()) return true when {
if(unit.type.isWaterUnit()) return tileInfo.isWater || tileInfo.isCityCenter() unit.type.isAirUnit() -> true
else return tileInfo.isLand 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 - // 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... // 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) val viableTilesToPlaceUnitIn = getTilesAtDistance(position, 2)
.filter { isTileMovePotential(it) && it.neighbors.any { n->n in viableTilesToPlaceUnitInAtDistance1 } } .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) } val unitToPlaceTile = viableTilesToPlaceUnitIn.firstOrNull { unit.movement.canMoveTo(it) }
if(unitToPlaceTile!=null) { 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 {
civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner civInfo.removeUnit(unit) // since we added it to the civ units in the previous assignOwner
return null // we didn't actually create a unit... 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 return unit
} }
fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean = false): List<TileInfo> { fun getViewableTiles(position: Vector2, sightDistance: Int, ignoreCurrentTileHeight: Boolean)
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance) : List<TileInfo> {
if (ignoreCurrentTileHeight) return getTilesInDistance(position, sightDistance).toList()
val viewableTiles = getTilesInDistance(position, 1).toMutableList() val viewableTiles = getTilesInDistance(position, 1).toMutableList()
val currentTileHeight = get(position).getHeight() val currentTileHeight = get(position).getHeight()

View File

@ -43,9 +43,9 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float) class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
if(unitMovement==0f) return PathsToTilesWithinTurn()
val distanceToTiles = PathsToTilesWithinTurn() val distanceToTiles = PathsToTilesWithinTurn()
if(unitMovement==0f) return distanceToTiles
val unitTile = unit.getTile().tileMap[origin] val unitTile = unit.getTile().tileMap[origin]
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f) distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f)
var tilesToCheck = listOf(unitTile) var tilesToCheck = listOf(unitTile)
@ -344,7 +344,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return true 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>> { fun getArialPathsToCities(): HashMap<TileInfo, ArrayList<TileInfo>> {
var tilesToCheck = ArrayList<TileInfo>() var tilesToCheck = ArrayList<TileInfo>()

View File

@ -98,7 +98,7 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() {
updateAnnexAndRazeCityButton() updateAnnexAndRazeCityButton()
updateTileGroups() updateTileGroups()
if (city.getCenterTile().getTilesAtDistance(4).isNotEmpty()) if (city.getCenterTile().getTilesAtDistance(4).any())
displayTutorial(Tutorial.CityRange) displayTutorial(Tutorial.CityRange)
} }

View File

@ -251,18 +251,20 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
tileGroups[unit.getTile()]!!.selectUnit(unit) tileGroups[unit.getTile()]!!.selectUnit(unit)
val isAirUnit = unit.type.isAirUnit() val isAirUnit = unit.type.isAirUnit()
val tilesInMoveRange = if(isAirUnit) unit.getTile().getTilesInDistance(unit.getRange()) val tilesInMoveRange =
else unit.movement.getDistanceToTiles().keys if (isAirUnit)
unit.getTile().getTilesInDistance(unit.getRange())
else
unit.movement.getDistanceToTiles().keys.asSequence()
if(isAirUnit) for (tile in tilesInMoveRange) {
for(tile in tilesInMoveRange) val tileToColor = tileGroups.getValue(tile)
tileGroups[tile]!!.showCircle(Color.BLUE,0.3f) if (isAirUnit)
tileToColor.showCircle(Color.BLUE, 0.3f)
for (tile: TileInfo in tilesInMoveRange)
if (unit.movement.canMoveTo(tile)) 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) if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
}
val unitType = unit.type val unitType = unit.type
val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf() val attackableTiles: List<TileInfo> = if (unitType.isCivilian()) listOf()
@ -281,16 +283,16 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
else 0.5f else 0.5f
for (tile in tileGroups.values) { for (tile in tileGroups.values) {
if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout if (tile.icons.populationIcon != null) tile.icons.populationIcon!!.color.a = fadeout
if (tile.icons.improvementIcon != null && tile.tileInfo.improvement!=Constants.barbarianEncampment if (tile.icons.improvementIcon != null && tile.tileInfo.improvement != Constants.barbarianEncampment
&& tile.tileInfo.improvement!=Constants.ancientRuins) && tile.tileInfo.improvement != Constants.ancientRuins)
tile.icons.improvementIcon!!.color.a = fadeout tile.icons.improvementIcon!!.color.a = fadeout
if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout if (tile.resourceImage != null) tile.resourceImage!!.color.a = fadeout
} }
} }
private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet<Vector2>) { private fun updateTilegroupsForSelectedCity(city: CityInfo, playerViewableTilePositions: HashSet<Vector2>) {
val attackableTiles: List<TileInfo> = UnitAutomation().getBombardTargets(city) val attackableTiles = UnitAutomation().getBombardTargets(city)
.filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) } .filter { (UncivGame.Current.viewEntireMapForDebug || playerViewableTilePositions.contains(it.position)) }
for (attackableTile in attackableTiles) { for (attackableTile in attackableTiles) {
tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57)) tileGroups[attackableTile]!!.showCircle(colorFromRGB(237, 41, 57))
tileGroups[attackableTile]!!.showCrosshair() tileGroups[attackableTile]!!.showCrosshair()