AI units much more cautious when attacking cities - only enter city bombard range if there's a good chance of capturing the city

#416
This commit is contained in:
Yair Morgenstern 2019-02-09 21:31:16 +02:00
parent 3feb231620
commit 10e9a5e481
2 changed files with 41 additions and 11 deletions

View file

@ -21,8 +21,8 @@ android {
applicationId "com.unciv.app"
minSdkVersion 14
targetSdkVersion 28
versionCode 200
versionName "2.13.1"
versionCode 201
versionName "2.13.2"
}
// Had to add this crap for Travis to build, it wanted to sign the app

View file

@ -1,7 +1,6 @@
package com.unciv.logic.automation
import com.unciv.UnCivGame
import com.unciv.logic.HexMath
import com.unciv.logic.battle.*
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
@ -142,7 +141,7 @@ class UnitAutomation{
class AttackableTile(val tileToAttackFrom:TileInfo, val tileToAttack:TileInfo)
fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>, minMovementBeforeAttack: Float = 0.1f): ArrayList<AttackableTile> {
fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): ArrayList<AttackableTile> {
val tilesWithEnemies = unit.civInfo.viewableTiles
.filter { containsAttackableEnemy(it, MapUnitCombatant(unit)) }
@ -233,12 +232,43 @@ class UnitAutomation{
val closestReachableEnemyCity = enemyCities
.asSequence()
.filter { unit.movementAlgs().canReach(it) }
.minBy { city ->
unit.civInfo.cities.asSequence().map { HexMath().getDistance(city.position, it.getCenterTile().position) }.min()!!
.sortedBy { city -> // sort enemy cities by closeness to our cities, and only then choose the first reachable - checking canReach is comparatively very time-intensive!
unit.civInfo.cities.asSequence().map { city.arialDistanceTo(it.getCenterTile()) }.min()!!
}
.firstOrNull { unit.movementAlgs().canReach(it) }
if (closestReachableEnemyCity != null) {
unit.movementAlgs().headTowards(closestReachableEnemyCity)
val unitDistanceToTiles = unit.getDistanceToTiles()
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2)
val reachableTilesNotInBombardRange = unitDistanceToTiles.keys.filter { it !in tilesInBombardRange }
val canMoveIntoBombardRange = tilesInBombardRange.any { unitDistanceToTiles.containsKey(it)}
if(!canMoveIntoBombardRange) // no need to worry, keep going as the movement alg. says
unit.movementAlgs().headTowards(closestReachableEnemyCity)
else{
if(unit.getRange()>2){ // should never be in a bombardable position
val tilesCanAttackFromButNotInBombardRange =
reachableTilesNotInBombardRange.filter{it.arialDistanceTo(closestReachableEnemyCity) <= unit.getRange()}
// move into position far away enough that the bombard doesn't hurt
if(tilesCanAttackFromButNotInBombardRange.any())
unit.movementAlgs().headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!)
}
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!! }
var totalAttackOnCityPerTurn = -20 // cities heal 20 per turn, so anything below that its useless
val enemyCityCombatant = CityCombatant(closestReachableEnemyCity.getCity()!!)
for(militaryUnit in militaryUnitsAroundEnemyCity){
totalAttackOnCityPerTurn += BattleDamage().calculateDamageToDefender(MapUnitCombatant(militaryUnit), enemyCityCombatant)
}
if(totalAttackOnCityPerTurn * 3 > closestReachableEnemyCity.getCity()!!.health) // if we can defeat it in 3 turns with the current units,
unit.movementAlgs().headTowards(closestReachableEnemyCity) // go for it!
}
}
return true
}
return false
@ -246,7 +276,7 @@ class UnitAutomation{
private fun tryLandUnitToAttackPosition(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false
val attackableEnemiesNextTurn = getAttackableEnemies(unit,unitDistanceToTiles, -200f)
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying
.filter {
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
@ -264,7 +294,7 @@ class UnitAutomation{
}
private fun tryAttackNearbyEnemy(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean {
val attackableEnemies = getAttackableEnemies(unit,unitDistanceToTiles)
val attackableEnemies = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying
.filter {
BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit),
@ -435,7 +465,7 @@ class SpecificUnitAutomation{
fun automateGeneral(unit: MapUnit){
//try to follow nearby units. Do not garrison in city if possible
val militantToCompany = unit.getDistanceToTiles().map { it.key }
.firstOrNull {val militant = it.militaryUnit;
.firstOrNull {val militant = it.militaryUnit
militant != null && militant.civInfo == unit.civInfo
&& (it.civilianUnit == null || it.civilianUnit == unit)
&& militant.getMaxMovement() <= 2.0f && !it.isCityCenter()}