Workers now build forts (#10944)

* Workers now build forts

* Workers prioritise other tiles over removing forts

* Units don't pillage forts

* Increased how close a city has to be to be viable for fort building

* Decreased fort value, especially for allied city-states

* Units no longer prioritise pillaging forts instead of not pillaging them at all
This commit is contained in:
Oskar Niesen 2024-01-28 03:07:46 -06:00 committed by GitHub
parent f93a3f462b
commit f8d97968ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 42 deletions

View File

@ -388,10 +388,10 @@ object UnitAutomation {
.filter { it.value.totalDistance < unit.currentMovement }.keys
.filter { unit.movement.canMoveTo(it) && UnitActionsPillage.canPillage(unit, it)
&& (it.canPillageTileImprovement()
|| (it.canPillageRoad() && it.getRoadOwner() != null && unit.civ.isAtWarWith(it.getRoadOwner()!!)))}
|| (it.canPillageRoad() && it.getRoadOwner() != null && unit.civ.isAtWarWith(it.getRoadOwner()!!))) }
if (tilesThatCanWalkToAndThenPillage.isEmpty()) return false
val tileToPillage = tilesThatCanWalkToAndThenPillage.maxByOrNull { it.getDefensiveBonus() }!!
val tileToPillage = tilesThatCanWalkToAndThenPillage.maxByOrNull { it.getDefensiveBonus(false) }!!
if (unit.getTile() != tileToPillage)
unit.movement.moveToTile(tileToPillage)

View File

