mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-25 04:43:05 -04:00
Military unit healing improvement (#11195)
* Military units prioritize healing more * Military units try to pillage tiles when attacking * tryHeal() now tries to pillage multiple times if a unit have more movement * Barbarians now pillage more * Units stay healing on its tile if it can heal in two turns * Units will heal when no enemies are around in their territory * TryHealUnit returns false after pillaging to full health * Refactored canUnitHealInTurnsOnCurrentTile * Refactored movePreparingAttack pillaging
This commit is contained in:
parent
c8f9f38d96
commit
a5f1ba0401
@ -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)
|
||||
|
@ -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<Tile> {
|
||||
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].
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user