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() .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. 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()) 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 // 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 } val otherSettlers = unit.civ.units.getCivUnits().filter { it.currentMovement > 0 && it.baseUnit == unit.baseUnit }
if(foundCityAction?.action != null && if (foundCityAction?.action != null &&
otherSettlers.none { otherSettlers.none {
CityLocationTileRanker.rankTileAsCityCenter( CityLocationTileRanker.rankTileAsCityCenter(
it.getTile(), unit.civ 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 if (unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting
&& !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
// 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 bestCityLocation: Tile? = val bestCityLocation: Tile? =
CityLocationTileRanker.getBestTilesToFoundCity(unit).firstOrNull { CityLocationTileRanker.getBestTilesToFoundCity(unit).firstOrNull {
if (it.first in tilesWhereWeWillBeCaptured) return@firstOrNull false
val pathSize = unit.movement.getShortestPath(it.first).size val pathSize = unit.movement.getShortestPath(it.first).size
return@firstOrNull pathSize in 1..3 return@firstOrNull pathSize in 1..3
}?.first }?.first
@ -169,7 +168,7 @@ object SpecificUnitAutomation {
if (frontierCity != null && getFrontierScore(frontierCity) > 0 && unit.movement.canReach(frontierCity.getCenterTile())) if (frontierCity != null && getFrontierScore(frontierCity) > 0 && unit.movement.canReach(frontierCity.getCenterTile()))
unit.movement.headTowards(frontierCity.getCenterTile()) unit.movement.headTowards(frontierCity.getCenterTile())
if (UnitAutomation.tryExplore(unit)) return // try to find new areas 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 return
} }

View File

@ -110,10 +110,14 @@ object UnitAutomation {
} }
@JvmStatic @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 unitDistanceToTiles = unit.movement.getDistanceToTiles()
val reachableTiles = unitDistanceToTiles 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 val reachableTilesMaxWalkingDistance = reachableTiles
.filter { it.value.totalDistance == unit.currentMovement .filter { it.value.totalDistance == unit.currentMovement
@ -234,11 +238,17 @@ object UnitAutomation {
unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key) 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)) if (unit.hasUnique(UniqueType.FoundCity))
return SpecificUnitAutomation.automateSettlerActions(unit) return SpecificUnitAutomation.automateSettlerActions(unit, tilesWhereWeWillBeCaptured)
if (unit.cache.hasUniqueToBuildImprovements) if (unit.cache.hasUniqueToBuildImprovements)
return WorkerAutomation.automateWorkerAction(unit) return WorkerAutomation.automateWorkerAction(unit, tilesWhereWeWillBeCaptured)
if (unit.hasUnique(UniqueType.MayFoundReligion) if (unit.hasUnique(UniqueType.MayFoundReligion)
&& unit.civ.religionManager.religionState < ReligionState.Religion && unit.civ.religionManager.religionState < ReligionState.Religion
@ -762,14 +772,12 @@ object UnitAutomation {
private fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean { private fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean {
// This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy // 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 // 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 val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { containsEnemyMilitaryUnit(unit, it) } .filter { containsEnemyMilitaryUnit(unit, it) }
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()) { if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()
if (unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) && unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) {
runAway(unit) runAway(unit)
return true return true
} }

View File

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