Resolved #6767 - AI will not declare war if it definitely can't take a city (#6771)

* Resolved #6767 - AI will not declare war if it definitely can't take a city

* Not sure what would happen if you try to calculate damage for civilian attacker and don't care to find out
This commit is contained in:
Yair Morgenstern
2022-05-13 12:33:21 +03:00
committed by GitHub
parent 09b4e82589
commit f0461121e4
6 changed files with 45 additions and 40 deletions

View File

@ -18,9 +18,10 @@ object BattleHelper {
val attackableEnemies = getAttackableEnemies(unit, unit.movement.getDistanceToTiles(), stayOnTile=stayOnTile)
// Only take enemies we can fight without dying
.filter {
BattleDamage.calculateDamageToAttacker(MapUnitCombatant(unit),
it.tileToAttackFrom,
Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health
BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(unit),
Battle.getMapCombatantOfTile(it.tileToAttack)!!
) < unit.health
}
val enemyTileToAttack = chooseAttackTarget(unit, attackableEnemies)
@ -129,9 +130,10 @@ object BattleHelper {
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying
.filter {
BattleDamage.calculateDamageToAttacker(MapUnitCombatant(unit),
it.tileToAttackFrom,
Battle.getMapCombatantOfTile(it.tileToAttack)!!) < unit.health
BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(unit),
Battle.getMapCombatantOfTile(it.tileToAttack)!!
) < unit.health
}
.filter { it.tileToAttackFrom.isLand }

View File

@ -1,6 +1,9 @@
package com.unciv.logic.automation
import com.unciv.Constants
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.INonPerpetualConstruction
import com.unciv.logic.city.PerpetualConstruction
@ -687,17 +690,33 @@ object NextTurnAutomation {
}
private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int {
if(civInfo.cities.isEmpty() || otherCiv.cities.isEmpty()) return 0
val baseForce = 30f
val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force).toFloat() + baseForce
var theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force).toFloat() + baseForce
//for city-states, also consider there protectors
if(otherCiv.isCityState() and otherCiv.getProtectorCivs().isNotEmpty()) {
if (otherCiv.isCityState() and otherCiv.getProtectorCivs().isNotEmpty()) {
theirCombatStrength += otherCiv.getProtectorCivs().sumOf{it.getStatForRanking(RankingType.Force)}
}
if (theirCombatStrength > ourCombatStrength) return 0
val closestCities = getClosestCities(civInfo, otherCiv)
val ourCity = closestCities.city1
val theirCity = closestCities.city2
if (civInfo.getCivUnits().filter { it.isMilitary() }.none {
val damageRecievedWhenAttacking =
BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(it),
CityCombatant(theirCity)
)
damageRecievedWhenAttacking < 100
})
return 0 // You don't have any units that can attack this city without dying, don't declare war.
fun isTileCanMoveThrough(tileInfo: TileInfo): Boolean {
val owner = tileInfo.getOwner()
return !tileInfo.isImpassible()
@ -721,9 +740,6 @@ object NextTurnAutomation {
}
modifierMap["Relative combat strength"] = combatStrengthModifier
val closestCities = getClosestCities(civInfo, otherCiv)
val ourCity = closestCities.city1
val theirCity = closestCities.city2
if (closestCities.aerialDistance > 7)
modifierMap["Far away cities"] = -10

View File

@ -356,9 +356,10 @@ object UnitAutomation {
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
).filter {
// Ignore units that would 1-shot you if you attacked. Account for taking terrain damage after the fact.
BattleDamage.calculateDamageToAttacker(MapUnitCombatant(unit),
it.tileToAttackFrom,
Battle.getMapCombatantOfTile(it.tileToAttack)!!)
BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(unit),
Battle.getMapCombatantOfTile(it.tileToAttack)!!
)
+ unit.getDamageFromTerrain(it.tileToAttackFrom) < unit.health
}
@ -413,25 +414,13 @@ object UnitAutomation {
// only focus on *attacking* 1 enemy at a time otherwise you'll lose on both fronts
val enemies = unit.civInfo.getKnownCivs().filter { unit.civInfo.isAtWarWith(it) && it.cities.isNotEmpty() }
val enemies = unit.civInfo.getKnownCivs()
.filter { unit.civInfo.isAtWarWith(it) && it.cities.isNotEmpty() }
val ourCities = unit.civInfo.cities
var closestEnemyCity:CityInfo?=null
var closestDistance = 10000
for (enemy in enemies) {
val knownEnemyCities = enemy.cities.filter { it.location in unit.civInfo.exploredTiles }
if (knownEnemyCities.isEmpty()) continue
for(enemyCity in knownEnemyCities) {
val distanceToClosestCityOfOurs = ourCities.minOf {
it.getCenterTile().aerialDistanceTo(enemyCity.getCenterTile())
}
if (distanceToClosestCityOfOurs < closestDistance){
closestDistance = distanceToClosestCityOfOurs
closestEnemyCity = enemyCity
}
}
}
val closestEnemyCity = enemies
.map { NextTurnAutomation.getClosestCities(unit.civInfo, it) }
.minByOrNull { it.aerialDistance }?.city2
if (closestEnemyCity==null) return false // no attackable cities found
// Our main attack target is the closest city, but we're fine with deviating from that a bit
@ -451,6 +440,7 @@ object UnitAutomation {
}
return false
}
private fun headTowardsEnemyCity(unit: MapUnit, closestReachableEnemyCity: TileInfo): Boolean {
val unitDistanceToTiles = unit.movement.getDistanceToTiles()

View File

@ -214,8 +214,8 @@ object Battle {
}
private fun takeDamage(attacker: ICombatant, defender: ICombatant) {
var potentialDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, attacker.getTile(), defender)
var potentialDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, attacker.getTile(), defender)
var potentialDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender)
var potentialDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender)
val defenderHealthBefore = defender.getHealth()
@ -803,9 +803,8 @@ object Battle {
if (Random().nextFloat() > interceptor.interceptChance() / 100f) return
var damage = BattleDamage.calculateDamageToDefender(
MapUnitCombatant(interceptor),
null,
attacker
MapUnitCombatant(interceptor),
attacker
)
var damageFactor = 1f + interceptor.interceptDamagePercentBonus().toFloat() / 100f

View File

@ -260,7 +260,6 @@ object BattleDamage {
fun calculateDamageToAttacker(
attacker: ICombatant,
tileToAttackFrom: TileInfo?,
defender: ICombatant,
ignoreRandomness: Boolean = false,
): Int {
@ -273,7 +272,6 @@ object BattleDamage {
fun calculateDamageToDefender(
attacker: ICombatant,
tileToAttackFrom: TileInfo?,
defender: ICombatant,
ignoreRandomness: Boolean = false,
): Int {

View File

@ -180,8 +180,8 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
row()
}
var damageToDefender = BattleDamage.calculateDamageToDefender(attacker, null, defender, true)
var damageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, null, defender, true)
var damageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender, true)
var damageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender, true)
if (damageToAttacker>attacker.getHealth() && damageToDefender>defender.getHealth()) // when damage exceeds health, we don't want to show negative health numbers