Ai nuke improvement (#9968)

* Improved Nuke AI

* AI can only nuke visible tiles now

* Removed an extra space

* Removed commented changes from another feature in testing

* Removed commented changes from another feature in testing again

* AI now doesn't calculate the value of nuking a tile while at peace

* Removed extra change related to attacking cities.
This commit is contained in:
Oskar Niesen 2023-08-28 02:49:57 -05:00 committed by GitHub
parent c8de5a7de3
commit 0db070a25f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 19 deletions

View File

@ -7,6 +7,7 @@ import com.unciv.logic.battle.GreatGeneralImplementation
import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.battle.TargetHelper import com.unciv.logic.battle.TargetHelper
import com.unciv.logic.city.City import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
@ -494,25 +495,76 @@ object SpecificUnitAutomation {
} }
fun automateNukes(unit: MapUnit) { fun automateNukes(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange()) if (!unit.civ.isAtWar()) return
for (tile in tilesInRange) { // We should *Almost* never want to nuke our own city, so don't consider it
// For now AI will only use nukes against cities because in all honesty that's the best use for them. val tilesInRange = unit.currentTile.getTilesInDistanceRange(2..unit.getRange())
if (tile.isCityCenter() var highestTileNukeValue = 0
&& tile.getOwner()!!.isAtWarWith(unit.civ) var tileToNuke: Tile? = null
&& tile.getCity()!!.health > tile.getCity()!!.getMaxHealth() / 2 tilesInRange.forEach {
&& Battle.mayUseNuke(MapUnitCombatant(unit), tile)) { val value = getNukeLocationValue(unit, it)
val blastRadius = unit.getNukeBlastRadius() if (value > highestTileNukeValue) {
highestTileNukeValue = value
tileToNuke = it
}
}
if (highestTileNukeValue > 0) {
Battle.NUKE(MapUnitCombatant(unit), tileToNuke!!)
}
tryRelocateToNearbyAttackableCities(unit)
}
/**
* Ranks the tile to nuke based off of all tiles in it's blast radius
* By default the value is -500 to prevent inefficient nuking.
*/
fun getNukeLocationValue(nuke: MapUnit, tile: Tile): Int {
val civ = nuke.civ
if (!Battle.mayUseNuke(MapUnitCombatant(nuke), tile)) return Int.MIN_VALUE
val blastRadius = nuke.getNukeBlastRadius()
val tilesInBlastRadius = tile.getTilesInDistance(blastRadius) val tilesInBlastRadius = tile.getTilesInDistance(blastRadius)
val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } + val civsInBlastRadius = tilesInBlastRadius.mapNotNull { it.getOwner() } +
tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ } tilesInBlastRadius.mapNotNull { it.getFirstUnit()?.civ }
// Don't nuke if it means we will be declaring war on someone! // Don't nuke if it means we will be declaring war on someone!
if (civsInBlastRadius.none { it != unit.civ && !it.isAtWarWith(unit.civ) }) { if (civsInBlastRadius.any { it != civ && !it.isAtWarWith(civ) }) return -100000
Battle.NUKE(MapUnitCombatant(unit), tile) // If there are no enemies to hit, don't nuke
return if (!civsInBlastRadius.any { it.isAtWarWith(civ) }) return -100000
// Launching a Nuke uses resources, therefore don't launch it by default
var explosionValue = -500
// Returns either ourValue or thierValue depending on if the input Civ matches the Nuke's Civ
fun evaluateCivValue(targetCiv: Civilization, ourValue: Int, theirValue: Int): Int {
if (targetCiv == civ) // We are nuking something that we own!
return ourValue
return theirValue // We are nuking an enemy!
} }
for (targetTile in tilesInBlastRadius) {
// We can only account for visible units
if (tile.isVisible(civ)) {
if (targetTile.militaryUnit != null && !targetTile.militaryUnit!!.isInvisible(civ))
explosionValue += evaluateCivValue(targetTile.militaryUnit?.civ!!, -150, 50)
if (targetTile.civilianUnit != null && !targetTile.civilianUnit!!.isInvisible(civ))
explosionValue += evaluateCivValue(targetTile.civilianUnit?.civ!!, -100, 25)
} }
// Never nuke our own Civ, don't nuke single enemy civs as well
if (targetTile.isCityCenter()
&& !(targetTile.getCity()!!.health <= 50f
&& targetTile.neighbors.any {it.militaryUnit?.civ == civ})) // Prefer not to nuke cities that we are about to take
explosionValue += evaluateCivValue(targetTile.getCity()?.civ!!, -100000, 250)
else if (targetTile.owningCity != null) {
val owningCiv = targetTile.owningCity?.civ!!
// If there is a tile to add fallout to there is a 50% chance it will get fallout
if (!(tile.isWater || tile.isImpassible() || targetTile.terrainFeatures.any { it == "Fallout" }))
explosionValue += evaluateCivValue(owningCiv, -40, 10)
// If there is an improvment to pillage
if (targetTile.improvement != null && !targetTile.improvementIsPillaged)
explosionValue += evaluateCivValue(owningCiv, -40, 20)
} }
tryRelocateToNearbyAttackableCities(unit) // If the value is too low end the search early
if (explosionValue < -1000) return explosionValue
}
return explosionValue
} }
// This really needs to be changed, to have better targeting for missiles // This really needs to be changed, to have better targeting for missiles

View File

@ -178,7 +178,7 @@ object UnitAutomation {
if (unit.baseUnit.isAirUnit() && unit.canIntercept()) if (unit.baseUnit.isAirUnit() && unit.canIntercept())
return SpecificUnitAutomation.automateFighter(unit) return SpecificUnitAutomation.automateFighter(unit)
if (unit.baseUnit.isAirUnit()) if (unit.baseUnit.isAirUnit() && !unit.baseUnit.isNuclearWeapon())
return SpecificUnitAutomation.automateBomber(unit) return SpecificUnitAutomation.automateBomber(unit)
if (unit.baseUnit.isNuclearWeapon()) if (unit.baseUnit.isNuclearWeapon())

View File

@ -759,6 +759,8 @@ object Battle {
*/ */
fun mayUseNuke(nuke: MapUnitCombatant, targetTile: Tile): Boolean { fun mayUseNuke(nuke: MapUnitCombatant, targetTile: Tile): Boolean {
if (nuke.getTile() == targetTile) return false if (nuke.getTile() == targetTile) return false
// Can only nuke visible Tiles
if (!targetTile.isVisible(nuke.getCivInfo())) return false
var canNuke = true var canNuke = true
val attackerCiv = nuke.getCivInfo() val attackerCiv = nuke.getCivInfo()