mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-28 14:24:43 -04:00
Refactor: Move checking targets from automation to logic (#9945)
* Move checking targets from automation to logic * Ending newline, move attackable tile * move getBombardableTiles for similar reasons * fix package name * remove import
This commit is contained in:
parent
7cb39dcecf
commit
08a04d3575
@ -1,21 +1,18 @@
|
|||||||
package com.unciv.logic.automation.unit
|
package com.unciv.logic.automation.unit
|
||||||
|
|
||||||
import com.unciv.Constants
|
import com.unciv.logic.battle.AttackableTile
|
||||||
import com.unciv.logic.battle.Battle
|
import com.unciv.logic.battle.Battle
|
||||||
import com.unciv.logic.battle.BattleDamage
|
import com.unciv.logic.battle.BattleDamage
|
||||||
import com.unciv.logic.battle.CityCombatant
|
|
||||||
import com.unciv.logic.battle.ICombatant
|
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.battle.TargetHelper
|
||||||
import com.unciv.logic.map.mapunit.MapUnit
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
import com.unciv.logic.map.mapunit.PathsToTilesWithinTurn
|
|
||||||
import com.unciv.logic.map.tile.Tile
|
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
|
|
||||||
object BattleHelper {
|
object BattleHelper {
|
||||||
|
|
||||||
fun tryAttackNearbyEnemy(unit: MapUnit, stayOnTile: Boolean = false): Boolean {
|
fun tryAttackNearbyEnemy(unit: MapUnit, stayOnTile: Boolean = false): Boolean {
|
||||||
if (unit.hasUnique(UniqueType.CannotAttack)) return false
|
if (unit.hasUnique(UniqueType.CannotAttack)) return false
|
||||||
val attackableEnemies = getAttackableEnemies(unit, unit.movement.getDistanceToTiles(), stayOnTile=stayOnTile)
|
val attackableEnemies = TargetHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles(), stayOnTile=stayOnTile)
|
||||||
// Only take enemies we can fight without dying
|
// Only take enemies we can fight without dying
|
||||||
.filter {
|
.filter {
|
||||||
BattleDamage.calculateDamageToAttacker(
|
BattleDamage.calculateDamageToAttacker(
|
||||||
@ -32,131 +29,11 @@ object BattleHelper {
|
|||||||
return unit.currentMovement == 0f
|
return unit.currentMovement == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAttackableEnemies(
|
|
||||||
unit: MapUnit,
|
|
||||||
unitDistanceToTiles: PathsToTilesWithinTurn,
|
|
||||||
tilesToCheck: List<Tile>? = null,
|
|
||||||
stayOnTile: Boolean = false
|
|
||||||
): ArrayList<AttackableTile> {
|
|
||||||
val rangeOfAttack = unit.getRange()
|
|
||||||
val attackableTiles = ArrayList<AttackableTile>()
|
|
||||||
|
|
||||||
val unitMustBeSetUp = unit.hasUnique(UniqueType.MustSetUp)
|
|
||||||
val tilesToAttackFrom = if (stayOnTile || unit.baseUnit.movesLikeAirUnits())
|
|
||||||
sequenceOf(Pair(unit.currentTile, unit.currentMovement))
|
|
||||||
else
|
|
||||||
unitDistanceToTiles.asSequence()
|
|
||||||
.map { (tile, distance) ->
|
|
||||||
val movementPointsToExpendAfterMovement = if (unitMustBeSetUp) 1 else 0
|
|
||||||
val movementPointsToExpendHere =
|
|
||||||
if (unitMustBeSetUp && !unit.isSetUpForSiege()) 1 else 0
|
|
||||||
val movementPointsToExpendBeforeAttack =
|
|
||||||
if (tile == unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement
|
|
||||||
val movementLeft =
|
|
||||||
unit.currentMovement - distance.totalDistance - movementPointsToExpendBeforeAttack
|
|
||||||
Pair(tile, movementLeft)
|
|
||||||
}
|
|
||||||
// still got leftover movement points after all that, to attack
|
|
||||||
.filter { it.second > Constants.minimumMovementEpsilon }
|
|
||||||
.filter {
|
|
||||||
it.first == unit.getTile() || unit.movement.canMoveTo(it.first)
|
|
||||||
}
|
|
||||||
|
|
||||||
val tilesWithEnemies: HashSet<Tile> = HashSet()
|
|
||||||
val tilesWithoutEnemies: HashSet<Tile> = HashSet()
|
|
||||||
for ((reachableTile, movementLeft) in tilesToAttackFrom) { // tiles we'll still have energy after we reach there
|
|
||||||
val tilesInAttackRange =
|
|
||||||
if (unit.hasUnique(UniqueType.IndirectFire) || unit.baseUnit.movesLikeAirUnits())
|
|
||||||
reachableTile.getTilesInDistance(rangeOfAttack)
|
|
||||||
else reachableTile.tileMap.getViewableTiles(reachableTile.position, rangeOfAttack, true).asSequence()
|
|
||||||
|
|
||||||
for (tile in tilesInAttackRange) {
|
|
||||||
// Since military units can technically enter tiles with enemy civilians,
|
|
||||||
// some try to move to to the tile and then attack the unit it contains, which is silly
|
|
||||||
if (tile == reachableTile) continue
|
|
||||||
if (tile in tilesWithEnemies) attackableTiles += AttackableTile(
|
|
||||||
reachableTile,
|
|
||||||
tile,
|
|
||||||
movementLeft,
|
|
||||||
Battle.getMapCombatantOfTile(tile)
|
|
||||||
)
|
|
||||||
else if (tile in tilesWithoutEnemies) continue // avoid checking the same empty tile multiple times
|
|
||||||
else if (tileContainsAttackableEnemy(unit, tile, tilesToCheck)) {
|
|
||||||
tilesWithEnemies += tile
|
|
||||||
attackableTiles += AttackableTile(
|
|
||||||
reachableTile, tile, movementLeft,
|
|
||||||
Battle.getMapCombatantOfTile(tile)
|
|
||||||
)
|
|
||||||
} else if (unit.isPreparingAirSweep()) {
|
|
||||||
tilesWithEnemies += tile
|
|
||||||
attackableTiles += AttackableTile(
|
|
||||||
reachableTile, tile, movementLeft,
|
|
||||||
Battle.getMapCombatantOfTile(tile)
|
|
||||||
)
|
|
||||||
} else tilesWithoutEnemies += tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attackableTiles
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tileContainsAttackableEnemy(unit: MapUnit, tile: Tile, tilesToCheck: List<Tile>?): Boolean {
|
|
||||||
if (!containsAttackableEnemy(tile, MapUnitCombatant(unit))) return false
|
|
||||||
if (tile !in (tilesToCheck ?: unit.civ.viewableTiles)) return false
|
|
||||||
val mapCombatant = Battle.getMapCombatantOfTile(tile)
|
|
||||||
|
|
||||||
return (!unit.baseUnit.isMelee() || mapCombatant !is MapUnitCombatant || !mapCombatant.unit.isCivilian() || unit.movement.canPassThrough(tile))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun containsAttackableEnemy(tile: Tile, combatant: ICombatant): Boolean {
|
|
||||||
if (combatant is MapUnitCombatant && combatant.unit.isEmbarked() && !combatant.hasUnique(UniqueType.AttackOnSea)) {
|
|
||||||
// Can't attack water units while embarked, only land
|
|
||||||
if (tile.isWater || combatant.isRanged())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val tileCombatant = Battle.getMapCombatantOfTile(tile) ?: return false
|
|
||||||
if (tileCombatant.getCivInfo() == combatant.getCivInfo()) return false
|
|
||||||
// If the user automates units, one may capture the city before the user had a chance to decide what to do with it,
|
|
||||||
// and then the next unit should not attack that city
|
|
||||||
if (tileCombatant is CityCombatant && tileCombatant.city.hasJustBeenConquered) return false
|
|
||||||
if (!combatant.getCivInfo().isAtWarWith(tileCombatant.getCivInfo())) return false
|
|
||||||
|
|
||||||
if (combatant is MapUnitCombatant && combatant.isLandUnit() && combatant.isMelee() && tile.isWater &&
|
|
||||||
!combatant.getCivInfo().tech.unitsCanEmbark && !combatant.unit.cache.canMoveOnWater
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (combatant is MapUnitCombatant && combatant.hasUnique(UniqueType.CannotAttack))
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (combatant is MapUnitCombatant &&
|
|
||||||
combatant.unit.getMatchingUniques(UniqueType.CanOnlyAttackUnits).run {
|
|
||||||
any() && none { tileCombatant.matchesCategory(it.params[0]) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (combatant is MapUnitCombatant &&
|
|
||||||
combatant.unit.getMatchingUniques(UniqueType.CanOnlyAttackTiles).run {
|
|
||||||
any() && none { tile.matchesFilter(it.params[0]) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
|
|
||||||
// Only units with the right unique can view submarines (or other invisible units) from more then one tile away.
|
|
||||||
// Garrisoned invisible units can be attacked by anyone, as else the city will be in invincible.
|
|
||||||
if (tileCombatant.isInvisible(combatant.getCivInfo()) && !tile.isCityCenter()) {
|
|
||||||
return combatant is MapUnitCombatant
|
|
||||||
&& combatant.getCivInfo().viewableInvisibleUnitsTiles.map { it.position }.contains(tile.position)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun tryDisembarkUnitToAttackPosition(unit: MapUnit): Boolean {
|
fun tryDisembarkUnitToAttackPosition(unit: MapUnit): Boolean {
|
||||||
if (!unit.baseUnit.isMelee() || !unit.baseUnit.isLandUnit() || !unit.isEmbarked()) return false
|
if (!unit.baseUnit.isMelee() || !unit.baseUnit.isLandUnit() || !unit.isEmbarked()) return false
|
||||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||||
|
|
||||||
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
|
val attackableEnemiesNextTurn = TargetHelper.getAttackableEnemies(unit, unitDistanceToTiles)
|
||||||
// Only take enemies we can fight without dying
|
// Only take enemies we can fight without dying
|
||||||
.filter {
|
.filter {
|
||||||
BattleDamage.calculateDamageToAttacker(
|
BattleDamage.calculateDamageToAttacker(
|
||||||
|
@ -5,6 +5,7 @@ import com.unciv.logic.automation.Automation
|
|||||||
import com.unciv.logic.battle.Battle
|
import com.unciv.logic.battle.Battle
|
||||||
import com.unciv.logic.battle.GreatGeneralImplementation
|
import com.unciv.logic.battle.GreatGeneralImplementation
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.battle.TargetHelper
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||||
import com.unciv.logic.map.mapunit.MapUnit
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
@ -480,7 +481,7 @@ object SpecificUnitAutomation {
|
|||||||
.filter { destinationCity ->
|
.filter { destinationCity ->
|
||||||
destinationCity != airUnit.currentTile
|
destinationCity != airUnit.currentTile
|
||||||
&& destinationCity.getTilesInDistance(airUnit.getRange())
|
&& destinationCity.getTilesInDistance(airUnit.getRange())
|
||||||
.any { BattleHelper.containsAttackableEnemy(it, MapUnitCombatant(airUnit)) }
|
.any { TargetHelper.containsAttackableEnemy(it, MapUnitCombatant(airUnit)) }
|
||||||
}
|
}
|
||||||
if (citiesThatCanAttackFrom.isEmpty()) return
|
if (citiesThatCanAttackFrom.isEmpty()) return
|
||||||
|
|
||||||
@ -547,7 +548,7 @@ object SpecificUnitAutomation {
|
|||||||
if (city.getTilesInDistance(unit.getRange())
|
if (city.getTilesInDistance(unit.getRange())
|
||||||
.any {
|
.any {
|
||||||
it.isVisible(unit.civ) &&
|
it.isVisible(unit.civ) &&
|
||||||
BattleHelper.containsAttackableEnemy(
|
TargetHelper.containsAttackableEnemy(
|
||||||
it,
|
it,
|
||||||
MapUnitCombatant(unit)
|
MapUnitCombatant(unit)
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,7 @@ import com.unciv.logic.battle.BattleDamage
|
|||||||
import com.unciv.logic.battle.CityCombatant
|
import com.unciv.logic.battle.CityCombatant
|
||||||
import com.unciv.logic.battle.ICombatant
|
import com.unciv.logic.battle.ICombatant
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.battle.TargetHelper
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.civilization.NotificationCategory
|
import com.unciv.logic.civilization.NotificationCategory
|
||||||
@ -467,11 +468,6 @@ object UnitAutomation {
|
|||||||
return unit.currentMovement == 0f
|
return unit.currentMovement == 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a list of visible tiles which have something attackable */
|
|
||||||
fun getBombardableTiles(city: City): Sequence<Tile> =
|
|
||||||
city.getCenterTile().getTilesInDistance(city.range)
|
|
||||||
.filter { it.isVisible(city.civ) && BattleHelper.containsAttackableEnemy(it, CityCombatant(city)) }
|
|
||||||
|
|
||||||
/** Move towards the closest attackable enemy of the [unit].
|
/** Move towards the closest attackable enemy of the [unit].
|
||||||
*
|
*
|
||||||
* Limited by [CLOSE_ENEMY_TURNS_AWAY_LIMIT] and [CLOSE_ENEMY_TILES_AWAY_LIMIT].
|
* Limited by [CLOSE_ENEMY_TURNS_AWAY_LIMIT] and [CLOSE_ENEMY_TILES_AWAY_LIMIT].
|
||||||
@ -482,7 +478,7 @@ object UnitAutomation {
|
|||||||
unit.getTile().position,
|
unit.getTile().position,
|
||||||
unit.getMaxMovement() * CLOSE_ENEMY_TURNS_AWAY_LIMIT
|
unit.getMaxMovement() * CLOSE_ENEMY_TURNS_AWAY_LIMIT
|
||||||
)
|
)
|
||||||
var closeEnemies = BattleHelper.getAttackableEnemies(
|
var closeEnemies = TargetHelper.getAttackableEnemies(
|
||||||
unit,
|
unit,
|
||||||
unitDistanceToTiles,
|
unitDistanceToTiles,
|
||||||
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
|
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
|
||||||
@ -674,7 +670,7 @@ object UnitAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun chooseBombardTarget(city: City): ICombatant? {
|
private fun chooseBombardTarget(city: City): ICombatant? {
|
||||||
var targets = getBombardableTiles(city).map { Battle.getMapCombatantOfTile(it)!! }
|
var targets = TargetHelper.getBombardableTiles(city).map { Battle.getMapCombatantOfTile(it)!! }
|
||||||
if (targets.none()) return null
|
if (targets.none()) return null
|
||||||
|
|
||||||
val siegeUnits = targets
|
val siegeUnits = targets
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.unciv.logic.automation.unit
|
package com.unciv.logic.battle
|
||||||
|
|
||||||
import com.unciv.logic.battle.ICombatant
|
import com.unciv.logic.battle.ICombatant
|
||||||
import com.unciv.logic.map.tile.Tile
|
import com.unciv.logic.map.tile.Tile
|
@ -4,7 +4,6 @@ import com.badlogic.gdx.math.Vector2
|
|||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
import com.unciv.logic.automation.civilization.NextTurnAutomation
|
||||||
import com.unciv.logic.automation.unit.AttackableTile
|
|
||||||
import com.unciv.logic.automation.unit.SpecificUnitAutomation
|
import com.unciv.logic.automation.unit.SpecificUnitAutomation
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.civilization.AlertType
|
import com.unciv.logic.civilization.AlertType
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.unciv.logic.battle
|
package com.unciv.logic.battle
|
||||||
|
|
||||||
import com.unciv.logic.automation.unit.BattleHelper
|
|
||||||
import com.unciv.logic.automation.unit.SpecificUnitAutomation
|
import com.unciv.logic.automation.unit.SpecificUnitAutomation
|
||||||
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
|
||||||
@ -95,7 +94,7 @@ object GreatGeneralImplementation {
|
|||||||
unitTile.getTilesInDistance(unitBonusRadius).sumOf { auraTile ->
|
unitTile.getTilesInDistance(unitBonusRadius).sumOf { auraTile ->
|
||||||
val militaryUnit = auraTile.militaryUnit
|
val militaryUnit = auraTile.militaryUnit
|
||||||
if (militaryUnit == null || militaryUnit.civ != general.civ || militaryUnit.isEmbarked()) 0
|
if (militaryUnit == null || militaryUnit.civ != general.civ || militaryUnit.isEmbarked()) 0
|
||||||
else if (BattleHelper.getAttackableEnemies(militaryUnit, militaryUnit.movement.getDistanceToTiles()).isEmpty()) 0
|
else if (TargetHelper.getAttackableEnemies(militaryUnit, militaryUnit.movement.getDistanceToTiles()).isEmpty()) 0
|
||||||
else generalBonusData.firstOrNull {
|
else generalBonusData.firstOrNull {
|
||||||
// "Military" as commented above only a small optimization
|
// "Military" as commented above only a small optimization
|
||||||
auraTile.aerialDistanceTo(unitTile) <= it.radius
|
auraTile.aerialDistanceTo(unitTile) <= it.radius
|
||||||
|
136
core/src/com/unciv/logic/battle/TargetHelper.kt
Normal file
136
core/src/com/unciv/logic/battle/TargetHelper.kt
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package com.unciv.logic.battle
|
||||||
|
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.logic.city.City
|
||||||
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
|
import com.unciv.logic.map.mapunit.PathsToTilesWithinTurn
|
||||||
|
import com.unciv.logic.map.tile.Tile
|
||||||
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
|
|
||||||
|
object TargetHelper {
|
||||||
|
fun getAttackableEnemies(
|
||||||
|
unit: MapUnit,
|
||||||
|
unitDistanceToTiles: PathsToTilesWithinTurn,
|
||||||
|
tilesToCheck: List<Tile>? = null,
|
||||||
|
stayOnTile: Boolean = false
|
||||||
|
): ArrayList<AttackableTile> {
|
||||||
|
val rangeOfAttack = unit.getRange()
|
||||||
|
val attackableTiles = ArrayList<AttackableTile>()
|
||||||
|
|
||||||
|
val unitMustBeSetUp = unit.hasUnique(UniqueType.MustSetUp)
|
||||||
|
val tilesToAttackFrom = if (stayOnTile || unit.baseUnit.movesLikeAirUnits())
|
||||||
|
sequenceOf(Pair(unit.currentTile, unit.currentMovement))
|
||||||
|
else
|
||||||
|
unitDistanceToTiles.asSequence()
|
||||||
|
.map { (tile, distance) ->
|
||||||
|
val movementPointsToExpendAfterMovement = if (unitMustBeSetUp) 1 else 0
|
||||||
|
val movementPointsToExpendHere =
|
||||||
|
if (unitMustBeSetUp && !unit.isSetUpForSiege()) 1 else 0
|
||||||
|
val movementPointsToExpendBeforeAttack =
|
||||||
|
if (tile == unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement
|
||||||
|
val movementLeft =
|
||||||
|
unit.currentMovement - distance.totalDistance - movementPointsToExpendBeforeAttack
|
||||||
|
Pair(tile, movementLeft)
|
||||||
|
}
|
||||||
|
// still got leftover movement points after all that, to attack
|
||||||
|
.filter { it.second > Constants.minimumMovementEpsilon }
|
||||||
|
.filter {
|
||||||
|
it.first == unit.getTile() || unit.movement.canMoveTo(it.first)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tilesWithEnemies: HashSet<Tile> = HashSet()
|
||||||
|
val tilesWithoutEnemies: HashSet<Tile> = HashSet()
|
||||||
|
for ((reachableTile, movementLeft) in tilesToAttackFrom) { // tiles we'll still have energy after we reach there
|
||||||
|
val tilesInAttackRange =
|
||||||
|
if (unit.hasUnique(UniqueType.IndirectFire) || unit.baseUnit.movesLikeAirUnits())
|
||||||
|
reachableTile.getTilesInDistance(rangeOfAttack)
|
||||||
|
else reachableTile.tileMap.getViewableTiles(reachableTile.position, rangeOfAttack, true).asSequence()
|
||||||
|
|
||||||
|
for (tile in tilesInAttackRange) {
|
||||||
|
// Since military units can technically enter tiles with enemy civilians,
|
||||||
|
// some try to move to to the tile and then attack the unit it contains, which is silly
|
||||||
|
if (tile == reachableTile) continue
|
||||||
|
if (tile in tilesWithEnemies) attackableTiles += AttackableTile(
|
||||||
|
reachableTile,
|
||||||
|
tile,
|
||||||
|
movementLeft,
|
||||||
|
Battle.getMapCombatantOfTile(tile)
|
||||||
|
)
|
||||||
|
else if (tile in tilesWithoutEnemies) continue // avoid checking the same empty tile multiple times
|
||||||
|
else if (tileContainsAttackableEnemy(unit, tile, tilesToCheck)) {
|
||||||
|
tilesWithEnemies += tile
|
||||||
|
attackableTiles += AttackableTile(
|
||||||
|
reachableTile, tile, movementLeft,
|
||||||
|
Battle.getMapCombatantOfTile(tile)
|
||||||
|
)
|
||||||
|
} else if (unit.isPreparingAirSweep()) {
|
||||||
|
tilesWithEnemies += tile
|
||||||
|
attackableTiles += AttackableTile(
|
||||||
|
reachableTile, tile, movementLeft,
|
||||||
|
Battle.getMapCombatantOfTile(tile)
|
||||||
|
)
|
||||||
|
} else tilesWithoutEnemies += tile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attackableTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun tileContainsAttackableEnemy(unit: MapUnit, tile: Tile, tilesToCheck: List<Tile>?): Boolean {
|
||||||
|
if (!containsAttackableEnemy(tile, MapUnitCombatant(unit))) return false
|
||||||
|
if (tile !in (tilesToCheck ?: unit.civ.viewableTiles)) return false
|
||||||
|
val mapCombatant = Battle.getMapCombatantOfTile(tile)
|
||||||
|
|
||||||
|
return (!unit.baseUnit.isMelee() || mapCombatant !is MapUnitCombatant || !mapCombatant.unit.isCivilian() || unit.movement.canPassThrough(tile))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun containsAttackableEnemy(tile: Tile, combatant: ICombatant): Boolean {
|
||||||
|
if (combatant is MapUnitCombatant && combatant.unit.isEmbarked() && !combatant.hasUnique(UniqueType.AttackOnSea)) {
|
||||||
|
// Can't attack water units while embarked, only land
|
||||||
|
if (tile.isWater || combatant.isRanged())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val tileCombatant = Battle.getMapCombatantOfTile(tile) ?: return false
|
||||||
|
if (tileCombatant.getCivInfo() == combatant.getCivInfo()) return false
|
||||||
|
// If the user automates units, one may capture the city before the user had a chance to decide what to do with it,
|
||||||
|
// and then the next unit should not attack that city
|
||||||
|
if (tileCombatant is CityCombatant && tileCombatant.city.hasJustBeenConquered) return false
|
||||||
|
if (!combatant.getCivInfo().isAtWarWith(tileCombatant.getCivInfo())) return false
|
||||||
|
|
||||||
|
if (combatant is MapUnitCombatant && combatant.isLandUnit() && combatant.isMelee() && tile.isWater &&
|
||||||
|
!combatant.getCivInfo().tech.unitsCanEmbark && !combatant.unit.cache.canMoveOnWater
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (combatant is MapUnitCombatant && combatant.hasUnique(UniqueType.CannotAttack))
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (combatant is MapUnitCombatant &&
|
||||||
|
combatant.unit.getMatchingUniques(UniqueType.CanOnlyAttackUnits).run {
|
||||||
|
any() && none { tileCombatant.matchesCategory(it.params[0]) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (combatant is MapUnitCombatant &&
|
||||||
|
combatant.unit.getMatchingUniques(UniqueType.CanOnlyAttackTiles).run {
|
||||||
|
any() && none { tile.matchesFilter(it.params[0]) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
|
||||||
|
// Only units with the right unique can view submarines (or other invisible units) from more then one tile away.
|
||||||
|
// Garrisoned invisible units can be attacked by anyone, as else the city will be in invincible.
|
||||||
|
if (tileCombatant.isInvisible(combatant.getCivInfo()) && !tile.isCityCenter()) {
|
||||||
|
return combatant is MapUnitCombatant
|
||||||
|
&& combatant.getCivInfo().viewableInvisibleUnitsTiles.map { it.position }.contains(tile.position)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get a list of visible tiles which have something attackable */
|
||||||
|
fun getBombardableTiles(city: City): Sequence<Tile> =
|
||||||
|
city.getCenterTile().getTilesInDistance(city.range)
|
||||||
|
.filter { it.isVisible(city.civ) && containsAttackableEnemy(it, CityCombatant(city)) }
|
||||||
|
|
||||||
|
}
|
@ -15,12 +15,11 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
|
|||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.automation.unit.AttackableTile
|
|
||||||
import com.unciv.logic.automation.unit.BattleHelper
|
|
||||||
import com.unciv.logic.automation.unit.CityLocationTileRanker
|
import com.unciv.logic.automation.unit.CityLocationTileRanker
|
||||||
import com.unciv.logic.automation.unit.UnitAutomation
|
import com.unciv.logic.battle.AttackableTile
|
||||||
import com.unciv.logic.battle.Battle
|
import com.unciv.logic.battle.Battle
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.battle.TargetHelper
|
||||||
import com.unciv.logic.city.City
|
import com.unciv.logic.city.City
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
@ -224,7 +223,7 @@ class WorldMapHolder(
|
|||||||
/** If we are in unit-swapping mode and didn't find a swap partner, we don't want to move or attack */
|
/** If we are in unit-swapping mode and didn't find a swap partner, we don't want to move or attack */
|
||||||
} else {
|
} else {
|
||||||
// This seems inefficient as the tileToAttack is already known - but the method also calculates tileToAttackFrom
|
// This seems inefficient as the tileToAttack is already known - but the method also calculates tileToAttackFrom
|
||||||
val attackableTile = BattleHelper
|
val attackableTile = TargetHelper
|
||||||
.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
|
.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
|
||||||
.firstOrNull { it.tileToAttack == tile }
|
.firstOrNull { it.tileToAttack == tile }
|
||||||
if (unit.canAttack() && attackableTile != null) {
|
if (unit.canAttack() && attackableTile != null) {
|
||||||
@ -700,7 +699,7 @@ class WorldMapHolder(
|
|||||||
|| (targetTile.isCityCenter() && unit.civ.hasExplored(targetTile)) }
|
|| (targetTile.isCityCenter() && unit.civ.hasExplored(targetTile)) }
|
||||||
.map { AttackableTile(unit.getTile(), it, 1f, null) }
|
.map { AttackableTile(unit.getTile(), it, 1f, null) }
|
||||||
.toList()
|
.toList()
|
||||||
else BattleHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
|
else TargetHelper.getAttackableEnemies(unit, unit.movement.getDistanceToTiles())
|
||||||
.filter { it.tileToAttack.isVisible(unit.civ) }
|
.filter { it.tileToAttack.isVisible(unit.civ) }
|
||||||
.distinctBy { it.tileToAttack }
|
.distinctBy { it.tileToAttack }
|
||||||
|
|
||||||
@ -730,7 +729,7 @@ class WorldMapHolder(
|
|||||||
|
|
||||||
private fun updateBombardableTilesForSelectedCity(city: City) {
|
private fun updateBombardableTilesForSelectedCity(city: City) {
|
||||||
if (!city.canBombard()) return
|
if (!city.canBombard()) return
|
||||||
for (attackableTile in UnitAutomation.getBombardableTiles(city)) {
|
for (attackableTile in TargetHelper.getBombardableTiles(city)) {
|
||||||
val group = tileGroups[attackableTile]!!
|
val group = tileGroups[attackableTile]!!
|
||||||
group.layerOverlay.showHighlight(colorFromRGB(237, 41, 57))
|
group.layerOverlay.showHighlight(colorFromRGB(237, 41, 57))
|
||||||
group.layerOverlay.showCrosshair()
|
group.layerOverlay.showCrosshair()
|
||||||
|
@ -4,14 +4,13 @@ import com.badlogic.gdx.graphics.Color
|
|||||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.unciv.logic.automation.unit.AttackableTile
|
import com.unciv.logic.battle.AttackableTile
|
||||||
import com.unciv.logic.automation.unit.BattleHelper
|
|
||||||
import com.unciv.logic.automation.unit.UnitAutomation
|
|
||||||
import com.unciv.logic.battle.Battle
|
import com.unciv.logic.battle.Battle
|
||||||
import com.unciv.logic.battle.BattleDamage
|
import com.unciv.logic.battle.BattleDamage
|
||||||
import com.unciv.logic.battle.CityCombatant
|
import com.unciv.logic.battle.CityCombatant
|
||||||
import com.unciv.logic.battle.ICombatant
|
import com.unciv.logic.battle.ICombatant
|
||||||
import com.unciv.logic.battle.MapUnitCombatant
|
import com.unciv.logic.battle.MapUnitCombatant
|
||||||
|
import com.unciv.logic.battle.TargetHelper
|
||||||
import com.unciv.logic.map.tile.Tile
|
import com.unciv.logic.map.tile.Tile
|
||||||
import com.unciv.models.UncivSound
|
import com.unciv.models.UncivSound
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
@ -73,7 +72,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
|
|||||||
val defender = tryGetDefender() ?: return hide()
|
val defender = tryGetDefender() ?: return hide()
|
||||||
if (attacker is CityCombatant && defender is CityCombatant) return hide()
|
if (attacker is CityCombatant && defender is CityCombatant) return hide()
|
||||||
val tileToAttackFrom = if (attacker is MapUnitCombatant)
|
val tileToAttackFrom = if (attacker is MapUnitCombatant)
|
||||||
BattleHelper.getAttackableEnemies(
|
TargetHelper.getAttackableEnemies(
|
||||||
attacker.unit,
|
attacker.unit,
|
||||||
attacker.unit.movement.getDistanceToTiles()
|
attacker.unit.movement.getDistanceToTiles()
|
||||||
)
|
)
|
||||||
@ -247,11 +246,11 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
|
|||||||
|
|
||||||
if (attacker.canAttack()) {
|
if (attacker.canAttack()) {
|
||||||
if (attacker is MapUnitCombatant) {
|
if (attacker is MapUnitCombatant) {
|
||||||
attackableTile = BattleHelper
|
attackableTile = TargetHelper
|
||||||
.getAttackableEnemies(attacker.unit, attacker.unit.movement.getDistanceToTiles())
|
.getAttackableEnemies(attacker.unit, attacker.unit.movement.getDistanceToTiles())
|
||||||
.firstOrNull{ it.tileToAttack == defender.getTile()}
|
.firstOrNull{ it.tileToAttack == defender.getTile()}
|
||||||
} else if (attacker is CityCombatant) {
|
} else if (attacker is CityCombatant) {
|
||||||
val canBombard = UnitAutomation.getBombardableTiles(attacker.city).contains(defender.getTile())
|
val canBombard = TargetHelper.getBombardableTiles(attacker.city).contains(defender.getTile())
|
||||||
if (canBombard) {
|
if (canBombard) {
|
||||||
attackableTile = AttackableTile(attacker.getTile(), defender.getTile(), 0f, defender)
|
attackableTile = AttackableTile(attacker.getTile(), defender.getTile(), 0f, defender)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user