diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index d8e432f287..27af878be2 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -3,6 +3,7 @@ package com.unciv.logic.automation import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.BFS +import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.TileInfo import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.tile.ResourceType @@ -116,6 +117,7 @@ object Automation { } } + // Ranks a tile for any purpose except the expansion algorithm of cities internal fun rankTile(tile: TileInfo?, civInfo: CivilizationInfo): Float { if (tile == null) return 0f val tileOwner = tile.getOwner() @@ -130,6 +132,68 @@ object Automation { } return rank } + + // Ranks a tile for the expansion algorithm of cities + internal fun rankTileForExpansion(tile: TileInfo, cityInfo: CityInfo): Int { + // https://github.com/Gedemon/Civ5-DLL/blob/aa29e80751f541ae04858b6d2a2c7dcca454201e/CvGameCoreDLL_Expansion1/CvCity.cpp#L10301 + // Apparently this is not the full calculation. The exact tiles are also + // dependent on which tiles are between the chosen tile and the city center + // Exact details are not implemented, but can be found in CvAStar.cpp:2119, + // function `InfluenceCost()`. + // Implementing these will require an additional variable for each terrainType + val distance = tile.aerialDistanceTo(cityInfo.getCenterTile()) + + // Higher score means tile is less likely to be picked + var score = distance * 100 + + // Resources are good: less points + if (tile.hasViewableResource(cityInfo.civInfo)) { + if (tile.getTileResource().resourceType != ResourceType.Bonus) score -= 105 + else if (distance <= 3) score -= 104 + + } else { + // Water tiles without resources aren't great + if (tile.isWater) score += 25 + // Can't work it anyways + if (distance > 3) score += 100 + } + + // Improvements are good: less points + if (tile.improvement != null && + tile.getImprovementStats(tile.getTileImprovement()!!, cityInfo.civInfo, cityInfo).toHashMap().values.sum() > 0f + ) score -= 5 + + // The original checks if the tile has a road, but adds a score of 0 if it does. + // Therefore, this check is removed here. + + if (tile.naturalWonder != null) score -= 105 + + // Straight up take the sum of all yields + score -= tile.getTileStats(null, cityInfo.civInfo).toHashMap().values.sum().toInt() + + // Check if we get access to better tiles from this tile + var adjacentNaturalWonder = false + + for (adjacentTile in tile.neighbors.filter { it.getOwner() == null }) { + val adjacentDistance = cityInfo.getCenterTile().aerialDistanceTo(adjacentTile) + if (adjacentTile.hasViewableResource(cityInfo.civInfo) && + (adjacentDistance < 3 || + adjacentTile.getTileResource().resourceType != ResourceType.Bonus + ) + ) score -= 1 + if (adjacentTile.naturalWonder != null) { + if (adjacentDistance < 3) adjacentNaturalWonder = true + score -= 1 + } + } + if (adjacentNaturalWonder) score -= 1 + + // Tiles not adjacent to owned land are very hard to acquire + if (tile.neighbors.none { it.getCity() != null && it.getCity()!!.id == cityInfo.id }) + score += 1000 + + return score + } fun rankStatsValue(stats: Stats, civInfo: CivilizationInfo): Float { var rank = 0.0f diff --git a/core/src/com/unciv/logic/city/CityExpansionManager.kt b/core/src/com/unciv/logic/city/CityExpansionManager.kt index 24584b6339..574708b56b 100644 --- a/core/src/com/unciv/logic/city/CityExpansionManager.kt +++ b/core/src/com/unciv/logic/city/CityExpansionManager.kt @@ -72,19 +72,18 @@ class CityExpansionManager { return cost.roundToInt() } - fun chooseNewTileToOwn(): TileInfo? { - for (i in 2..5) { - val tiles = cityInfo.getCenterTile().getTilesInDistance(i) - .filter { - it.getOwner() == null - && it.neighbors.any { tile -> tile.getCity() == cityInfo } - } - val chosenTile = tiles.maxByOrNull { Automation.rankTile(it, cityInfo.civInfo) } - if (chosenTile != null) - return chosenTile + val choosableTiles = cityInfo.getCenterTile().getTilesInDistance(5) + .filter { it.getOwner() == null } + + // Technically, in the original a random tile with the lowest score was selected + // However, doing this requires either caching it, which is way more work, + // or selecting all possible tiles and only choosing one when the border expands. + // But since the order in which tiles are selected in distance is kinda random anyways, + // this is fine. + return choosableTiles.minByOrNull { + Automation.rankTileForExpansion(it, cityInfo) } - return null } //region state-changing functions