AI civilian improvement: Don't freeze when enemy is near, keep working in tiles where he can't reach

This commit is contained in:
Yair Morgenstern
2023-05-22 14:24:05 +03:00
parent fc81d7dda3
commit edd09910a1
3 changed files with 34 additions and 26 deletions

View File

@ -126,12 +126,12 @@ object SpecificUnitAutomation {
.firstOrNull()?.action?.invoke()
}
fun automateSettlerActions(unit: MapUnit) {
fun automateSettlerActions(unit: MapUnit, tilesWhereWeWillBeCaptured: Set<Tile>) {
if (unit.civ.gameInfo.turns == 0) { // Special case, we want AI to settle in place on turn 1.
val foundCityAction = UnitActions.getFoundCityAction(unit, unit.getTile())
// Depending on era and difficulty we might start with more than one settler. In that case settle the one with the best location
val otherSettlers = unit.civ.units.getCivUnits().filter { it.currentMovement > 0 && it.baseUnit == unit.baseUnit }
if(foundCityAction?.action != null &&
if (foundCityAction?.action != null &&
otherSettlers.none {
CityLocationTileRanker.rankTileAsCityCenter(
it.getTile(), unit.civ
@ -145,14 +145,13 @@ object SpecificUnitAutomation {
}
}
if (unit.getTile().militaryUnit == null // Don't move until you're accompanied by a military unit
&& !unit.civ.isCityState() // ..unless you're a city state that was unable to settle its city on turn 1
&& unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting
if (unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting
// 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
val bestCityLocation: Tile? =
CityLocationTileRanker.getBestTilesToFoundCity(unit).firstOrNull {
if (it.first in tilesWhereWeWillBeCaptured) return@firstOrNull false
val pathSize = unit.movement.getShortestPath(it.first).size
return@firstOrNull pathSize in 1..3
}?.first
@ -169,7 +168,7 @@ object SpecificUnitAutomation {
if (frontierCity != null && getFrontierScore(frontierCity) > 0 && unit.movement.canReach(frontierCity.getCenterTile()))
unit.movement.headTowards(frontierCity.getCenterTile())
if (UnitAutomation.tryExplore(unit)) return // try to find new areas
UnitAutomation.wander(unit) // go around aimlessly
UnitAutomation.wander(unit, tilesToAvoid = tilesWhereWeWillBeCaptured) // go around aimlessly
return
}

View File

@ -110,10 +110,14 @@ object UnitAutomation {
}
@JvmStatic
fun wander(unit: MapUnit, stayInTerritory: Boolean = false) {
fun wander(unit: MapUnit, stayInTerritory: Boolean = false, tilesToAvoid:Set<Tile> = setOf()) {
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
val reachableTiles = unitDistanceToTiles
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
.filter {
it.key !in tilesToAvoid
&& unit.movement.canMoveTo(it.key)
&& unit.movement.canReach(it.key)
}
val reachableTilesMaxWalkingDistance = reachableTiles
.filter { it.value.totalDistance == unit.currentMovement
@ -234,11 +238,17 @@ object UnitAutomation {
unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key)
}
val tilesWhereWeWillBeCaptured = unit.currentTile.getTilesInDistance(5)
.mapNotNull { it.militaryUnit }
.filter { it.civ.isAtWarWith(unit.civ) }
.flatMap { it.movement.getReachableTilesInCurrentTurn() }
.toSet()
if (unit.hasUnique(UniqueType.FoundCity))
return SpecificUnitAutomation.automateSettlerActions(unit)
return SpecificUnitAutomation.automateSettlerActions(unit, tilesWhereWeWillBeCaptured)
if (unit.cache.hasUniqueToBuildImprovements)
return WorkerAutomation.automateWorkerAction(unit)
return WorkerAutomation.automateWorkerAction(unit, tilesWhereWeWillBeCaptured)
if (unit.hasUnique(UniqueType.MayFoundReligion)
&& unit.civ.religionManager.religionState < ReligionState.Religion
@ -762,14 +772,12 @@ object UnitAutomation {
private fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean {
// This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
// Cheaper than determining which enemies could attack us next turn
//todo - stay when we're stacked with a good military unit???
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { containsEnemyMilitaryUnit(unit, it) }
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()) {
if (unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter())
runAway(unit)
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()
&& unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) {
runAway(unit)
return true
}

View File

@ -108,8 +108,8 @@ class WorkerAutomation(
companion object {
/** Maps to instance [WorkerAutomation.automateWorkerAction] knowing only the MapUnit */
fun automateWorkerAction(unit: MapUnit) {
unit.civ.getWorkerAutomation().automateWorkerAction(unit)
fun automateWorkerAction(unit: MapUnit, tilesWhereWeWillBeCaptured: Set<Tile>) {
unit.civ.getWorkerAutomation().automateWorkerAction(unit, tilesWhereWeWillBeCaptured)
}
/** Convenience shortcut supports old calling syntax for [WorkerAutomation.getPriority] */
@ -131,9 +131,9 @@ class WorkerAutomation(
/**
* Automate one Worker - decide what to do and where, move, start or continue work.
*/
fun automateWorkerAction(unit: MapUnit) {
fun automateWorkerAction(unit: MapUnit, tilesWhereWeWillBeCaptured: Set<Tile>) {
val currentTile = unit.getTile()
val tileToWork = findTileToWork(unit)
val tileToWork = findTileToWork(unit, tilesWhereWeWillBeCaptured)
if (getPriority(tileToWork, civInfo) < 3) { // building roads is more important
if (tryConnectingCities(unit)) return
@ -201,7 +201,7 @@ class WorkerAutomation(
// Idle CS units should wander so they don't obstruct players so much
if (unit.civ.isCityState())
wander(unit, stayInTerritory = true)
wander(unit, stayInTerritory = true, tilesToAvoid = tilesWhereWeWillBeCaptured)
}
/**
@ -279,16 +279,17 @@ class WorkerAutomation(
* Looks for a worthwhile tile to improve
* @return The current tile if no tile to work was found
*/
private fun findTileToWork(unit: MapUnit): Tile {
private fun findTileToWork(unit: MapUnit, tilesToAvoid: Set<Tile>): Tile {
val currentTile = unit.getTile()
val workableTiles = currentTile.getTilesInDistance(4)
.filter {
(it.civilianUnit == null || it == currentTile)
&& (it.owningCity == null || it.getOwner()==civInfo)
&& getPriority(it) > 1
&& it.getTilesInDistance(2) // don't work in range of enemy cities
it !in tilesToAvoid
&& (it.civilianUnit == null || it == currentTile)
&& (it.owningCity == null || it.getOwner()==civInfo)
&& getPriority(it) > 1
&& it.getTilesInDistance(2) // don't work in range of enemy cities
.none { tile -> tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo) }
&& it.getTilesInDistance(3) // don't work in range of enemy units
&& it.getTilesInDistance(3) // don't work in range of enemy units
.none { tile -> tile.militaryUnit != null && tile.militaryUnit!!.civ.isAtWarWith(civInfo)}
}
.sortedByDescending { getPriority(it) }