mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-26 23:58:43 +07:00
AI doesn't settle very unfavorable locations (#11305)
* AI doesn't settle locations with less than 50 value * Settlers try and found cities at a distance of 7 more * Reduced the value of being on the coast * RankTile quits early if the tile is already owned
This commit is contained in:
@ -4,6 +4,7 @@ import com.unciv.logic.automation.Automation
|
|||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
|
import com.unciv.logic.map.HexMath
|
||||||
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
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
@ -21,7 +22,7 @@ object CityLocationTileRanker {
|
|||||||
/**
|
/**
|
||||||
* Returns a hashmap of tiles to their ranking plus the a the highest value tile and its value
|
* Returns a hashmap of tiles to their ranking plus the a the highest value tile and its value
|
||||||
*/
|
*/
|
||||||
fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null): BestTilesToFoundCity {
|
fun getBestTilesToFoundCity(unit: MapUnit, distanceToSearch: Int? = null, minimumValue: Float): BestTilesToFoundCity {
|
||||||
val range = if (distanceToSearch != null) distanceToSearch else {
|
val range = if (distanceToSearch != null) distanceToSearch else {
|
||||||
val distanceFromHome = if (unit.civ.cities.isEmpty()) 0
|
val distanceFromHome = if (unit.civ.cities.isEmpty()) 0
|
||||||
else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
|
else unit.civ.cities.minOf { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
|
||||||
@ -39,10 +40,11 @@ object CityLocationTileRanker {
|
|||||||
val possibleTileLocationsWithRank = possibleCityLocations
|
val possibleTileLocationsWithRank = possibleCityLocations
|
||||||
.map {
|
.map {
|
||||||
val tileValue = rankTileToSettle(it, unit.civ, nearbyCities, baseTileMap, uniqueCache)
|
val tileValue = rankTileToSettle(it, unit.civ, nearbyCities, baseTileMap, uniqueCache)
|
||||||
bestTilesToFoundCity.tileRankMap[it] = tileValue
|
if (tileValue >= minimumValue)
|
||||||
|
bestTilesToFoundCity.tileRankMap[it] = tileValue
|
||||||
|
|
||||||
Pair(it, tileValue)
|
Pair(it, tileValue)
|
||||||
}
|
}.filter { it.second >= minimumValue }
|
||||||
.sortedByDescending { it.second }
|
.sortedByDescending { it.second }
|
||||||
|
|
||||||
val bestReachableTile = possibleTileLocationsWithRank.firstOrNull { unit.movement.canReach(it.first) }
|
val bestReachableTile = possibleTileLocationsWithRank.firstOrNull { unit.movement.canReach(it.first) }
|
||||||
@ -85,7 +87,7 @@ object CityLocationTileRanker {
|
|||||||
// Only count a luxary resource that we don't have yet as unique once
|
// Only count a luxary resource that we don't have yet as unique once
|
||||||
val newUniqueLuxuryResources = HashSet<String>()
|
val newUniqueLuxuryResources = HashSet<String>()
|
||||||
|
|
||||||
if (onCoast) tileValue += 15
|
if (onCoast) tileValue += 8
|
||||||
if (newCityTile.isAdjacentToRiver()) tileValue += 10
|
if (newCityTile.isAdjacentToRiver()) tileValue += 10
|
||||||
if (newCityTile.terrainHasUnique(UniqueType.FreshWater)) tileValue += 5
|
if (newCityTile.terrainHasUnique(UniqueType.FreshWater)) tileValue += 5
|
||||||
// We want to found the city on an oasis because it can't be improved otherwise
|
// We want to found the city on an oasis because it can't be improved otherwise
|
||||||
@ -93,11 +95,16 @@ object CityLocationTileRanker {
|
|||||||
// If we build the city on a resource tile, then we can't build any special improvements on it
|
// If we build the city on a resource tile, then we can't build any special improvements on it
|
||||||
if (newCityTile.resource != null) tileValue -= 4
|
if (newCityTile.resource != null) tileValue -= 4
|
||||||
|
|
||||||
|
var tiles = 0
|
||||||
for (i in 0..3) {
|
for (i in 0..3) {
|
||||||
for (nearbyTile in newCityTile.getTilesAtDistance(i)) {
|
for (nearbyTile in newCityTile.getTilesAtDistance(i)) {
|
||||||
|
tiles++
|
||||||
tileValue += rankTile(nearbyTile, civ, onCoast, newUniqueLuxuryResources, baseTileMap, uniqueCache)
|
tileValue += rankTile(nearbyTile, civ, onCoast, newUniqueLuxuryResources, baseTileMap, uniqueCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Placing cities on the edge of the map is bad, we can't even build improvements on them!
|
||||||
|
tileValue -= (HexMath.getNumberOfTilesInHexagon(3) - tiles) * 3
|
||||||
return tileValue
|
return tileValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,35 +118,37 @@ object CityLocationTileRanker {
|
|||||||
// If it is not higher the settler may get stuck when it ranks the same tile differently
|
// If it is not higher the settler may get stuck when it ranks the same tile differently
|
||||||
// as it moves away from the city and doesn't include it in the calculation
|
// as it moves away from the city and doesn't include it in the calculation
|
||||||
// and values it higher than when it moves closer to the city
|
// and values it higher than when it moves closer to the city
|
||||||
distanceToCity == 6 -> 2f
|
distanceToCity == 7 -> 5f // Perfect location, there aren't any unused tiles in between
|
||||||
distanceToCity == 5 -> 5f
|
distanceToCity == 6 -> -4f
|
||||||
distanceToCity == 4 -> 10f
|
distanceToCity == 5 -> -8f
|
||||||
distanceToCity == 3 -> 15f
|
distanceToCity == 4 -> -20f
|
||||||
distanceToCity < 3 -> 25f // Even if it is a mod that lets us settle closer, lets still not do it
|
distanceToCity == 3 -> -25f
|
||||||
|
distanceToCity < 3 -> -30f // Even if it is a mod that lets us settle closer, lets still not do it
|
||||||
else -> 0f
|
else -> 0f
|
||||||
}
|
}
|
||||||
// Bigger cities will expand more so we want to stay away from them
|
// Bigger cities will expand more so we want to stay away from them
|
||||||
// Reduces the chance that we don't settle at the begining
|
// Reduces the chance that we don't settle at the begining
|
||||||
distanceToCityModifier *= when {
|
distanceToCityModifier *= when {
|
||||||
city.population.population >= 12 -> 4f
|
city.population.population >= 12 -> 2f
|
||||||
city.population.population >= 8 -> 3f
|
city.population.population >= 8 -> 1.5f
|
||||||
city.population.population >= 3 -> 2f
|
city.population.population >= 3 -> 1.2f
|
||||||
else -> 1f
|
else -> 1f
|
||||||
}
|
}
|
||||||
// It is worse to settle cities near our own compare to near another civ
|
// It is worse to settle cities near our own compare to near another civ
|
||||||
// Do not settle near our capital unless really necessary
|
// Do not settle near our capital unless really necessary
|
||||||
// Having a strong capital is esential to constructing wonders
|
// Having a strong capital is esential to constructing wonders
|
||||||
if (city.civ == civ) distanceToCityModifier *= if (city.isCapital()) 5 else 2
|
if (city.civ == civ) distanceToCityModifier *= if (city.isCapital()) 2 else 1
|
||||||
modifier -= distanceToCityModifier
|
modifier += distanceToCityModifier
|
||||||
}
|
}
|
||||||
return modifier
|
return modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rankTile(rankTile: Tile, civ: Civilization, onCoast: Boolean, newUniqueLuxuryResources: HashSet<String>,
|
private fun rankTile(rankTile: Tile, civ: Civilization, onCoast: Boolean, newUniqueLuxuryResources: HashSet<String>,
|
||||||
baseTileMap: HashMap<Tile, Float>, uniqueCache: LocalUniqueCache): Float {
|
baseTileMap: HashMap<Tile, Float>, uniqueCache: LocalUniqueCache): Float {
|
||||||
|
if (rankTile.getCity() != null) return -1f
|
||||||
var locationSpecificTileValue = 0f
|
var locationSpecificTileValue = 0f
|
||||||
// Don't settle near but not on the coast
|
// Don't settle near but not on the coast
|
||||||
if (rankTile.isCoastalTile() && !onCoast) locationSpecificTileValue -= 10
|
if (rankTile.isCoastalTile() && !onCoast) locationSpecificTileValue -= 2
|
||||||
// Apply the effect of having a lighthouse, since we can probably assume that we will build it
|
// Apply the effect of having a lighthouse, since we can probably assume that we will build it
|
||||||
if (onCoast && rankTile.isOcean) locationSpecificTileValue += 1
|
if (onCoast && rankTile.isOcean) locationSpecificTileValue += 1
|
||||||
// Check if there are any new unique luxury resources
|
// Check if there are any new unique luxury resources
|
||||||
@ -154,6 +163,8 @@ object CityLocationTileRanker {
|
|||||||
if (rankTile.getOwner() != null && rankTile.getOwner() != civ) return 0f
|
if (rankTile.getOwner() != null && rankTile.getOwner() != civ) return 0f
|
||||||
|
|
||||||
var rankTileValue = Automation.rankStatsValue(rankTile.stats.getTileStats(null, civ, uniqueCache), civ)
|
var rankTileValue = Automation.rankStatsValue(rankTile.stats.getTileStats(null, civ, uniqueCache), civ)
|
||||||
|
// We can't build improvements on water tiles without resources
|
||||||
|
if (!rankTile.isLand) rankTileValue -= 1
|
||||||
|
|
||||||
if (rankTile.resource != null) {
|
if (rankTile.resource != null) {
|
||||||
rankTileValue += when (rankTile.tileResource.resourceType) {
|
rankTileValue += when (rankTile.tileResource.resourceType) {
|
||||||
|
@ -107,7 +107,7 @@ object SpecificUnitAutomation {
|
|||||||
|
|
||||||
// It's possible that we'll see a tile "over the sea" that's better than the tiles close by, but that's not a reason to abandon the close tiles!
|
// It's possible that we'll see a tile "over the sea" that's better than the tiles close by, but that's not a reason to abandon the close tiles!
|
||||||
// Also this lead to some routing problems, see https://github.com/yairm210/Unciv/issues/3653
|
// Also this lead to some routing problems, see https://github.com/yairm210/Unciv/issues/3653
|
||||||
val bestTilesInfo = CityLocationTileRanker.getBestTilesToFoundCity(unit, rangeToSearch)
|
val bestTilesInfo = CityLocationTileRanker.getBestTilesToFoundCity(unit, rangeToSearch, 50f)
|
||||||
var bestCityLocation: Tile? = null
|
var bestCityLocation: Tile? = null
|
||||||
|
|
||||||
if (unit.civ.gameInfo.turns == 0 && unit.civ.cities.isEmpty() && bestTilesInfo.tileRankMap.containsKey(unit.getTile())) { // Special case, we want AI to settle in place on turn 1.
|
if (unit.civ.gameInfo.turns == 0 && unit.civ.cities.isEmpty() && bestTilesInfo.tileRankMap.containsKey(unit.getTile())) { // Special case, we want AI to settle in place on turn 1.
|
||||||
@ -133,7 +133,7 @@ object SpecificUnitAutomation {
|
|||||||
|
|
||||||
// If the tile we are currently on is close to the best tile, then lets just settle here instead
|
// If the tile we are currently on is close to the best tile, then lets just settle here instead
|
||||||
if (bestTilesInfo.tileRankMap.containsKey(unit.getTile())
|
if (bestTilesInfo.tileRankMap.containsKey(unit.getTile())
|
||||||
&& (bestTilesInfo.bestTile == null || bestTilesInfo.tileRankMap[unit.getTile()]!! >= bestTilesInfo.bestTileRank - 10)) {
|
&& (bestTilesInfo.bestTile == null || bestTilesInfo.tileRankMap[unit.getTile()]!! >= bestTilesInfo.bestTileRank - 2)) {
|
||||||
bestCityLocation = unit.getTile()
|
bestCityLocation = unit.getTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,7 +858,7 @@ class WorldMapHolder(
|
|||||||
// Highlight best tiles for city founding
|
// Highlight best tiles for city founding
|
||||||
if (unit.hasUnique(UniqueType.FoundCity)
|
if (unit.hasUnique(UniqueType.FoundCity)
|
||||||
&& UncivGame.Current.settings.showSettlersSuggestedCityLocations) {
|
&& UncivGame.Current.settings.showSettlersSuggestedCityLocations) {
|
||||||
CityLocationTileRanker.getBestTilesToFoundCity(unit).tileRankMap.asSequence()
|
CityLocationTileRanker.getBestTilesToFoundCity(unit, minimumValue = 50f).tileRankMap.asSequence()
|
||||||
.filter { it.key.isExplored(unit.civ) }.sortedByDescending { it.value }.take(3).forEach {
|
.filter { it.key.isExplored(unit.civ) }.sortedByDescending { it.value }.take(3).forEach {
|
||||||
tileGroups[it.key]!!.layerOverlay.showGoodCityLocationIndicator()
|
tileGroups[it.key]!!.layerOverlay.showGoodCityLocationIndicator()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user