@ -11,6 +11,7 @@ import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.BFS
import com.unciv.logic.map.HexMath
import com.unciv.logic.map.MapPathing
@ -29,6 +30,7 @@ import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques
import com.unciv.utils.Log
import com.unciv.utils.debug
import kotlin.math.abs
private object WorkerAutomationConst {
/** BFS max size is determined by the aerial distance of two cities to connect, padded with this */
@ -350,9 +352,7 @@ class WorkerAutomation(
&& tileHasWorkToDo(reachedTile, unit)
) {
debug("WorkerAutomation: ${unit.label()} -> start improving $reachedTile")
return reachedTile.startWorkingOnImprovement(
chooseImprovement(unit, reachedTile)!!, civInfo, unit
)
return reachedTile.startWorkingOnImprovement(tileRankings[reachedTile]!!.bestImprovement!!, civInfo, unit)
}
}
return
@ -726,6 +726,9 @@ class WorkerAutomation(
}
if (isImprovementProbablyAFort(improvement)) {
value += evaluateFortSurroundings(tile, improvement.hasUnique(UniqueType.TakesOverAdjacentTiles))
} else if (tile.getTileImprovement() != null && isImprovementProbablyAFort(tile.getTileImprovement()!!)) {
// Replace/build improvements on other tiles before this one
value /= 2
}
return value
}
@ -756,7 +759,8 @@ class WorkerAutomation(
//todo Should this not also check impassable and the fort improvement's terrainsCanBeBuiltOn/uniques?
if (tile.isCityCenter() // don't build fort in the city
|| !tile.isLand // don't build fort in the water
|| tile.hasViewableResource(civInfo) // don't build on resource tiles
|| (tile.hasViewableResource(civInfo)
&& tile.tileResource.resourceType != ResourceType.Bonus) // don't build on resource tiles
|| tile.containsGreatImprovement() // don't build on great improvements (including citadel)
) return false
@ -771,35 +775,38 @@ class WorkerAutomation(
* @return Yes the location is good for a Fort here
*/
private fun evaluateFortSurroundings(tile: Tile, isCitadel: Boolean): Float {
//todo for placement is disabled for now, it should be re-enabled in the future.
return -100f
//todo Is the Citadel code dead anyway? If not - why does the nearestTiles check not respect the param?
// build on our land only
if (tile.owningCity?.civ != civInfo &&
// except citadel which can be built near-by
(!isCitadel || tile.neighbors.all { it.getOwner() != civInfo }) ||
// except citadel which can be built near-by
(!isCitadel || tile.neighbors.all { it.getOwner() != civInfo }) ||
!isAcceptableTileForFort(tile)) return 0f
val enemyCivs = civInfo.getKnownCivs()
.filter { it != civInfo && it.cities.isNotEmpty() && civInfo.isAtWarWith(it) }
.filter { it != civInfo && it.cities.isNotEmpty() && (civInfo.isAtWarWith(it) || civInfo.getDiplomacyManager(it).isRelationshipLevelLE(RelationshipLevel.Enemy)) }
// no potential enemies
if (enemyCivs.none()) return 0f
var valueOfFort = 3f
var valueOfFort = 2f
if (civInfo.isCityState() && civInfo.getAllyCiv() != null) valueOfFort -= 1f // Allied city states probably don't need to build forts
if (tile.hasViewableResource(civInfo)) valueOfFort -= 1
// if this place is not perfect, let's see if there is a better one
val nearestTiles = tile.getTilesInDistance(2).filter { it.owningCity?.civ == civInfo }.toList()
val nearestTiles = tile.getTilesInDistance(1).filter { it.owningCity?.civ == civInfo }.toList()
for (closeTile in nearestTiles) {
// don't build forts too close to the cities
if (closeTile.isCityCenter()) valueOfFort -= 1f
if (closeTile.isCityCenter()) {
valueOfFort -= .5f
continue
}
// don't build forts too close to other forts
if (closeTile.improvement != null
&& isImprovementProbablyAFort(closeTile.getTileImprovement()!!)
|| (closeTile.improvementInProgress != null && isImprovementProbablyAFort(closeTile.improvementInProgress!!)))
valueOfFort -= .5f
// there is another better tile for the fort
|| (closeTile.improvementInProgress != null && isImprovementProbablyAFort(closeTile.improvementInProgress!!)))
valueOfFort -= 1f
// there is probably another better tile for the fort
if (!tile.isHill() && closeTile.isHill() &&
isAcceptableTileForFort(closeTile)) valueOfFort -= .5f
isAcceptableTileForFort(closeTile)) valueOfFort -= .2f
// We want to build forts more in choke points
if (tile.isImpassible()) valueOfFort += .2f
}
@ -807,38 +814,43 @@ class WorkerAutomation(
val threatMapping: (Civilization) -> Int = {
// the war is already a good nudge to build forts
(if (civInfo.isAtWarWith(it)) 5 else 0) +
// let's check also the force of the enemy
when (Automation.threatAssessment(civInfo, it)) {
ThreatLevel.VeryLow -> 1 // do not build forts
ThreatLevel.Low -> 6 // too close, let's build until it is late
ThreatLevel.Medium -> 10
ThreatLevel.High -> 15 // they are strong, let's built until they reach us
ThreatLevel.VeryHigh -> 20
}
// let's check also the force of the enemy
when (Automation.threatAssessment(civInfo, it)) {
ThreatLevel.VeryLow -> 1 // do not build forts
ThreatLevel.Low -> 6 // too close, let's build until it is late
ThreatLevel.Medium -> 10
ThreatLevel.High -> 15 // they are strong, let's built until they reach us
ThreatLevel.VeryHigh -> 20
}
}
val enemyCivsIsCloseEnough = enemyCivs.filter { NextTurnAutomation.getMinDistanceBetweenCities(
civInfo, it) <= threatMapping(it) }
// no threat, let's not build fort
// No threat, let's not build fort
if (enemyCivsIsCloseEnough.none()) return 0f
// make list of enemy cities as sources of threat
// Make a list of enemy cities as sources of threat
val enemyCities = mutableListOf<Tile>()
enemyCivsIsCloseEnough.forEach { enemyCities.addAll(it.cities.map { city -> city.getCenterTile() }) }
// find closest enemy city
// Find closest enemy city
val closestEnemyCity = enemyCities.minByOrNull { it.aerialDistanceTo(tile) }!!
val distanceToEnemy = tile.aerialDistanceTo(closestEnemyCity)
val distanceToEnemyCity = tile.aerialDistanceTo(closestEnemyCity)
// Find our closest city to defend from this enemy city
val closestCity = civInfo.cities.minByOrNull { it.getCenterTile().aerialDistanceTo(tile) }!!.getCenterTile()
val distanceToCity = tile.aerialDistanceTo(closestCity)
val distanceBetweenCities = closestEnemyCity.aerialDistanceTo(closestCity)
// Find the distance between the target enemy city to our closest city
val distanceOfEnemyCityToClosestCityOfUs = civInfo.cities.map { it.getCenterTile().aerialDistanceTo(closestEnemyCity) }.minBy { it }
// find closest our city to defend from this enemy city
val closestOurCity = civInfo.cities.minByOrNull { it.getCenterTile().aerialDistanceTo(tile) }!!.getCenterTile()
val distanceToOurCity = tile.aerialDistanceTo(closestOurCity)
// We don't want to defend city closest to this this tile if it is behind other cities
if (distanceBetweenCities >= distanceOfEnemyCityToClosestCityOfUs + 2) return 0f
val distanceBetweenCities = closestEnemyCity.aerialDistanceTo(closestOurCity)
// This location is not between a city and the enemy
if (distanceToEnemy >= distanceBetweenCities) return 0f
// This location is not between the city and the enemy
if (distanceToEnemyCity >= distanceBetweenCities
// Don't build in enemy city range
|| distanceToEnemyCity <= 2) return 0f
valueOfFort += 2 - Math.abs(distanceBetweenCities - 1 - distanceToEnemy)
// let's build fort on the front line, not behind the city
valueOfFort += 2 - abs(distanceBetweenCities - 1 - distanceToEnemyCity)
// +2 is a acceptable deviation from the straight line between cities
return valueOfFort.coerceAtLeast(0f)
}

View File

@ -561,7 +561,7 @@ open class Tile : IsPartOfGameInfoSerialization {
fun getTilesInDistanceRange(range: IntRange): Sequence<Tile> = tileMap.getTilesInDistanceRange(position, range)
fun getTilesAtDistance(distance: Int): Sequence<Tile> =tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float {
fun getDefensiveBonus(includeImprovementBonus: Boolean = true): Float {
var bonus = baseTerrainObject.defenceBonus
if (terrainFeatureObjects.isNotEmpty()) {
val otherTerrainBonus = terrainFeatureObjects.maxOf { it.defenceBonus }
@ -569,7 +569,7 @@ open class Tile : IsPartOfGameInfoSerialization {
}
if (naturalWonder != null) bonus += getNaturalWonder().defenceBonus
val tileImprovement = getUnpillagedTileImprovement()
if (tileImprovement != null) {
if (tileImprovement != null && includeImprovementBonus) {
for (unique in tileImprovement.getMatchingUniques(UniqueType.DefensiveBonus, StateForConditionals(tile = this)))
bonus += unique.params[0].toFloat() / 100
}