mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 22:06:05 -04:00
AI: Build melee naval units to defend coastal cities and move them there
This commit is contained in:
parent
654b9f80f2
commit
27a5bd0cc5
@ -2,13 +2,13 @@ package com.unciv.logic.automation
|
|||||||
|
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.city.CityFocus
|
import com.unciv.logic.city.CityFocus
|
||||||
import com.unciv.models.ruleset.INonPerpetualConstruction
|
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.map.BFS
|
import com.unciv.logic.map.BFS
|
||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
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.Building
|
import com.unciv.models.ruleset.Building
|
||||||
|
import com.unciv.models.ruleset.INonPerpetualConstruction
|
||||||
import com.unciv.models.ruleset.Victory
|
import com.unciv.models.ruleset.Victory
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.tile.TileImprovement
|
import com.unciv.models.ruleset.tile.TileImprovement
|
||||||
@ -141,12 +141,23 @@ object Automation {
|
|||||||
|
|
||||||
// if not coastal, removeShips == true so don't even consider ships
|
// if not coastal, removeShips == true so don't even consider ships
|
||||||
var removeShips = true
|
var removeShips = true
|
||||||
|
var isMissingNavalUnitsForCityDefence = false
|
||||||
|
|
||||||
|
fun isNavalMeleeUnit(unit: BaseUnit) = unit.isMelee() && unit.type.isWaterUnit()
|
||||||
if (city.isCoastal()) {
|
if (city.isCoastal()) {
|
||||||
// in the future this could be simplified by assigning every distinct non-lake body of
|
// in the future this could be simplified by assigning every distinct non-lake body of
|
||||||
// water their own ID like a continent ID
|
// water their own ID like a continent ID
|
||||||
val findWaterConnectedCitiesAndEnemies =
|
val findWaterConnectedCitiesAndEnemies =
|
||||||
BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() }
|
BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() }
|
||||||
findWaterConnectedCitiesAndEnemies.stepToEnd()
|
findWaterConnectedCitiesAndEnemies.stepToEnd()
|
||||||
|
|
||||||
|
val numberOfOurConnectedCities = findWaterConnectedCitiesAndEnemies.getReachedTiles()
|
||||||
|
.count { it.isCityCenter() && it.getOwner() == city.civ }
|
||||||
|
val numberOfOurNavalMeleeUnits = findWaterConnectedCitiesAndEnemies.getReachedTiles().asSequence()
|
||||||
|
.flatMap { it.getUnits() }
|
||||||
|
.count { isNavalMeleeUnit(it.baseUnit) }
|
||||||
|
isMissingNavalUnitsForCityDefence = numberOfOurConnectedCities > numberOfOurNavalMeleeUnits
|
||||||
|
|
||||||
removeShips = findWaterConnectedCitiesAndEnemies.getReachedTiles().none {
|
removeShips = findWaterConnectedCitiesAndEnemies.getReachedTiles().none {
|
||||||
(it.isCityCenter() && it.getOwner() != city.civ)
|
(it.isCityCenter() && it.getOwner() != city.civ)
|
||||||
|| (it.militaryUnit != null && it.militaryUnit!!.civ != city.civ)
|
|| (it.militaryUnit != null && it.militaryUnit!!.civ != city.civ)
|
||||||
@ -174,7 +185,13 @@ object Automation {
|
|||||||
chosenUnit = militaryUnits
|
chosenUnit = militaryUnits
|
||||||
.filter { it.isRanged() }
|
.filter { it.isRanged() }
|
||||||
.maxByOrNull { it.cost }!!
|
.maxByOrNull { it.cost }!!
|
||||||
} else { // randomize type of unit and take the most expensive of its kind
|
}
|
||||||
|
else if (isMissingNavalUnitsForCityDefence && militaryUnits.any { isNavalMeleeUnit(it) }){
|
||||||
|
chosenUnit = militaryUnits
|
||||||
|
.filter { isNavalMeleeUnit(it) }
|
||||||
|
.maxBy { it.cost }
|
||||||
|
}
|
||||||
|
else { // randomize type of unit and take the most expensive of its kind
|
||||||
val bestUnitsForType = hashMapOf<String, BaseUnit>()
|
val bestUnitsForType = hashMapOf<String, BaseUnit>()
|
||||||
for (unit in militaryUnits) {
|
for (unit in militaryUnits) {
|
||||||
if (bestUnitsForType[unit.unitType] == null || bestUnitsForType[unit.unitType]!!.cost < unit.cost) {
|
if (bestUnitsForType[unit.unitType] == null || bestUnitsForType[unit.unitType]!!.cost < unit.cost) {
|
||||||
|
@ -109,7 +109,6 @@ object UnitAutomation {
|
|||||||
&& unit.movement.canReach(tile) // expensive, evaluate last
|
&& unit.movement.canReach(tile) // expensive, evaluate last
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun wander(unit: MapUnit, stayInTerritory: Boolean = false, tilesToAvoid:Set<Tile> = setOf()) {
|
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
|
||||||
@ -205,7 +204,9 @@ object UnitAutomation {
|
|||||||
// Focus all units without a specific target on the enemy city closest to one of our cities
|
// Focus all units without a specific target on the enemy city closest to one of our cities
|
||||||
if (tryHeadTowardsEnemyCity(unit)) return
|
if (tryHeadTowardsEnemyCity(unit)) return
|
||||||
|
|
||||||
if (tryGarrisoningUnit(unit)) return
|
if (tryGarrisoningRangedLandUnit(unit)) return
|
||||||
|
|
||||||
|
if (tryStationingMeleeNavalUnit(unit)) return
|
||||||
|
|
||||||
if (unit.health < 80 && tryHealUnit(unit)) return
|
if (unit.health < 80 && tryHealUnit(unit)) return
|
||||||
|
|
||||||
@ -727,7 +728,7 @@ object UnitAutomation {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryGarrisoningUnit(unit: MapUnit): Boolean {
|
private fun tryGarrisoningRangedLandUnit(unit: MapUnit): Boolean {
|
||||||
if (unit.baseUnit.isMelee() || unit.baseUnit.isWaterUnit()) return false // don't garrison melee units, they're not that good at it
|
if (unit.baseUnit.isMelee() || unit.baseUnit.isWaterUnit()) return false // don't garrison melee units, they're not that good at it
|
||||||
val citiesWithoutGarrison = unit.civ.cities.filter {
|
val citiesWithoutGarrison = unit.civ.cities.filter {
|
||||||
val centerTile = it.getCenterTile()
|
val centerTile = it.getCenterTile()
|
||||||
@ -750,8 +751,10 @@ object UnitAutomation {
|
|||||||
} else {
|
} else {
|
||||||
if (unit.getTile().isCityCenter() &&
|
if (unit.getTile().isCityCenter() &&
|
||||||
isCityThatNeedsDefendingInWartime(unit.getTile().getCity()!!)) return true
|
isCityThatNeedsDefendingInWartime(unit.getTile().getCity()!!)) return true
|
||||||
citiesWithoutGarrison.asSequence()
|
val citiesWithoutGarrisonThatNeedDefending = citiesWithoutGarrison.asSequence()
|
||||||
.filter { isCityThatNeedsDefendingInWartime(it) }
|
.filter { isCityThatNeedsDefendingInWartime(it) }
|
||||||
|
if (citiesWithoutGarrisonThatNeedDefending.any()) citiesWithoutGarrisonThatNeedDefending
|
||||||
|
else citiesWithoutGarrison.asSequence()
|
||||||
}
|
}
|
||||||
|
|
||||||
val closestReachableCityNeedsDefending = citiesToTry
|
val closestReachableCityNeedsDefending = citiesToTry
|
||||||
@ -762,6 +765,35 @@ object UnitAutomation {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryStationingMeleeNavalUnit(unit: MapUnit): Boolean {
|
||||||
|
fun isMeleeNaval(mapUnit: MapUnit) = mapUnit.baseUnit.isMelee() && mapUnit.type.isWaterUnit()
|
||||||
|
|
||||||
|
if (!isMeleeNaval(unit)) return false
|
||||||
|
val closeCity = unit.getTile().getTilesInDistance(3)
|
||||||
|
.firstOrNull { it.isCityCenter() }
|
||||||
|
|
||||||
|
// We're the closest unit to this city, we should stay here :)
|
||||||
|
if (closeCity != null && closeCity.getTilesInDistance(3)
|
||||||
|
.flatMap { it.getUnits() }
|
||||||
|
.firstOrNull {isMeleeNaval(it)} == unit
|
||||||
|
&& unit.movement.canReach(closeCity)) {
|
||||||
|
unit.movement.headTowards(closeCity)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val citiesWithoutNavalDefence = unit.civ.cities.filter { it.isCoastal() }
|
||||||
|
.filter { it.getCenterTile().aerialDistanceTo(unit.getTile()) < 20 } // Not too far away
|
||||||
|
.filter { it.getCenterTile().getTilesInDistance(3)
|
||||||
|
.flatMap { it.getUnits() }
|
||||||
|
.none { isMeleeNaval(it) }}
|
||||||
|
|
||||||
|
val reachableCity = citiesWithoutNavalDefence.firstOrNull {
|
||||||
|
unit.movement.canReach(it.getCenterTile())
|
||||||
|
} ?: return false
|
||||||
|
unit.movement.headTowards(reachableCity.getCenterTile())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/** This is what a unit with the 'explore' action does.
|
/** This is what a unit with the 'explore' action does.
|
||||||
It also explores, but also has other functions, like healing if necessary. */
|
It also explores, but also has other functions, like healing if necessary. */
|
||||||
fun automatedExplore(unit: MapUnit) {
|
fun automatedExplore(unit: MapUnit) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user