mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 21:35:14 -04:00
Better declare war and city battle decisions (hopefully) for AI
This commit is contained in:
parent
38333c2600
commit
3865972737
@ -8,11 +8,13 @@ import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
||||
import com.unciv.logic.map.BFS
|
||||
import com.unciv.logic.map.MapUnit
|
||||
import com.unciv.logic.trade.*
|
||||
import com.unciv.models.ruleset.VictoryType
|
||||
import com.unciv.models.ruleset.tech.Technology
|
||||
import com.unciv.models.translations.tr
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.math.min
|
||||
|
||||
object NextTurnAutomation{
|
||||
@ -394,24 +396,56 @@ object NextTurnAutomation{
|
||||
.filterNot { it == civInfo || it.cities.isEmpty() || !civInfo.getDiplomacyManager(it).canDeclareWar() }
|
||||
if (enemyCivs.isEmpty()) return
|
||||
|
||||
var enemyCivsToEvaluate = enemyCivs.filter { getMinDistanceBetweenCities(civInfo, it) <= 7 }
|
||||
val civWithBestMotivationToAttack = enemyCivs
|
||||
.map { Pair(it, motivationToAttack(civInfo, it)) }
|
||||
.maxBy { it.second }!!
|
||||
|
||||
var isFightingFarAway = false
|
||||
if (enemyCivsToEvaluate.isEmpty()) { // so, there ARE civs we can declare war on, just not close by
|
||||
if (civInfo.victoryType() != VictoryType.Domination) return // No point in attacking civs that are too far away
|
||||
enemyCivsToEvaluate = enemyCivs
|
||||
isFightingFarAway = true
|
||||
if (civWithBestMotivationToAttack.second >= 20)
|
||||
civInfo.getDiplomacyManager(civWithBestMotivationToAttack.first).declareWar()
|
||||
}
|
||||
|
||||
private fun motivationToAttack(civInfo:CivilizationInfo, otherCiv:CivilizationInfo): Int {
|
||||
val ourCombatStrength = Automation.evaluteCombatStrength(civInfo).toFloat()
|
||||
val theirCombatStrength = Automation.evaluteCombatStrength(otherCiv)
|
||||
if (theirCombatStrength > ourCombatStrength) return 0
|
||||
|
||||
val modifierMap = HashMap<String, Int>()
|
||||
val combatStrengthRatio = ourCombatStrength / theirCombatStrength
|
||||
val combatStrengthModifier = when {
|
||||
combatStrengthRatio > 3f -> 30
|
||||
combatStrengthRatio > 2f -> 20
|
||||
combatStrengthRatio > 1.5f -> 10
|
||||
else -> 0
|
||||
}
|
||||
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
|
||||
|
||||
val landPathBFS = BFS(ourCity.getCenterTile()) {
|
||||
val owner = it.getOwner();
|
||||
it.isLand && !it.getBaseTerrain().impassable
|
||||
&& (owner == otherCiv || owner == null || civInfo.canEnterTiles(owner))
|
||||
}
|
||||
|
||||
val weakestCloseCiv = enemyCivsToEvaluate.minBy { Automation.evaluteCombatStrength(it) }!!
|
||||
val weakestCloseCivCombatStrength = Automation.evaluteCombatStrength(weakestCloseCiv)
|
||||
val ourCombatStrength = Automation.evaluteCombatStrength(civInfo)
|
||||
landPathBFS.stepUntilDestination(theirCity.getCenterTile())
|
||||
if (!landPathBFS.hasReachedTile(theirCity.getCenterTile()))
|
||||
modifierMap["No land path"] = -10
|
||||
|
||||
val amountWeNeedToBeStronger =
|
||||
if (civInfo.victoryType() == VictoryType.Domination && !isFightingFarAway) 1.5f else 2f
|
||||
if(civInfo.getDiplomacyManager(otherCiv).hasFlag(DiplomacyFlags.ResearchAgreement))
|
||||
modifierMap["Research Agreement"] = -5
|
||||
|
||||
if (weakestCloseCivCombatStrength * amountWeNeedToBeStronger < ourCombatStrength)
|
||||
civInfo.getDiplomacyManager(weakestCloseCiv).declareWar()
|
||||
if(civInfo.getDiplomacyManager(otherCiv).resourcesFromTrade().any { it.amount > 0 })
|
||||
modifierMap["Receiving trade resources"] = -5
|
||||
|
||||
if(theirCity.getTiles().none { it.neighbors.any { it.getOwner()==theirCity.civInfo && it.getCity() != theirCity } })
|
||||
modifierMap["Isolated city"] = 15
|
||||
|
||||
return modifierMap.values.sum()
|
||||
}
|
||||
|
||||
private fun automateUnits(civInfo: CivilizationInfo) {
|
||||
|
@ -281,9 +281,37 @@ object UnitAutomation {
|
||||
.firstOrNull { unit.movement.canReach(it) }
|
||||
|
||||
if (closestReachableEnemyCity != null) {
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
return headTowardsEnemyCity(unit, closestReachableEnemyCity)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun headTowardsEnemyCity(unit: MapUnit, closestReachableEnemyCity: TileInfo): Boolean {
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
val unitRange = unit.getRange()
|
||||
|
||||
if (unitRange > 2) { // long-ranged unit, should never be in a bombardable position
|
||||
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
|
||||
val tileToMoveTo =
|
||||
unitDistanceToTiles.asSequence()
|
||||
.filter {
|
||||
it.key.aerialDistanceTo(closestReachableEnemyCity) <=
|
||||
unitRange && it.key !in tilesInBombardRange
|
||||
}
|
||||
.minBy { it.value.totalDistance }?.key
|
||||
|
||||
// move into position far away enough that the bombard doesn't hurt
|
||||
if (tileToMoveTo != null) {
|
||||
unit.movement.headTowards(tileToMoveTo)
|
||||
return true
|
||||
}
|
||||
return false // didn't move
|
||||
}
|
||||
|
||||
val numberOfUnitsAroundCity = closestReachableEnemyCity.getTilesInDistance(4)
|
||||
.count { it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo }
|
||||
|
||||
if (numberOfUnitsAroundCity < 3) {
|
||||
// don't head straight to the city, try to head to landing grounds -
|
||||
// this is against tha AI's brilliant plan of having everyone embarked and attacking via sea when unnecessary.
|
||||
val tileToHeadTo = closestReachableEnemyCity.getTilesInDistanceRange(3..4)
|
||||
@ -291,43 +319,15 @@ object UnitAutomation {
|
||||
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
|
||||
.firstOrNull { unit.movement.canReach(it) }
|
||||
|
||||
if (tileToHeadTo != null) // no need to worry, keep going as the movement alg. says
|
||||
if (tileToHeadTo != null) { // no need to worry, keep going as the movement alg. says
|
||||
unit.movement.headTowards(tileToHeadTo)
|
||||
else {
|
||||
val unitRange = unit.getRange()
|
||||
if (unitRange > 2) { // should never be in a bombardable position
|
||||
val tilesInBombardRange = closestReachableEnemyCity.getTilesInDistance(2).toSet()
|
||||
val tileToMoveTo =
|
||||
unitDistanceToTiles.asSequence()
|
||||
.filter {
|
||||
it.key.aerialDistanceTo(closestReachableEnemyCity) <=
|
||||
unitRange && it.key !in tilesInBombardRange
|
||||
}
|
||||
.minBy { it.value.totalDistance }?.key
|
||||
|
||||
// move into position far away enough that the bombard doesn't hurt
|
||||
if (tileToMoveTo != null)
|
||||
unit.movement.headTowards(tileToMoveTo)
|
||||
} else { // unit range <= 2
|
||||
// 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)
|
||||
.map { it.militaryUnit }.filterNotNull()
|
||||
.filter { it.civInfo == unit.civInfo }
|
||||
//todo: use CONSTANT for 20
|
||||
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.movement.headTowards(closestReachableEnemyCity) // go for it!
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
unit.movement.headTowards(closestReachableEnemyCity) // go for it!
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun tryBombardEnemy(city: CityInfo): Boolean {
|
||||
|
Loading…
x
Reference in New Issue
Block a user