diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 67e2bf29ff..63fb8cf9c1 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -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() + 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) { diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 6ad3f8fba6..d324291979 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -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 {