mirror of
https://github.com/yairm210/Unciv.git
synced 2025-03-09 20:29:50 +07:00
Get distance to nearest enemy rework (#10481)
* Initial refactor * Moved checking if a tile has an enemy to a new method * Rewrote getDistanceToEnemyUnit * changed the position of the logic of checking if the enemy is still there * Changed some of the other methods to use the ThreatManager getClosestEnemy() * Added a new getTilesWithEnemyUnitsInDistance method * Added a new getEnemyMilitaryUnitsInDistance method * Converted a few lines to use ThreatManager * Changed Air units to use threat manager * Fixed tileWithEnemy error * distanceToClosestEnemyTiles now clears at the start of every turn * Added blank lines to end of ThreatManager.kt * Renamed tilesInRange to tilesWithEnemyUnitsInRange * Changed ArrayList return to a MutableList * Removed ClosestEnemyTileData being a data class * Improved commenting * Improved commenting2 * getEnemyMilitaryUnitsInDistance now uses a flatMap and moved getDangerousTiles to threat manager * Created a new helper method getEnemyUnitsOnTiles * Renamed clearThreatData to clear * Added shortcut if maxDist is less than or equal to distanceSearched * Fixed distanceWithNoEnemies in getTilesWithEnemyUnitsInDistance * Fixed notFoundDistance being higher than maxDistance when takeLargerValues is false * Added some ThreatManager tests * Added some more ThreatManager tests * Removed visible map after use * getTilesWithEnemyUnitsInDistance doesn't search distances <= tileData.distanceSearched (previously was <) * Added 3 more tests --------- Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
parent
dc7f1f703a
commit
f1ceaa216a
@ -342,7 +342,7 @@ object NextTurnAutomation {
|
||||
private fun getUnitPriority(unit: MapUnit, isAtWar: Boolean): Int {
|
||||
if (unit.isCivilian() && !unit.isGreatPersonOfType("War")) return 1 // Civilian
|
||||
if (unit.baseUnit.isAirUnit()) return 2
|
||||
val distance = if (!isAtWar) 0 else unit.getDistanceToEnemyUnit(6)
|
||||
val distance = if (!isAtWar) 0 else unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(),6)
|
||||
// Lower health units should move earlier to swap with higher health units
|
||||
return distance + (unit.health / 10) + when {
|
||||
unit.baseUnit.isRanged() -> 10
|
||||
|
@ -10,8 +10,8 @@ import com.unciv.logic.map.tile.Tile
|
||||
object AirUnitAutomation {
|
||||
|
||||
fun automateFighter(unit: MapUnit) {
|
||||
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
|
||||
val enemyAirUnitsInRange = tilesInRange
|
||||
val tilesWithEnemyUnitsInRange = unit.civ.threatManager.getTilesWithEnemyUnitsInDistance(unit.getTile(), unit.getRange())
|
||||
val enemyAirUnitsInRange = tilesWithEnemyUnitsInRange
|
||||
.flatMap { it.airUnits.asSequence() }.filter { it.civ.isAtWarWith(unit.civ) }
|
||||
|
||||
if (enemyAirUnitsInRange.any()) return // we need to be on standby in case they attack
|
||||
|
@ -26,9 +26,7 @@ object CivilianUnitAutomation {
|
||||
unit.movement.moveToTile(tilesCanMoveTo.minByOrNull { it.value.totalDistance }!!.key)
|
||||
}
|
||||
|
||||
val tilesWhereWeWillBeCaptured = unit.currentTile.getTilesInDistance(5)
|
||||
.mapNotNull { it.militaryUnit }
|
||||
.filter { it.civ.isAtWarWith(unit.civ) }
|
||||
val tilesWhereWeWillBeCaptured = unit.civ.threatManager.getEnemyMilitaryUnitsInDistance(unit.getTile(),5)
|
||||
.flatMap { it.movement.getReachableTilesInCurrentTurn() }
|
||||
.filter { it.militaryUnit?.civ != unit.civ }
|
||||
.toSet()
|
||||
@ -139,7 +137,7 @@ object CivilianUnitAutomation {
|
||||
// This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
|
||||
// Cheaper than determining which enemies could attack us next turn
|
||||
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
|
||||
.filter { UnitAutomation.containsEnemyMilitaryUnit(unit, it) }
|
||||
.filter { unit.civ.threatManager.doesTileHaveMilitaryEnemy(it) }
|
||||
|
||||
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()
|
||||
&& unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter()) {
|
||||
@ -168,16 +166,9 @@ object CivilianUnitAutomation {
|
||||
}
|
||||
val tileFurthestFromEnemy = reachableTiles.keys
|
||||
.filter { unit.movement.canMoveTo(it) && unit.getDamageFromTerrain(it) < unit.health }
|
||||
.maxByOrNull { countDistanceToClosestEnemy(unit, it) }
|
||||
.maxByOrNull { unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(), 4, false) }
|
||||
?: return // can't move anywhere!
|
||||
unit.movement.moveToTile(tileFurthestFromEnemy)
|
||||
}
|
||||
|
||||
|
||||
private fun countDistanceToClosestEnemy(unit: MapUnit, tile: Tile): Int {
|
||||
for (i in 1..3)
|
||||
if (tile.getTilesAtDistance(i).any { UnitAutomation.containsEnemyMilitaryUnit(unit, it) })
|
||||
return i
|
||||
return 4
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ object UnitAutomation {
|
||||
&& tile.neighbors.any { !unit.civ.hasExplored(it) }
|
||||
&& (!unit.civ.isCityState() || tile.neighbors.any { it.getOwner() == unit.civ }) // Don't want city-states exploring far outside their borders
|
||||
&& unit.getDamageFromTerrain(tile) <= 0 // Don't take unnecessary damage
|
||||
&& tile.getTilesInDistance(3) .none { containsEnemyMilitaryUnit(unit, it) } // don't walk in range of enemy units
|
||||
&& unit.civ.threatManager.getDistanceToClosestEnemyUnit(tile, 3) <= 3 // don't walk in range of enemy units
|
||||
&& unit.movement.canMoveTo(tile) // expensive, evaluate last
|
||||
&& unit.movement.canReach(tile) // expensive, evaluate last
|
||||
}
|
||||
@ -262,8 +262,8 @@ object UnitAutomation {
|
||||
// Precondition: This must be a military unit
|
||||
if (unit.isCivilian()) return false
|
||||
// Better to do a more healing oriented move then
|
||||
if (unit.getDistanceToEnemyUnit(6, true) > 4) return false
|
||||
|
||||
if (unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(),6, true) > 4) return false
|
||||
|
||||
if (unit.baseUnit.isAirUnit()) {
|
||||
return false
|
||||
}
|
||||
@ -272,12 +272,14 @@ object UnitAutomation {
|
||||
val swapableTiles = unitDistanceToTiles.keys.filter { it.militaryUnit != null && it.militaryUnit!!.owner == unit.owner}.reversed()
|
||||
for (swapTile in swapableTiles) {
|
||||
val otherUnit = swapTile.militaryUnit!!
|
||||
if (otherUnit.health > 80
|
||||
&& unit.getDistanceToEnemyUnit(6, false) < otherUnit.getDistanceToEnemyUnit(6,false)) {
|
||||
val ourDistanceToClosestEnemy = unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(),6, false)
|
||||
if (otherUnit.health > 80
|
||||
&& ourDistanceToClosestEnemy < otherUnit.civ.threatManager.getDistanceToClosestEnemyUnit(otherUnit.getTile(),6,false)) {
|
||||
|
||||
if (otherUnit.baseUnit.isRanged()) {
|
||||
// Don't swap ranged units closer than they have to be
|
||||
val range = otherUnit.baseUnit.range
|
||||
if (unit.getDistanceToEnemyUnit(6) < range)
|
||||
if (ourDistanceToClosestEnemy < range)
|
||||
continue
|
||||
}
|
||||
if (unit.movement.canUnitSwapTo(swapTile)) {
|
||||
@ -299,7 +301,7 @@ object UnitAutomation {
|
||||
|
||||
val currentUnitTile = unit.getTile()
|
||||
|
||||
val dangerousTiles = getDangerousTiles(unit)
|
||||
val dangerousTiles = unit.civ.threatManager.getDangerousTiles(unit)
|
||||
|
||||
val viableTilesForHealing = unitDistanceToTiles.keys
|
||||
.filter { it !in dangerousTiles && unit.movement.canMoveTo(it) }
|
||||
@ -345,23 +347,6 @@ object UnitAutomation {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getDangerousTiles(unit: MapUnit): HashSet<Tile> {
|
||||
val nearbyRangedEnemyUnits = unit.currentTile.getTilesInDistance(3)
|
||||
.flatMap { tile -> tile.getUnits().filter { unit.civ.isAtWarWith(it.civ) } }
|
||||
|
||||
val tilesInRangeOfAttack = nearbyRangedEnemyUnits
|
||||
.flatMap { it.getTile().getTilesInDistance(it.getRange()) }
|
||||
|
||||
val tilesWithinBombardmentRange = unit.currentTile.getTilesInDistance(3)
|
||||
.filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.range) }
|
||||
|
||||
val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(3)
|
||||
.filter { unit.getDamageFromTerrain(it) > 0 }
|
||||
|
||||
return (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet()
|
||||
}
|
||||
|
||||
fun tryPillageImprovement(unit: MapUnit): Boolean {
|
||||
if (unit.isCivilian()) return false
|
||||
val unitDistanceToTiles = unit.movement.getDistanceToTiles()
|
||||
@ -603,10 +588,6 @@ object UnitAutomation {
|
||||
unit.civ.addNotification("${unit.shortDisplayName()} finished exploring.", unit.currentTile.position, NotificationCategory.Units, unit.name, "OtherIcons/Sleep")
|
||||
unit.action = null
|
||||
}
|
||||
|
||||
|
||||
internal fun containsEnemyMilitaryUnit(unit: MapUnit, tile: Tile) =
|
||||
tile.militaryUnit != null
|
||||
&& tile.militaryUnit!!.civ.isAtWarWith(unit.civ)
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.unciv.logic.civilization.managers.QuestManager
|
||||
import com.unciv.logic.civilization.managers.ReligionManager
|
||||
import com.unciv.logic.civilization.managers.RuinsManager
|
||||
import com.unciv.logic.civilization.managers.TechManager
|
||||
import com.unciv.logic.civilization.managers.ThreatManager
|
||||
import com.unciv.logic.civilization.managers.UnitManager
|
||||
import com.unciv.logic.civilization.managers.VictoryManager
|
||||
import com.unciv.logic.civilization.transients.CivInfoStatsForNextTurn
|
||||
@ -88,6 +89,9 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
|
||||
@Transient
|
||||
val units = UnitManager(this)
|
||||
|
||||
@Transient
|
||||
var threatManager = ThreatManager(this)
|
||||
|
||||
@Transient
|
||||
var diplomacyFunctions = DiplomacyFunctions(this)
|
||||
|
149
core/src/com/unciv/logic/civilization/managers/ThreatManager.kt
Normal file
149
core/src/com/unciv/logic/civilization/managers/ThreatManager.kt
Normal file
@ -0,0 +1,149 @@
|
||||
package com.unciv.logic.civilization.managers
|
||||
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
|
||||
class ThreatManager(val civInfo: Civilization) {
|
||||
|
||||
class ClosestEnemyTileData(
|
||||
/** The farthest radius in which we have checked all the tiles for enemies.
|
||||
* A value of 2 means there are no enemies in a radius of 2. */
|
||||
var distanceSearched: Int,
|
||||
/** It is guaranteed that there is no enemy within a radius of D-1.
|
||||
* The enemy that we saw might have been killed.
|
||||
* so we have to check the tileWithEnemy to see if we need to search again. */
|
||||
var distanceToClosestEnemy: Int? = null,
|
||||
/** Stores the location of the enemy that we saw.
|
||||
* This allows us to quickly check if they are still alive.
|
||||
* and if we should search farther. */
|
||||
var tileWithEnemy: Tile? = null
|
||||
)
|
||||
|
||||
private val distanceToClosestEnemyTiles = HashMap<Tile, ClosestEnemyTileData>()
|
||||
|
||||
/**
|
||||
* Gets the distance to the closest visible enemy unit or city.
|
||||
* The result value is cached and since it is called each turn in NextTurnAutomation.getUnitPriority
|
||||
* each subsequent calls are likely to be free.
|
||||
*/
|
||||
fun getDistanceToClosestEnemyUnit(tile: Tile, maxDist: Int, takeLargerValues: Boolean = true): Int {
|
||||
val tileData = distanceToClosestEnemyTiles[tile]
|
||||
// Needs to be a high value, but not the max value so we can still add to it. Example: nextTurnAutomation sorting
|
||||
val notFoundDistance = if (takeLargerValues) 500000 else maxDist
|
||||
var minDistanceToSearch = 1
|
||||
// Look if we can return the cache or if we can reduce our search
|
||||
if (tileData != null) {
|
||||
if (tileData.distanceToClosestEnemy == null) {
|
||||
if (tileData.distanceSearched >= maxDist)
|
||||
return notFoundDistance
|
||||
// else: we need to search more we didn't search as far as we are looking for now
|
||||
} else if (doesTileHaveMilitaryEnemy(tileData.tileWithEnemy!!)) {
|
||||
// The enemy is still there
|
||||
return if (tileData.distanceToClosestEnemy!! <= maxDist || takeLargerValues)
|
||||
tileData.distanceToClosestEnemy!!
|
||||
else notFoundDistance
|
||||
}
|
||||
// Only search the tiles that we haven't searched yet
|
||||
minDistanceToSearch = (tileData.distanceSearched + 1).coerceAtLeast(1)
|
||||
}
|
||||
|
||||
// Search for nearby enemies and store the results
|
||||
for (i in minDistanceToSearch..maxDist) {
|
||||
for (searchTile in tile.getTilesAtDistance(i)) {
|
||||
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
||||
// We have only completely searched a radius of i - 1
|
||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(i - 1, i, searchTile)
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(maxDist, null, null)
|
||||
return notFoundDistance
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all tiles with enemy units on them in distance.
|
||||
* May be quicker than a manual search because of caching.
|
||||
* Also ends up calculating and caching [getDistanceToClosestEnemyUnit].
|
||||
*/
|
||||
fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList<Tile> {
|
||||
val tileData = distanceToClosestEnemyTiles[tile]
|
||||
|
||||
// Shortcut, we don't need to search for anything
|
||||
if (tileData != null && maxDist <= tileData.distanceSearched)
|
||||
return ArrayList<Tile>()
|
||||
|
||||
val minDistanceToSearch = (tileData?.distanceSearched?.coerceAtLeast(0) ?: 0) + 1
|
||||
var distanceWithNoEnemies = tileData?.distanceSearched ?: 0
|
||||
var closestEnemyDistance = tileData?.distanceToClosestEnemy
|
||||
var tileWithEnemy = tileData?.tileWithEnemy
|
||||
val tilesWithEnemies = ArrayList<Tile>()
|
||||
|
||||
for (i in minDistanceToSearch..maxDist) {
|
||||
for (searchTile in tile.getTilesAtDistance(i)) {
|
||||
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
||||
tilesWithEnemies.add(searchTile)
|
||||
}
|
||||
}
|
||||
if (tilesWithEnemies.isEmpty() && distanceWithNoEnemies < i) {
|
||||
distanceWithNoEnemies = i
|
||||
}
|
||||
if (tilesWithEnemies.isNotEmpty() && (closestEnemyDistance == null || closestEnemyDistance < i)) {
|
||||
closestEnemyDistance = i
|
||||
tileWithEnemy = tilesWithEnemies.first()
|
||||
}
|
||||
}
|
||||
// Cache our results for later
|
||||
// tilesWithEnemies must return the enemy at a distance of closestEnemyDistance
|
||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(distanceWithNoEnemies, closestEnemyDistance, tileWithEnemy)
|
||||
return tilesWithEnemies
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all enemy military units within maxDistance of the tile.
|
||||
*/
|
||||
fun getEnemyMilitaryUnitsInDistance(tile: Tile, maxDist: Int): List<MapUnit> =
|
||||
getEnemyUnitsOnTiles(getTilesWithEnemyUnitsInDistance(tile, maxDist))
|
||||
|
||||
fun getEnemyUnitsOnTiles(tilesWithEnemyUnitsInDistance:List<Tile>): List<MapUnit> =
|
||||
tilesWithEnemyUnitsInDistance.flatMap { enemyTile -> enemyTile.getUnits()
|
||||
.filter { it.isMilitary() && civInfo.isAtWarWith(it.civ) } }
|
||||
|
||||
|
||||
fun getDangerousTiles(unit: MapUnit, distance: Int = 3): HashSet<Tile> {
|
||||
val tilesWithEnemyUnits = getTilesWithEnemyUnitsInDistance(unit.getTile(), distance)
|
||||
val nearbyRangedEnemyUnits = getEnemyUnitsOnTiles(tilesWithEnemyUnits)
|
||||
|
||||
val tilesInRangeOfAttack = nearbyRangedEnemyUnits
|
||||
.flatMap { it.getTile().getTilesInDistance(it.getRange()) }
|
||||
|
||||
val tilesWithinBombardmentRange = tilesWithEnemyUnits
|
||||
.filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.range) }
|
||||
|
||||
val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(distance)
|
||||
.filter { unit.getDamageFromTerrain(it) > 0 }
|
||||
|
||||
return (tilesInRangeOfAttack + tilesWithinBombardmentRange + tilesWithTerrainDamage).toHashSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the tile has a visible enemy, otherwise returns false.
|
||||
*/
|
||||
fun doesTileHaveMilitaryEnemy(tile: Tile): Boolean {
|
||||
if (!tile.isExplored(civInfo)) return false
|
||||
if (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo)) return true
|
||||
if (!tile.isVisible(civInfo)) return false
|
||||
if (tile.militaryUnit != null
|
||||
&& tile.militaryUnit!!.civ.isAtWarWith(civInfo)
|
||||
&& !tile.militaryUnit!!.isInvisible(civInfo))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
distanceToClosestEnemyTiles.clear()
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ class TurnManager(val civInfo: Civilization) {
|
||||
|
||||
fun startTurn(progressBar: NextTurnProgress? = null) {
|
||||
if (civInfo.isSpectator()) return
|
||||
|
||||
|
||||
civInfo.threatManager.clear()
|
||||
if (civInfo.isMajorCiv() && civInfo.isAlive()) {
|
||||
civInfo.statsHistory.recordRankingStats(civInfo)
|
||||
}
|
||||
|
@ -487,40 +487,6 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
|
||||
fun isGreatPerson() = baseUnit.isGreatPerson()
|
||||
fun isGreatPersonOfType(type: String) = baseUnit.isGreatPersonOfType(type)
|
||||
|
||||
/**
|
||||
* Gets the distance to the closest visible enemy unit or city.
|
||||
* The result value is cached
|
||||
* Since it is called each turn each subsequent call is essentially free
|
||||
*/
|
||||
fun getDistanceToEnemyUnit(maxDist: Int, takeLargerValues: Boolean = true): Int {
|
||||
if (cache.distanceToClosestEnemyUnit != null) {
|
||||
return if ((takeLargerValues || cache.distanceToClosestEnemyUnit!! < maxDist))
|
||||
cache.distanceToClosestEnemyUnit!!
|
||||
// In some cases we might rely on every distance farther than maxDist being the same
|
||||
else Int.MAX_VALUE
|
||||
}
|
||||
|
||||
fun tileHasEnemyCity(tile: Tile): Boolean = tile.isExplored(civ)
|
||||
&& tile.isCityCenter()
|
||||
&& tile.getCity()!!.civ.isAtWarWith(civ)
|
||||
|
||||
fun tileHasEnemyMilitaryUnit(tile: Tile): Boolean = tile.isVisible(civ)
|
||||
&& tile.militaryUnit != null
|
||||
&& tile.militaryUnit!!.civ.isAtWarWith(civ)
|
||||
&& !tile.militaryUnit!!.isInvisible(civ)
|
||||
|
||||
// Needs to be a high value, but not the max value so we can still add to it
|
||||
cache.distanceToClosestEnemyUnit = 500000
|
||||
for (i in 1..maxDist) {
|
||||
if (currentTile.getTilesAtDistance(i).any {
|
||||
tileHasEnemyCity(it) || tileHasEnemyMilitaryUnit(it) }) {
|
||||
cache.distanceToClosestEnemyUnit = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return cache.distanceToClosestEnemyUnit!!
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region state-changing functions
|
||||
@ -565,10 +531,6 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
|
||||
val currentTile = getTile()
|
||||
if (isMoving()) {
|
||||
// We have moved so invalidate the previous calculation
|
||||
cache.distanceToClosestEnemyUnit = null
|
||||
cache.distanceToClosestEnemyUnitSearched = null
|
||||
|
||||
val destinationTile = getMovementDestination()
|
||||
if (!movement.canReach(destinationTile)) { // That tile that we were moving towards is now unreachable -
|
||||
// for instance we headed towards an unknown tile and it's apparently unreachable
|
||||
@ -923,7 +885,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
"Non-City" -> true
|
||||
else -> {
|
||||
if (baseUnit.matchesFilter(filter)) return true
|
||||
if (civ.matchesFilter(filter)) return true
|
||||
if (civ.nation.matchesFilter(filter)) return true
|
||||
if (tempUniquesMap.containsKey(filter)) return true
|
||||
return false
|
||||
}
|
||||
|
@ -71,9 +71,6 @@ class MapUnitCache(private val mapUnit: MapUnit) {
|
||||
|
||||
var hasCitadelPlacementUnique = false
|
||||
|
||||
var distanceToClosestEnemyUnit: Int? = null
|
||||
var distanceToClosestEnemyUnitSearched: Int? = null
|
||||
|
||||
fun updateUniques() {
|
||||
|
||||
allTilesCosts1 = mapUnit.hasUnique(UniqueType.AllTilesCost1Move)
|
||||
|
@ -364,8 +364,6 @@ class UnitMovement(val unit: MapUnit) {
|
||||
fun moveToTile(destination: Tile, considerZoneOfControl: Boolean = true) {
|
||||
if (destination == unit.getTile() || unit.isDestroyed) return // already here (or dead)!
|
||||
// Reset closestEnemy chache
|
||||
unit.cache.distanceToClosestEnemyUnit = null
|
||||
unit.cache.distanceToClosestEnemyUnitSearched = null
|
||||
|
||||
if (unit.baseUnit.movesLikeAirUnits()) { // air units move differently from all other units
|
||||
if (unit.action != UnitActionType.Automate.value) unit.action = null
|
||||
|
@ -0,0 +1,173 @@
|
||||
package com.unciv.logic.civilization.managers
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
import com.unciv.testing.TestGame
|
||||
import com.unciv.utils.DebugUtils
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(GdxTestRunner::class)
|
||||
class ThreatManangerTests {
|
||||
|
||||
val testGame = TestGame()
|
||||
val civ = testGame.addCiv()
|
||||
val neutralCiv = testGame.addCiv()
|
||||
val enemyCiv = testGame.addCiv()
|
||||
val threatManager = civ.threatManager
|
||||
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
DebugUtils.VISIBLE_MAP = true // Needed to be able to see the enemy units
|
||||
testGame.makeHexagonalMap(10)
|
||||
civ.diplomacyFunctions.makeCivilizationsMeet(enemyCiv)
|
||||
civ.diplomacyFunctions.makeCivilizationsMeet(neutralCiv)
|
||||
civ.getDiplomacyManager(enemyCiv).declareWar()
|
||||
}
|
||||
|
||||
@After
|
||||
fun wrapUp() {
|
||||
DebugUtils.VISIBLE_MAP = false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Distance to closest enemy with no enemies`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
assertEquals(5, threatManager.getDistanceToClosestEnemyUnit(centerTile,5, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find tiles with enemies with no enemies`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
assertEquals(0, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find enemies on tiles with no enemies`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
assertEquals(0, threatManager.getEnemyUnitsOnTiles(threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5)).count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to enemy`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Warrior", neutralCiv, testGame.getTile(Vector2(1f, 1f)))
|
||||
assertEquals(3, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to closer enemy`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
assertEquals(3, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to farther enemy`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
assertEquals(2, threatManager.getDistanceToClosestEnemyUnit(centerTile, 2, false))
|
||||
// Cache results should say there is not a unit within a distance of 2
|
||||
// Therefore the warrior at distance 2 should not be checked
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
assertEquals(4, threatManager.getDistanceToClosestEnemyUnit(centerTile, 4, false))
|
||||
assertEquals(4, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to enemy wrong cache`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
assertEquals(3, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
testGame.getTile(Vector2(3f, 0f)).militaryUnit!!.removeFromTile()
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 1f)))
|
||||
assertEquals(4, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
testGame.getTile(Vector2(4f, 0f)).militaryUnit!!.removeFromTile()
|
||||
assertEquals(4, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5, false))
|
||||
testGame.getTile(Vector2(4f, 1f)).militaryUnit!!.removeFromTile()
|
||||
assertEquals(5, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to enemy cache`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
assertEquals(3, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
// An enemy unit should never be spawned closer than we previously searched
|
||||
// Therefore our cache results should return 3 instead of the closer unit at a distance of 2
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
assertEquals(3, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find tiles with enemy units`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
assertEquals(3, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
assertEquals(2, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 3).count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find tiles with enemy units cache`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
assertEquals(5, threatManager.getDistanceToClosestEnemyUnit(centerTile, 5, false))
|
||||
// We have stored in the cach that there is no enemy unit within a distance of 5
|
||||
// Therefore adding these units is illegal and should not be returned
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
assertEquals(0, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
assertEquals(0, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 3).count())
|
||||
|
||||
// Now it might be another turn, so it is allowed
|
||||
threatManager.clear()
|
||||
assertEquals(3, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
assertEquals(2, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 3).count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Find distance to enemy after find tiles with enemy units`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
assertEquals(1, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
assertEquals(1, threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5).count())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Find enemy units on tiles`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addCity(enemyCiv,testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Bomber", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(2f, 0f)))
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(4f, 0f)))
|
||||
testGame.addUnit("Warrior", neutralCiv, testGame.getTile(Vector2(-3f, -3f)))
|
||||
assertEquals(4, threatManager.getEnemyUnitsOnTiles(threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 5)).count())
|
||||
assertEquals(3, threatManager.getEnemyUnitsOnTiles(threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 3)).count())
|
||||
assertEquals(0, threatManager.getEnemyUnitsOnTiles(threatManager.getTilesWithEnemyUnitsInDistance(centerTile, 1)).count())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Dangerous tiles`() {
|
||||
val centerTile = testGame.getTile(Vector2(0f, 0f))
|
||||
testGame.addUnit("Warrior", civ, centerTile)
|
||||
testGame.addUnit("Warrior", enemyCiv, testGame.getTile(Vector2(3f, 0f)))
|
||||
testGame.addUnit("Archer", enemyCiv, testGame.getTile(Vector2(-3f, 0f)))
|
||||
val dangerousTiles = threatManager.getDangerousTiles(centerTile.militaryUnit!!,3)
|
||||
assertEquals(null, testGame.getTile(Vector2(3f, 0f)).getTilesInDistance(1).firstOrNull {tile -> !dangerousTiles.contains(tile)})
|
||||
assertEquals(null, testGame.getTile(Vector2(-3f, 0f)).getTilesInDistance(2).firstOrNull {tile -> !dangerousTiles.contains(tile)})
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user