mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 05:46:43 -04:00
ThreatManager improvement (#11030)
* Reworked ThreatManager to be optimised for getTilesWithEnemyUnitsInDistance * Refactored and added an exception * doesTileHaveMilitaryEnemy now searches all military units on the tile and not just the unit in the military slot * Fixed some errors * Refactored getTilesWithEnemyUnitsInDistance to use a mutableIterator * Added some more comments
This commit is contained in:
parent
cfc26e03fd
commit
10f3781e6f
@ -4,20 +4,20 @@ import com.unciv.logic.civilization.Civilization
|
|||||||
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles optimised operations related to finding threats or allies in an area.
|
||||||
|
*/
|
||||||
class ThreatManager(val civInfo: Civilization) {
|
class ThreatManager(val civInfo: Civilization) {
|
||||||
|
|
||||||
class ClosestEnemyTileData(
|
class ClosestEnemyTileData(
|
||||||
/** The farthest radius in which we have checked all the tiles for enemies.
|
/** The farthest radius in which we have checked tiles for enemies.
|
||||||
* A value of 2 means there are no enemies in a radius of 2. */
|
* A value of 2 means all enemies at a radius of 2 are in tilesWithEnemies. */
|
||||||
var distanceSearched: Int,
|
var distanceSearched: Int,
|
||||||
/** It is guaranteed that there is no enemy within a radius of D-1.
|
/** Stores the location of the enemy tiles that we saw with the distance at which we saw them.
|
||||||
* The enemy that we saw might have been killed.
|
* Tiles are sorted by distance in increasing order.
|
||||||
* so we have to check the tileWithEnemy to see if we need to search again. */
|
* This allows us to quickly check if they are still alive and if we should search farther.
|
||||||
var distanceToClosestEnemy: Int? = null,
|
* It is not guaranteed that each tile in this list has an enemy (since they may have died).*/
|
||||||
/** Stores the location of the enemy that we saw.
|
var tilesWithEnemies: MutableList<Pair<Tile,Int>>
|
||||||
* 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>()
|
private val distanceToClosestEnemyTiles = HashMap<Tile, ClosestEnemyTileData>()
|
||||||
@ -34,69 +34,96 @@ class ThreatManager(val civInfo: Civilization) {
|
|||||||
var minDistanceToSearch = 1
|
var minDistanceToSearch = 1
|
||||||
// Look if we can return the cache or if we can reduce our search
|
// Look if we can return the cache or if we can reduce our search
|
||||||
if (tileData != null) {
|
if (tileData != null) {
|
||||||
if (tileData.distanceToClosestEnemy == null) {
|
val tilesWithEnemies = tileData.tilesWithEnemies
|
||||||
if (tileData.distanceSearched >= maxDist)
|
// Check the tiles where we have previously found an enemy, if so it must be the closest
|
||||||
return notFoundDistance
|
while (tilesWithEnemies.isNotEmpty()) {
|
||||||
// else: we need to search more we didn't search as far as we are looking for now
|
val enemyTile = tilesWithEnemies.first()
|
||||||
} else if (doesTileHaveMilitaryEnemy(tileData.tileWithEnemy!!)) {
|
if (doesTileHaveMilitaryEnemy(enemyTile.first)) {
|
||||||
// The enemy is still there
|
return if (takeLargerValues) enemyTile.second
|
||||||
return if (tileData.distanceToClosestEnemy!! <= maxDist || takeLargerValues)
|
else enemyTile.second.coerceAtMost(maxDist)
|
||||||
tileData.distanceToClosestEnemy!!
|
} else {
|
||||||
else notFoundDistance
|
// This tile is no longer valid
|
||||||
|
tilesWithEnemies.removeFirst()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tileData.distanceSearched > maxDist) {
|
||||||
|
// We have already searched past the range we want to search and haven't found any enemies
|
||||||
|
return if (takeLargerValues) notFoundDistance else maxDist
|
||||||
|
}
|
||||||
|
|
||||||
// Only search the tiles that we haven't searched yet
|
// Only search the tiles that we haven't searched yet
|
||||||
minDistanceToSearch = (tileData.distanceSearched + 1).coerceAtLeast(1)
|
minDistanceToSearch = (tileData.distanceSearched + 1).coerceAtLeast(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (tileData != null && tileData.tilesWithEnemies.isNotEmpty()) throw IllegalStateException("There must be no elements in tile.data.tilesWithEnemies at this point")
|
||||||
|
val tilesWithEnemyAtDistance: MutableList<Pair<Tile,Int>> = mutableListOf()
|
||||||
// Search for nearby enemies and store the results
|
// Search for nearby enemies and store the results
|
||||||
for (i in minDistanceToSearch..maxDist) {
|
for (i in minDistanceToSearch..maxDist) {
|
||||||
for (searchTile in tile.getTilesAtDistance(i)) {
|
for (searchTile in tile.getTilesAtDistance(i)) {
|
||||||
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
||||||
// We have only completely searched a radius of i - 1
|
tilesWithEnemyAtDistance.add(Pair(searchTile, i))
|
||||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(i - 1, i, searchTile)
|
|
||||||
return i
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tilesWithEnemyAtDistance.isNotEmpty()) {
|
||||||
|
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(i, tilesWithEnemyAtDistance)
|
||||||
|
return i
|
||||||
|
}
|
||||||
}
|
}
|
||||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(maxDist, null, null)
|
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(maxDist, mutableListOf())
|
||||||
return notFoundDistance
|
return notFoundDistance
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all tiles with enemy units on them in distance.
|
* Returns all tiles with enemy units on them in distance.
|
||||||
|
* Every tile is guaranteed to have an enemy.
|
||||||
* May be quicker than a manual search because of caching.
|
* May be quicker than a manual search because of caching.
|
||||||
* Also ends up calculating and caching [getDistanceToClosestEnemyUnit].
|
* Also ends up calculating and caching [getDistanceToClosestEnemyUnit].
|
||||||
*/
|
*/
|
||||||
fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList<Tile> {
|
fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList<Tile> {
|
||||||
val tileData = distanceToClosestEnemyTiles[tile]
|
val tileData = distanceToClosestEnemyTiles[tile]
|
||||||
|
|
||||||
// Shortcut, we don't need to search for anything
|
// The list of tiles that we will return
|
||||||
if (tileData != null && maxDist <= tileData.distanceSearched)
|
val tilesWithEnemies: MutableList<Tile> = mutableListOf()
|
||||||
return ArrayList<Tile>()
|
// The list of tiles with distance that will be stored in distanceToClosestEnemyTiles
|
||||||
|
val tileDataTilesWithEnemies: MutableList<Pair<Tile,Int>> = if (tileData?.tilesWithEnemies != null) tileData.tilesWithEnemies else mutableListOf()
|
||||||
|
|
||||||
|
if (tileData != null && tileData.distanceSearched >= maxDist) {
|
||||||
|
// Add all tiles that we have previously found
|
||||||
|
val tilesWithEnemiesIterator = tileDataTilesWithEnemies.listIterator()
|
||||||
|
for (tileWithDistance in tilesWithEnemiesIterator) {
|
||||||
|
// Check if the next tile is out of our search range, if so lets stop here
|
||||||
|
if (tileWithDistance.second > maxDist) return tilesWithEnemies
|
||||||
|
// Check if the threat on the tile is still present
|
||||||
|
if (doesTileHaveMilitaryEnemy(tileWithDistance.first))
|
||||||
|
tilesWithEnemies.add(tileWithDistance.first)
|
||||||
|
else tilesWithEnemiesIterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't need to search for anything more if we have previously searched past maxDist
|
||||||
|
if (tileData != null && maxDist <= tileData.distanceSearched)
|
||||||
|
return tilesWithEnemies
|
||||||
|
|
||||||
|
|
||||||
|
// Search all tiles that haven't been searched yet up until madDist
|
||||||
val minDistanceToSearch = (tileData?.distanceSearched?.coerceAtLeast(0) ?: 0) + 1
|
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 (i in minDistanceToSearch..maxDist) {
|
||||||
for (searchTile in tile.getTilesAtDistance(i)) {
|
for (searchTile in tile.getTilesAtDistance(i)) {
|
||||||
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
if (doesTileHaveMilitaryEnemy(searchTile)) {
|
||||||
tilesWithEnemies.add(searchTile)
|
tilesWithEnemies.add(searchTile)
|
||||||
|
tileDataTilesWithEnemies.add(Pair(searchTile, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tilesWithEnemies.isEmpty() && distanceWithNoEnemies < i) {
|
|
||||||
distanceWithNoEnemies = i
|
|
||||||
}
|
|
||||||
if (tilesWithEnemies.isNotEmpty() && (closestEnemyDistance == null || closestEnemyDistance < i)) {
|
|
||||||
closestEnemyDistance = i
|
|
||||||
tileWithEnemy = tilesWithEnemies.first()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Cache our results for later
|
if (tileData != null) {
|
||||||
// tilesWithEnemies must return the enemy at a distance of closestEnemyDistance
|
tileData.distanceSearched = maxOf(tileData.distanceSearched, maxDist)
|
||||||
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(distanceWithNoEnemies, closestEnemyDistance, tileWithEnemy)
|
} else {
|
||||||
|
// Cache our results for later
|
||||||
|
distanceToClosestEnemyTiles[tile] = ClosestEnemyTileData(maxDist, tileDataTilesWithEnemies)
|
||||||
|
}
|
||||||
return tilesWithEnemies
|
return tilesWithEnemies
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,9 +162,9 @@ class ThreatManager(val civInfo: Civilization) {
|
|||||||
if (!tile.isExplored(civInfo)) return false
|
if (!tile.isExplored(civInfo)) return false
|
||||||
if (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo)) return true
|
if (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo)) return true
|
||||||
if (!tile.isVisible(civInfo)) return false
|
if (!tile.isVisible(civInfo)) return false
|
||||||
if (tile.militaryUnit != null
|
if (tile.getUnits().any { it.isMilitary()
|
||||||
&& tile.militaryUnit!!.civ.isAtWarWith(civInfo)
|
&& it.civ.isAtWarWith(civInfo)
|
||||||
&& !tile.militaryUnit!!.isInvisible(civInfo))
|
&& !it.isInvisible(civInfo) })
|
||||||
return true
|
return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user