diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 0a3de95314..67361a7613 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -2,13 +2,13 @@ package com.unciv.logic.automation import com.unciv.logic.city.City import com.unciv.logic.city.CityFocus -import com.unciv.models.ruleset.INonPerpetualConstruction import com.unciv.logic.civilization.Civilization import com.unciv.logic.map.BFS import com.unciv.logic.map.TileMap import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.INonPerpetualConstruction import com.unciv.models.ruleset.Victory import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement @@ -141,12 +141,23 @@ object Automation { // if not coastal, removeShips == true so don't even consider ships var removeShips = true + var isMissingNavalUnitsForCityDefence = false + + fun isNavalMeleeUnit(unit: BaseUnit) = unit.isMelee() && unit.type.isWaterUnit() if (city.isCoastal()) { // in the future this could be simplified by assigning every distinct non-lake body of // water their own ID like a continent ID val findWaterConnectedCitiesAndEnemies = BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() } 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 { (it.isCityCenter() && it.getOwner() != city.civ) || (it.militaryUnit != null && it.militaryUnit!!.civ != city.civ) @@ -174,7 +185,13 @@ object Automation { chosenUnit = militaryUnits .filter { it.isRanged() } .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() for (unit in militaryUnits) { if (bestUnitsForType[unit.unitType] == null || bestUnitsForType[unit.unitType]!!.cost < unit.cost) { diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index 74e37ebba1..1ee1c07480 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -109,7 +109,6 @@ object UnitAutomation { && unit.movement.canReach(tile) // expensive, evaluate last } - @JvmStatic fun wander(unit: MapUnit, stayInTerritory: Boolean = false, tilesToAvoid:Set = setOf()) { val unitDistanceToTiles = unit.movement.getDistanceToTiles() 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 if (tryHeadTowardsEnemyCity(unit)) return - if (tryGarrisoningUnit(unit)) return + if (tryGarrisoningRangedLandUnit(unit)) return + + if (tryStationingMeleeNavalUnit(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 val citiesWithoutGarrison = unit.civ.cities.filter { val centerTile = it.getCenterTile() @@ -750,8 +751,10 @@ object UnitAutomation { } else { if (unit.getTile().isCityCenter() && isCityThatNeedsDefendingInWartime(unit.getTile().getCity()!!)) return true - citiesWithoutGarrison.asSequence() + val citiesWithoutGarrisonThatNeedDefending = citiesWithoutGarrison.asSequence() .filter { isCityThatNeedsDefendingInWartime(it) } + if (citiesWithoutGarrisonThatNeedDefending.any()) citiesWithoutGarrisonThatNeedDefending + else citiesWithoutGarrison.asSequence() } val closestReachableCityNeedsDefending = citiesToTry @@ -762,6 +765,35 @@ object UnitAutomation { 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. It also explores, but also has other functions, like healing if necessary. */ fun automatedExplore(unit: MapUnit) {