AI knows not to try and heal units which would heal anyway

This commit is contained in:
Yair Morgenstern
2020-12-14 11:31:13 +02:00
parent fad967b31a
commit 24b46e8c2c

View File

@ -16,7 +16,7 @@ object UnitAutomation {
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
private fun isGoodTileToExplore(unit:MapUnit, tile:TileInfo): Boolean { private fun isGoodTileToExplore(unit: MapUnit, tile: TileInfo): Boolean {
return unit.movement.canMoveTo(tile) return unit.movement.canMoveTo(tile)
&& (tile.getOwner() == null || !tile.getOwner()!!.isCityState()) && (tile.getOwner() == null || !tile.getOwner()!!.isCityState())
&& tile.neighbors.any { it.position !in unit.civInfo.exploredTiles } && tile.neighbors.any { it.position !in unit.civInfo.exploredTiles }
@ -87,7 +87,7 @@ object UnitAutomation {
if (unit.civInfo.isBarbarian()) if (unit.civInfo.isBarbarian())
throw IllegalStateException("Barbarians is not allowed here.") throw IllegalStateException("Barbarians is not allowed here.")
if(unit.type.isCivilian()) { if (unit.type.isCivilian()) {
if (unit.hasUnique(Constants.settlerUnique)) if (unit.hasUnique(Constants.settlerUnique))
return SpecificUnitAutomation.automateSettlerActions(unit) return SpecificUnitAutomation.automateSettlerActions(unit)
@ -125,7 +125,7 @@ object UnitAutomation {
// Accompany settlers // Accompany settlers
if (tryAccompanySettlerOrGreatPerson(unit)) return if (tryAccompanySettlerOrGreatPerson(unit)) return
if(tryHeadTowardsSiegedCity(unit)) return if (tryHeadTowardsSiegedCity(unit)) return
if (unit.health < 50 && tryHealUnit(unit)) return // do nothing but heal if (unit.health < 50 && tryHealUnit(unit)) return // do nothing but heal
@ -135,7 +135,7 @@ object UnitAutomation {
// if there is an attackable unit in the vicinity, attack! // if there is an attackable unit in the vicinity, attack!
if (BattleHelper.tryAttackNearbyEnemy(unit)) return if (BattleHelper.tryAttackNearbyEnemy(unit)) return
if(tryTakeBackCapturedCity(unit)) return if (tryTakeBackCapturedCity(unit)) return
if (tryGarrisoningUnit(unit)) return if (tryGarrisoningUnit(unit)) return
@ -171,9 +171,14 @@ object UnitAutomation {
} }
fun tryHealUnit(unit: MapUnit): Boolean { fun tryHealUnit(unit: MapUnit): Boolean {
if (unit.type.isRanged() && unit.hasUnique("Unit will heal every turn, even if it performs an action"))
return false // will heal anyway, and attacks don't hurt
val unitDistanceToTiles = unit.movement.getDistanceToTiles() val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val tilesInDistance = unitDistanceToTiles.keys.filter { unit.movement.canMoveTo(it) } val tilesInDistance = unitDistanceToTiles.keys.filter { unit.movement.canMoveTo(it) }
if (unitDistanceToTiles.isEmpty()) return true // can't move, so... if (unitDistanceToTiles.isEmpty()) return true // can't move, so...
val currentUnitTile = unit.getTile() val currentUnitTile = unit.getTile()
if (tryPillageImprovement(unit)) return true if (tryPillageImprovement(unit)) return true
@ -191,14 +196,16 @@ object UnitAutomation {
var bestTilesForHealing = tilesByHealingRate.maxBy { it.key }!!.value var bestTilesForHealing = tilesByHealingRate.maxBy { it.key }!!.value
// within the tiles with best healing rate (say 15), we'll prefer one which has the highest defensive bonuses // within the tiles with best healing rate (say 15), we'll prefer one which has the highest defensive bonuses
val bestTilesWithoutBombardableTiles = bestTilesForHealing.filterNot { it.getTilesInDistance(2) val bestTilesWithoutBombardableTiles = bestTilesForHealing.filterNot {
.any { it.isCityCenter() && it.getOwner()!!.isAtWarWith(unit.civInfo) } } it.getTilesInDistance(2)
if(bestTilesWithoutBombardableTiles.any()) bestTilesForHealing = bestTilesWithoutBombardableTiles .any { it.isCityCenter() && it.getOwner()!!.isAtWarWith(unit.civInfo) }
}
if (bestTilesWithoutBombardableTiles.any()) bestTilesForHealing = bestTilesWithoutBombardableTiles
val bestTileForHealing = bestTilesForHealing.maxBy { it.getDefensiveBonus() }!! val bestTileForHealing = bestTilesForHealing.maxBy { it.getDefensiveBonus() }!!
val bestTileForHealingRank = unit.rankTileForHealing(bestTileForHealing) val bestTileForHealingRank = unit.rankTileForHealing(bestTileForHealing)
if (currentUnitTile != bestTileForHealing if (currentUnitTile != bestTileForHealing
&& bestTileForHealingRank > unit.rankTileForHealing(currentUnitTile)) && bestTileForHealingRank > unit.rankTileForHealing(currentUnitTile))
unit.movement.moveToTile(bestTileForHealing) unit.movement.moveToTile(bestTileForHealing)
unit.fortifyIfCan() unit.fortifyIfCan()
@ -243,7 +250,7 @@ object UnitAutomation {
// 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),
it.tileToAttackFrom, it.tileToAttackFrom,
Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health
} }
if (unit.type.isRanged()) if (unit.type.isRanged())
@ -262,8 +269,8 @@ object UnitAutomation {
val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits() val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits()
.firstOrNull { .firstOrNull {
val tile = it.currentTile val tile = it.currentTile
it.type==UnitType.Civilian && it.type == UnitType.Civilian &&
(it.hasUnique(Constants.settlerUnique) || unit.name in GreatPersonManager().statToGreatPersonMapping.values) (it.hasUnique(Constants.settlerUnique) || unit.name in GreatPersonManager().statToGreatPersonMapping.values)
&& tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile) && tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile)
} }
if (settlerOrGreatPersonToAccompany == null) return false if (settlerOrGreatPersonToAccompany == null) return false
@ -274,8 +281,10 @@ object UnitAutomation {
private fun tryHeadTowardsSiegedCity(unit: MapUnit): Boolean { private fun tryHeadTowardsSiegedCity(unit: MapUnit): Boolean {
val siegedCities = unit.civInfo.cities val siegedCities = unit.civInfo.cities
.asSequence() .asSequence()
.filter { unit.civInfo == it.civInfo && .filter {
it.health < it.getMaxHealth() * 0.75 } //Weird health issues and making sure that not all forces move to good defenses unit.civInfo == it.civInfo &&
it.health < it.getMaxHealth() * 0.75
} //Weird health issues and making sure that not all forces move to good defenses
val reachableTileNearSiegedCity = siegedCities val reachableTileNearSiegedCity = siegedCities
.flatMap { it.getCenterTile().getTilesAtDistance(2) } .flatMap { it.getCenterTile().getTilesAtDistance(2) }
@ -389,10 +398,12 @@ object UnitAutomation {
private fun tryTakeBackCapturedCity(unit: MapUnit): Boolean { private fun tryTakeBackCapturedCity(unit: MapUnit): Boolean {
var capturedCities = unit.civInfo.getKnownCivs().asSequence() var capturedCities = unit.civInfo.getKnownCivs().asSequence()
.flatMap { it.cities.asSequence() } .flatMap { it.cities.asSequence() }
.filter { unit.civInfo.isAtWarWith(it.civInfo) && .filter {
unit.civInfo.civName == it.foundingCiv && unit.civInfo.isAtWarWith(it.civInfo) &&
it.isInResistance() && unit.civInfo.civName == it.foundingCiv &&
it.health < it.getMaxHealth()} //Most likely just been captured it.isInResistance() &&
it.health < it.getMaxHealth()
} //Most likely just been captured
if (unit.type.isRanged()) // ranged units don't harm capturable cities, waste of a turn if (unit.type.isRanged()) // ranged units don't harm capturable cities, waste of a turn
@ -415,7 +426,7 @@ object UnitAutomation {
val citiesWithoutGarrison = unit.civInfo.cities.filter { val citiesWithoutGarrison = unit.civInfo.cities.filter {
val centerTile = it.getCenterTile() val centerTile = it.getCenterTile()
centerTile.militaryUnit == null centerTile.militaryUnit == null
&& unit.movement.canMoveTo(centerTile) && unit.movement.canMoveTo(centerTile)
} }
fun isCityThatNeedsDefendingInWartime(city: CityInfo): Boolean { fun isCityThatNeedsDefendingInWartime(city: CityInfo): Boolean {
@ -434,7 +445,7 @@ object UnitAutomation {
citiesToTry = citiesWithoutGarrison.asSequence() citiesToTry = citiesWithoutGarrison.asSequence()
} else { } else {
if (unit.getTile().isCityCenter() && if (unit.getTile().isCityCenter() &&
isCityThatNeedsDefendingInWartime(unit.getTile().getCity()!!)) return true isCityThatNeedsDefendingInWartime(unit.getTile().getCity()!!)) return true
citiesToTry = citiesWithoutGarrison.asSequence() citiesToTry = citiesWithoutGarrison.asSequence()
.filter { isCityThatNeedsDefendingInWartime(it) } .filter { isCityThatNeedsDefendingInWartime(it) }
@ -449,7 +460,7 @@ object UnitAutomation {
} }
/** 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) {
if (tryGoToRuinAndEncampment(unit) && unit.currentMovement == 0f) return if (tryGoToRuinAndEncampment(unit) && unit.currentMovement == 0f) return
if (unit.health < 80 && tryHealUnit(unit)) return if (unit.health < 80 && tryHealUnit(unit)) return
@ -462,20 +473,20 @@ object UnitAutomation {
fun runAway(unit: MapUnit) { fun runAway(unit: MapUnit) {
val reachableTiles = unit.movement.getDistanceToTiles() val reachableTiles = unit.movement.getDistanceToTiles()
val enterableCity = reachableTiles.keys.firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) } val enterableCity = reachableTiles.keys.firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) }
if(enterableCity!=null) { if (enterableCity != null) {
unit.movement.moveToTile(enterableCity) unit.movement.moveToTile(enterableCity)
return return
} }
val tileFurthestFromEnemy = reachableTiles.keys.filter { unit.movement.canMoveTo(it) } val tileFurthestFromEnemy = reachableTiles.keys.filter { unit.movement.canMoveTo(it) }
.maxBy{ countDistanceToClosestEnemy(unit, it)} .maxBy { countDistanceToClosestEnemy(unit, it) }
if(tileFurthestFromEnemy==null) return // can't move anywhere! if (tileFurthestFromEnemy == null) return // can't move anywhere!
unit.movement.moveToTile(tileFurthestFromEnemy) unit.movement.moveToTile(tileFurthestFromEnemy)
} }
fun countDistanceToClosestEnemy(unit: MapUnit, tile: TileInfo): Int { fun countDistanceToClosestEnemy(unit: MapUnit, tile: TileInfo): Int {
for(i in 1..3) for (i in 1..3)
if(tile.getTilesAtDistance(i).any{containsEnemyMilitaryUnit(unit,it)}) if (tile.getTilesAtDistance(i).any { containsEnemyMilitaryUnit(unit, it) })
return i return i
return 4 return 4
} }