Resolved #11200 - Add AI for land-based nukes

This commit is contained in:
Yair Morgenstern 2024-02-26 22:13:20 +02:00
parent e6a132a9d7
commit a0f710b885
2 changed files with 28 additions and 24 deletions

View File

@ -1,6 +1,7 @@
package com.unciv.logic.automation.unit package com.unciv.logic.automation.unit
import com.unciv.logic.battle.AirInterception import com.unciv.logic.battle.AirInterception
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.battle.Nuke import com.unciv.logic.battle.Nuke
import com.unciv.logic.battle.TargetHelper import com.unciv.logic.battle.TargetHelper
@ -25,17 +26,17 @@ object AirUnitAutomation {
val friendlyUsedFighterCount = friendlyAirUnitsInRange.count { it.health >= 50 && !it.canAttack() } val friendlyUsedFighterCount = friendlyAirUnitsInRange.count { it.health >= 50 && !it.canAttack() }
// 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 (friendlyUnusedFighterCount < enemyFighters) return
if (friendlyUsedFighterCount <= enemyFighters) { if (friendlyUsedFighterCount <= enemyFighters) {
fun airSweepDamagePercentBonus(): Int { fun airSweepDamagePercentBonus(): Int {
return unit.getMatchingUniques(UniqueType.StrengthWhenAirsweep) return unit.getMatchingUniques(UniqueType.StrengthWhenAirsweep)
.sumOf { it.params[0].toInt() } .sumOf { it.params[0].toInt() }
} }
// If we are outnumbered, don't heal after attacking and don't have an Air Sweep bonus // 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 // Then we shouldn't speed the air battle by killing our fighters, instead, focus on defending
if (friendlyUsedFighterCount + friendlyUnusedFighterCount < enemyFighters if (friendlyUsedFighterCount + friendlyUnusedFighterCount < enemyFighters
&& !unit.hasUnique(UniqueType.HealsEvenAfterAction) && !unit.hasUnique(UniqueType.HealsEvenAfterAction)
&& airSweepDamagePercentBonus() <= 0) { && airSweepDamagePercentBonus() <= 0) {
return return
@ -47,7 +48,7 @@ object AirUnitAutomation {
if (unit.health < 80) { if (unit.health < 80) {
return // Wait and heal up, no point in moving closer to battle if we aren't healed return // Wait and heal up, no point in moving closer to battle if we aren't healed
} }
if (BattleHelper.tryAttackNearbyEnemy(unit)) return if (BattleHelper.tryAttackNearbyEnemy(unit)) return
if (tryRelocateToCitiesWithEnemyNearBy(unit)) return if (tryRelocateToCitiesWithEnemyNearBy(unit)) return
@ -79,8 +80,8 @@ object AirUnitAutomation {
} }
private fun tryAirSweep(unit: MapUnit, tilesWithEnemyUnitsInRange: List<Tile>):Boolean { private fun tryAirSweep(unit: MapUnit, tilesWithEnemyUnitsInRange: List<Tile>):Boolean {
val targetTile = tilesWithEnemyUnitsInRange.filter { val targetTile = tilesWithEnemyUnitsInRange.filter {
tile -> tile.getUnits().any { it.civ.isAtWarWith(unit.civ) tile -> tile.getUnits().any { it.civ.isAtWarWith(unit.civ)
|| (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(unit.civ)) } || (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(unit.civ)) }
}.minByOrNull { it.aerialDistanceTo(unit.getTile()) } ?: return false }.minByOrNull { it.aerialDistanceTo(unit.getTile()) } ?: return false
AirInterception.airSweep(MapUnitCombatant(unit),targetTile) AirInterception.airSweep(MapUnitCombatant(unit),targetTile)
@ -124,20 +125,22 @@ object AirUnitAutomation {
fun automateNukes(unit: MapUnit) { fun automateNukes(unit: MapUnit) {
if (!unit.civ.isAtWar()) return if (!unit.civ.isAtWar()) return
// We should *Almost* never want to nuke our own city, so don't consider it // We should *Almost* never want to nuke our own city, so don't consider it
val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange()) if (unit.type.isAirUnit()) {
var highestTileNukeValue = 0 val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
var tileToNuke: Tile? = null val highestTileNukeValue = tilesInRange.map { it to getNukeLocationValue(unit, it) }
tilesInRange.forEach { .maxByOrNull { it.second }
val value = getNukeLocationValue(unit, it) if (highestTileNukeValue != null && highestTileNukeValue.second > 0)
if (value > highestTileNukeValue) { Nuke.NUKE(MapUnitCombatant(unit), highestTileNukeValue.first)
highestTileNukeValue = value
tileToNuke = it tryRelocateMissileToNearbyAttackableCities(unit)
} } else {
val attackableTiles = TargetHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
val highestTileNukeValue = attackableTiles.map { it to getNukeLocationValue(unit, it.tileToAttack) }
.maxByOrNull { it.second }
if (highestTileNukeValue != null && highestTileNukeValue.second > 0)
Battle.moveAndAttack(MapUnitCombatant(unit), highestTileNukeValue.first)
HeadTowardsEnemyCityAutomation.tryHeadTowardsEnemyCity(unit)
} }
if (highestTileNukeValue > 0) {
Nuke.NUKE(MapUnitCombatant(unit), tileToNuke!!)
}
tryRelocateMissileToNearbyAttackableCities(unit)
} }
/** /**
@ -173,7 +176,7 @@ object AirUnitAutomation {
if (targetUnit.isInvisible(civ)) continue if (targetUnit.isInvisible(civ)) continue
// If we are nuking a unit at ground zero, it is more likely to be destroyed // If we are nuking a unit at ground zero, it is more likely to be destroyed
val tileExplosionValue = if (targetTile == tile) 80 else 50 val tileExplosionValue = if (targetTile == tile) 80 else 50
if (targetUnit.isMilitary()) { if (targetUnit.isMilitary()) {
explosionValue += if (targetTile == tile) evaluateCivValue(targetUnit.civ, -200, tileExplosionValue) explosionValue += if (targetTile == tile) evaluateCivValue(targetUnit.civ, -200, tileExplosionValue)
else evaluateCivValue(targetUnit.civ, -150, 50) else evaluateCivValue(targetUnit.civ, -150, 50)

View File

@ -194,14 +194,15 @@ object UnitAutomation {
return return
} }
// Note that not all nukes have to be air units
if (unit.baseUnit.isNuclearWeapon()) {
return AirUnitAutomation.automateNukes(unit)
}
if (unit.baseUnit.isAirUnit()) { if (unit.baseUnit.isAirUnit()) {
if (unit.canIntercept()) if (unit.canIntercept())
return AirUnitAutomation.automateFighter(unit) return AirUnitAutomation.automateFighter(unit)
// Note that not all nukes have to be air units
if (unit.baseUnit.isNuclearWeapon())
return AirUnitAutomation.automateNukes(unit)
if (unit.hasUnique(UniqueType.SelfDestructs)) if (unit.hasUnique(UniqueType.SelfDestructs))
return AirUnitAutomation.automateMissile(unit) return AirUnitAutomation.automateMissile(unit)