diff --git a/core/src/com/unciv/logic/automation/civilization/BarbarianAutomation.kt b/core/src/com/unciv/logic/automation/civilization/BarbarianAutomation.kt index af0b7aedd5..2f0cdbe9d2 100644 --- a/core/src/com/unciv/logic/automation/civilization/BarbarianAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/BarbarianAutomation.kt @@ -49,7 +49,7 @@ class BarbarianAutomation(val civInfo: Civilization) { private fun automateCombatUnit(unit: MapUnit) { // 1 - Try pillaging to restore health (barbs don't auto-heal) - if (unit.health < 50 && UnitAutomation.tryPillageImprovement(unit)) return + if (unit.health < 50 && UnitAutomation.tryPillageImprovement(unit, true) && unit.currentMovement == 0f) return // 2 - trying to upgrade if (UnitAutomation.tryUpgradeUnit(unit)) return @@ -60,7 +60,9 @@ class BarbarianAutomation(val civInfo: Civilization) { if (!unit.isCivilian() && BattleHelper.tryAttackNearbyEnemy(unit)) return // 4 - trying to pillage tile or route - if (UnitAutomation.tryPillageImprovement(unit)) return + while (UnitAutomation.tryPillageImprovement(unit)) { + if (unit.currentMovement == 0f) return + } // 6 - wander UnitAutomation.wander(unit) diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index 22b996b6ac..4c882d7886 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -213,13 +213,16 @@ object UnitAutomation { if (tryUpgradeUnit(unit)) return + if (unit.health < 50 && (trySwapRetreat(unit) || tryHealUnit(unit))) return // do nothing but heal + + // If there are no enemies nearby and we can heal here, wait until we are at full health + if (unit.health < 100 && canUnitHealInTurnsOnCurrentTile(unit,2, 4)) return + // Accompany settlers if (tryAccompanySettlerOrGreatPerson(unit)) return if (tryHeadTowardsOurSiegedCity(unit)) return - if (unit.health < 50 && (trySwapRetreat(unit) || tryHealUnit(unit))) return // do nothing but heal - // if a embarked melee unit can land and attack next turn, do not attack from water. if (BattleHelper.tryDisembarkUnitToAttackPosition(unit)) return @@ -314,10 +317,18 @@ object UnitAutomation { if (unit.baseUnit.isRanged() && unit.hasUnique(UniqueType.HealsEvenAfterAction)) return false // will heal anyway, and attacks don't hurt - if (tryPillageImprovement(unit)) return true + // Try pillage improvements until healed + while(tryPillageImprovement(unit, false)) { + // If we are fully healed and can still do things, lets keep on going by returning false + if (unit.currentMovement == 0f || unit.health == 100) return unit.currentMovement == 0f + } + val unitDistanceToTiles = unit.movement.getDistanceToTiles() if (unitDistanceToTiles.isEmpty()) return true // can't move, so... + // If the unit can heal on this tile in two turns, just heal here + if (canUnitHealInTurnsOnCurrentTile(unit,3,)) return true + val currentUnitTile = unit.getTile() val dangerousTiles = unit.civ.threatManager.getDangerousTiles(unit) @@ -366,6 +377,19 @@ object UnitAutomation { return true } + /** + * @return true if the tile is safe and the unit can heal to full within [turns] + */ + private fun canUnitHealInTurnsOnCurrentTile(unit: MapUnit, turns: Int, noEnemyDistance: Int = 3): Boolean { + if (unit.hasUnique(UniqueType.HealsEvenAfterAction)) return false // We can keep on moving + // Check if we are not in a safe city and there is an enemy nearby this isn't a good tile to heal on + if (!(unit.getTile().isCityCenter() && unit.getTile().getCity()!!.health > 50) + && unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(), noEnemyDistance) <= noEnemyDistance) return false + + val healthRequiredPerTurn = (100 - unit.health) / turns + return healthRequiredPerTurn <= unit.rankTileForHealing(unit.getTile()) + } + private fun getDangerousTiles(unit: MapUnit): HashSet { val nearbyRangedEnemyUnits = unit.currentTile.getTilesInDistance(3) .flatMap { tile -> tile.getUnits().filter { unit.civ.isAtWarWith(it.civ) } } @@ -383,14 +407,17 @@ object UnitAutomation { return (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet() } - fun tryPillageImprovement(unit: MapUnit): Boolean { + /** + * @return true if the unit was able to pillage a tile, false otherwise + */ + fun tryPillageImprovement(unit: MapUnit, onlyPillageToHeal: Boolean = false): Boolean { if (unit.isCivilian()) return false val unitDistanceToTiles = unit.movement.getDistanceToTiles() val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles .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()!!))) } + || (!onlyPillageToHeal && it.canPillageRoad() && it.getRoadOwner() != null && unit.civ.isAtWarWith(it.getRoadOwner()!!))) } if (tilesThatCanWalkToAndThenPillage.isEmpty()) return false val tileToPillage = tilesThatCanWalkToAndThenPillage.maxByOrNull { it.getDefensiveBonus(false) }!! @@ -400,7 +427,7 @@ object UnitAutomation { // We CANNOT use invokeUnitAction, since the default unit action contains a popup, which - when automated - // runs a UI action on a side thread leading to crash! UnitActionsPillage.getPillageAction(unit, unit.currentTile)?.action?.invoke() - return unit.currentMovement == 0f + return true } /** Move towards the closest attackable enemy of the [unit]. diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 4e021eae33..fc27442f24 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -22,6 +22,7 @@ import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.ui.components.UnitMovementMemoryType +import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsPillage import com.unciv.utils.debug import kotlin.math.max import kotlin.math.min @@ -39,7 +40,7 @@ object Battle { * Currently not used by UI, only by automation via [BattleHelper.tryAttackNearbyEnemy][com.unciv.logic.automation.unit.BattleHelper.tryAttackNearbyEnemy] */ fun moveAndAttack(attacker: ICombatant, attackableTile: AttackableTile) { - if (!movePreparingAttack(attacker, attackableTile)) return + if (!movePreparingAttack(attacker, attackableTile, true)) return attackOrNuke(attacker, attackableTile) } @@ -48,8 +49,9 @@ object Battle { * * This is a logic function, not UI, so e.g. sound needs to be handled after calling this. */ - fun movePreparingAttack(attacker: ICombatant, attackableTile: AttackableTile): Boolean { + fun movePreparingAttack(attacker: ICombatant, attackableTile: AttackableTile, tryHealPillage: Boolean = false): Boolean { if (attacker !is MapUnitCombatant) return true + val tilesMovedThrough = attacker.unit.movement.getDistanceToTiles().getPathToTile(attackableTile.tileToAttackFrom) attacker.unit.movement.moveToTile(attackableTile.tileToAttackFrom) /** * When calculating movement distance, we assume that a hidden tile is 1 movement point, @@ -74,6 +76,19 @@ object Battle { attacker.unit.action = UnitActionType.SetUp.value attacker.unit.useMovementPoints(1f) } + + if (tryHealPillage) { + // Now lets retroactively see if we can pillage any improvement on the path improvement to heal + // while still being able to attack + for (tileToPillage in tilesMovedThrough) { + if (attacker.unit.currentMovement <= 1f || attacker.unit.health > 90) break // We are done pillaging + + if (UnitActionsPillage.canPillage(attacker.unit, tileToPillage) + && tileToPillage.canPillageTileImprovement()) { + UnitActionsPillage.getPillageAction(attacker.unit, tileToPillage)?.action?.invoke() + } + } + } return (attacker.unit.currentMovement > 0f) }