mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-09 15:29:32 +07:00
Make AI buy city tiles. (#9065)
* Make AI buy city tiles. * Address comments
This commit is contained in:
@ -45,6 +45,8 @@ import com.unciv.models.ruleset.unit.BaseUnit
|
|||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.screens.victoryscreen.RankingType
|
import com.unciv.ui.screens.victoryscreen.RankingType
|
||||||
|
import java.util.SortedMap
|
||||||
|
import java.util.TreeMap
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
object NextTurnAutomation {
|
object NextTurnAutomation {
|
||||||
@ -296,7 +298,6 @@ object NextTurnAutomation {
|
|||||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
val diploManager = cityState.getDiplomacyManager(civInfo)
|
||||||
if (diploManager.getInfluence() < 40) { // we want to gain influence with them
|
if (diploManager.getInfluence() < 40) { // we want to gain influence with them
|
||||||
tryGainInfluence(civInfo, cityState)
|
tryGainInfluence(civInfo, cityState)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,7 +309,6 @@ object NextTurnAutomation {
|
|||||||
potentialAllies.maxByOrNull { valueCityStateAlliance(civInfo, it) }!!
|
potentialAllies.maxByOrNull { valueCityStateAlliance(civInfo, it) }!!
|
||||||
if (cityState.getAllyCiv() != civInfo.civName && valueCityStateAlliance(civInfo, cityState) > 0) {
|
if (cityState.getAllyCiv() != civInfo.civName && valueCityStateAlliance(civInfo, cityState) > 0) {
|
||||||
tryGainInfluence(civInfo, cityState)
|
tryGainInfluence(civInfo, cityState)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,6 +321,98 @@ object NextTurnAutomation {
|
|||||||
city.cityConstructions.purchaseConstruction(construction.name, 0, true)
|
city.cityConstructions.purchaseConstruction(construction.name, 0, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybeBuyCityTiles(civInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun maybeBuyCityTiles(civInfo: Civilization) {
|
||||||
|
if (civInfo.gold <= 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
val highlyDesirableTiles: SortedMap<Tile, MutableSet<City>> = TreeMap(
|
||||||
|
compareByDescending<Tile?> { it?.naturalWonder != null }
|
||||||
|
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Luxury }
|
||||||
|
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Strategic }
|
||||||
|
// This is necessary, so that the map keeps Tiles with the same resource as two
|
||||||
|
// separate entries.
|
||||||
|
.thenBy { it.hashCode() }
|
||||||
|
)
|
||||||
|
for (city in civInfo.cities.filter { !it.isPuppet && !it.isBeingRazed }) {
|
||||||
|
val highlyDesirableTilesInCity = city.tilesInRange.filter {
|
||||||
|
val hasNaturalWonder = it.naturalWonder != null
|
||||||
|
val hasLuxuryCivDoesntOwn =
|
||||||
|
it.hasViewableResource(civInfo) &&
|
||||||
|
it.tileResource.resourceType == ResourceType.Luxury &&
|
||||||
|
!civInfo.hasResource(it.resource!!)
|
||||||
|
val hasResourceCivHasNoneOrLittle =
|
||||||
|
(it.hasViewableResource(civInfo)
|
||||||
|
&& it.tileResource.resourceType == ResourceType.Strategic &&
|
||||||
|
(civInfo.getCivResourcesByName()[it.resource!!] ?: 0) <= 3)
|
||||||
|
it.isVisible(civInfo) && it.getOwner() == null &&
|
||||||
|
(hasNaturalWonder || hasLuxuryCivDoesntOwn || hasResourceCivHasNoneOrLittle)
|
||||||
|
}
|
||||||
|
for (highlyDesirableTileInCity in highlyDesirableTilesInCity) {
|
||||||
|
highlyDesirableTiles.getOrPut(highlyDesirableTileInCity) { mutableSetOf() }
|
||||||
|
.add(city)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always try to buy highly desirable tiles if it can be afforded.
|
||||||
|
for (highlyDesirableTile in highlyDesirableTiles) {
|
||||||
|
val cityWithLeastCostToBuy = highlyDesirableTile.value.minBy {
|
||||||
|
it.getCenterTile().aerialDistanceTo(highlyDesirableTile.key)
|
||||||
|
}
|
||||||
|
val bfs = BFS(cityWithLeastCostToBuy.getCenterTile()) {
|
||||||
|
it.getOwner() == null || it.getOwner() == civInfo
|
||||||
|
}
|
||||||
|
bfs.stepUntilDestination(highlyDesirableTile.key)
|
||||||
|
val tilesThatNeedBuying =
|
||||||
|
bfs.getPathTo(highlyDesirableTile.key).filter { it.getOwner() != civInfo }
|
||||||
|
|
||||||
|
// We're trying to acquire everything and revert if it fails, because of the difficult
|
||||||
|
// way how tile acquisition cost is calculated. Everytime you buy a tile, the next one
|
||||||
|
// gets more expensive and by how much depends on other things such as game speed. To
|
||||||
|
// not introduce hidden dependencies on that and duplicate that logic here to calculate
|
||||||
|
// the price of the whole path, this is probably simpler.
|
||||||
|
var ranOutOfMoney = false
|
||||||
|
var goldSpent = 0
|
||||||
|
for (tileThatNeedsBuying in tilesThatNeedBuying) {
|
||||||
|
val goldCostOfTile =
|
||||||
|
cityWithLeastCostToBuy.expansion.getGoldCostOfTile(tileThatNeedsBuying)
|
||||||
|
if (civInfo.gold >= goldCostOfTile) {
|
||||||
|
cityWithLeastCostToBuy.expansion.buyTile(tileThatNeedsBuying)
|
||||||
|
goldSpent += goldCostOfTile
|
||||||
|
} else {
|
||||||
|
ranOutOfMoney = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ranOutOfMoney) {
|
||||||
|
for (tileThatNeedsBuying in tilesThatNeedBuying) {
|
||||||
|
cityWithLeastCostToBuy.expansion.relinquishOwnership(tileThatNeedsBuying)
|
||||||
|
}
|
||||||
|
civInfo.addGold(goldSpent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After highly desirable tiles, just buy the next tile the city expansion would go to if
|
||||||
|
// it doesn't consume too much of the total wealth. One could make this a lot more complex,
|
||||||
|
// but this is also taking cycles on the automation, so it might not be worth it. Things I
|
||||||
|
// considered where whether enemy cities / settlers are close etc., but I think this is a
|
||||||
|
// good approximation.
|
||||||
|
if (civInfo.gold > 500) {
|
||||||
|
var maxGoldToSpend = (civInfo.gold * 0.3).toInt()
|
||||||
|
for (city in civInfo.cities) {
|
||||||
|
val newTileToOwn = city.expansion.chooseNewTileToOwn()
|
||||||
|
if (newTileToOwn != null) {
|
||||||
|
val costOfNewTileToOwn = city.expansion.getGoldCostOfTile(newTileToOwn)
|
||||||
|
if (costOfNewTileToOwn > maxGoldToSpend) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
city.expansion.buyTile(newTileToOwn)
|
||||||
|
maxGoldToSpend -= costOfNewTileToOwn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int {
|
private fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int {
|
||||||
|
Reference in New Issue
Block a user