Air unit automation improvement (#10991)

* Improved AirUnitAutomation

* UnitPriority now has special cases for air units

* Fighters now Air-sweep

* Added extra air sweep logic

* Moved airSweepDamagePercentBonus to AirUnitAutomation.kt
This commit is contained in:
Oskar Niesen 2024-01-25 15:28:51 -06:00 committed by GitHub
parent 5cbc04b63a
commit d216db5ced
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 8 deletions

View File

@ -390,9 +390,15 @@ object NextTurnAutomation {
for (unit in sortedUnits) UnitAutomation.automateUnitMoves(unit)
}
/** Returns the priority of the unit, a lower value is higher priority **/
fun getUnitPriority(unit: MapUnit, isAtWar: Boolean): Int {
if (unit.isCivilian() && !unit.isGreatPersonOfType("War")) return 1 // Civilian
if (unit.baseUnit.isAirUnit()) return 2
if (unit.baseUnit.isAirUnit()) return when {
unit.canIntercept() -> 2 // Fighers first
unit.baseUnit.isNuclearWeapon() -> 3 // Then Nukes (area damage)
!unit.hasUnique(UniqueType.SelfDestructs) -> 4 // Then Bombers (reusable)
else -> 5 // Missiles
}
val distance = if (!isAtWar) 0 else unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(),6)
// Lower health units should move earlier to swap with higher health units
return distance + (unit.health / 10) + when {

View File

@ -1,21 +1,53 @@
package com.unciv.logic.automation.unit
import com.unciv.logic.battle.AirInterception
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.battle.Nuke
import com.unciv.logic.battle.TargetHelper
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.unique.UniqueType
object AirUnitAutomation {
fun automateFighter(unit: MapUnit) {
if (unit.health <= 50 && !unit.hasUnique(UniqueType.HealsEvenAfterAction)) return // Wait and heal
val tilesWithEnemyUnitsInRange = unit.civ.threatManager.getTilesWithEnemyUnitsInDistance(unit.getTile(), unit.getRange())
// TODO: Optimize [friendlyAirUnitsInRange] by creating an alternate [ThreatManager.getTilesWithEnemyUnitsInDistance] that handles only friendly units
val friendlyAirUnitsInRange = unit.getTile().getTilesInDistance(unit.getRange()).flatMap { it.airUnits }.filter { it.civ == unit.civ }
// Find all visible enemy air units
val enemyAirUnitsInRange = tilesWithEnemyUnitsInRange
.flatMap { it.airUnits.asSequence() }.filter { it.civ.isAtWarWith(unit.civ) }
val enemyFighters = enemyAirUnitsInRange.size / 2 // Assume half the planes are fighters
val friendlyUnusedFighterCount = friendlyAirUnitsInRange.count { it.health >= 50 && it.canAttack() }
val friendlyUsedFighterCount = friendlyAirUnitsInRange.count { it.health >= 50 && !it.canAttack() }
if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
// We need to be on standby in case they attack
if (friendlyUnusedFighterCount < enemyFighters) return
if (friendlyUsedFighterCount <= enemyFighters) {
fun airSweepDamagePercentBonus(): Int {
return unit.getMatchingUniques(UniqueType.StrengthWhenAirsweep)
.sumOf { it.params[0].toInt() }
}
// If we are outnumbered, don't heal after attacking and don't have an Air Sweep bonus
// Then we shouldn't speed the air battle by killing our fighters, instead, focus on defending
if (friendlyUsedFighterCount + friendlyUnusedFighterCount < enemyFighters
&& !unit.hasUnique(UniqueType.HealsEvenAfterAction)
&& airSweepDamagePercentBonus() <= 0) {
return
} else {
if (tryAirSweep(unit, tilesWithEnemyUnitsInRange)) return
}
}
if (unit.health < 80) {
return // Wait and heal up, no point in moving closer to battle if we aren't healed
}
if (BattleHelper.tryAttackNearbyEnemy(unit)) return
if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
@ -46,9 +78,25 @@ object AirUnitAutomation {
}
private fun tryAirSweep(unit: MapUnit, tilesWithEnemyUnitsInRange: List<Tile>):Boolean {
val targetTile = tilesWithEnemyUnitsInRange.filter {
tile -> tile.getUnits().any { it.civ.isAtWarWith(unit.civ)
|| (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(unit.civ)) }
}.minByOrNull { it.aerialDistanceTo(unit.getTile()) } ?: return false
AirInterception.airSweep(MapUnitCombatant(unit),targetTile)
if (unit.currentMovement > 0) return false
return true
}
fun automateBomber(unit: MapUnit) {
if (unit.health <= 50 && !unit.hasUnique(UniqueType.HealsEvenAfterAction)) return // Wait and heal
if (BattleHelper.tryAttackNearbyEnemy(unit)) return
if (unit.health <= 90 || (unit.health < 100 && !unit.civ.isAtWar())) {
return // Wait and heal
}
if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
val pathsToCities = unit.movement.getAerialPathsToCities()

View File

@ -15,8 +15,8 @@ object BattleHelper {
fun tryAttackNearbyEnemy(unit: MapUnit, stayOnTile: Boolean = false): Boolean {
if (unit.hasUnique(UniqueType.CannotAttack)) return false
val attackableEnemies = TargetHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles(), stayOnTile=stayOnTile)
// Only take enemies we can fight without dying
.filter {
// Only take enemies we can fight without dying or are made to die
.filter {unit.hasUnique(UniqueType.SelfDestructs) ||
BattleDamage.calculateDamageToAttacker(
MapUnitCombatant(unit),
Battle.getMapCombatantOfTile(it.tileToAttack)!!
@ -92,7 +92,7 @@ object BattleHelper {
if (attacker.baseUnit.isMelee()) {
val battleDamage = BattleDamage.calculateDamageToAttacker(attackerUnit, cityUnit)
if (attacker.health - battleDamage * 2 <= 0) {
if (attacker.health - battleDamage * 2 <= 0 && !attacker.hasUnique(UniqueType.SelfDestructs)) {
// The more fiendly units around the city, the more willing we should be to just attack the city
val friendlyUnitsAroundCity = city.getCenterTile().getTilesInDistance(3).count { it.militaryUnit?.civ == attacker.civ }
// If we have more than 4 other units around the city, go for it

View File

@ -197,15 +197,14 @@ object UnitAutomation {
if (unit.canIntercept())
return AirUnitAutomation.automateFighter(unit)
if (!unit.baseUnit.isNuclearWeapon())
return AirUnitAutomation.automateBomber(unit)
// Note that not all nukes have to be air units
if (unit.baseUnit.isNuclearWeapon())
return AirUnitAutomation.automateNukes(unit)
if (unit.hasUnique(UniqueType.SelfDestructs))
return AirUnitAutomation.automateMissile(unit)
return AirUnitAutomation.automateBomber(unit)
}
if (tryGoToRuinAndEncampment(unit) && unit.currentMovement == 0f) return