Resolved #937 - reveal all hidden tiles visible from every tile on unit movement path

This also solves the problem of units "skipping over" barbarian encampments and ancient ruins when on the path
This commit is contained in:
Yair Morgenstern
2019-07-21 23:32:06 +03:00
parent e73ad76227
commit 15f1648ca4
3 changed files with 55 additions and 53 deletions

View File

@ -21,8 +21,8 @@ android {
applicationId "com.unciv.app" applicationId "com.unciv.app"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 28 targetSdkVersion 28
versionCode 274 versionCode 275
versionName "2.18.3" versionName "2.18.4"
} }
// Had to add this crap for Travis to build, it wanted to sign the app // Had to add this crap for Travis to build, it wanted to sign the app

View File

@ -8,6 +8,7 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.GreatPersonManager import com.unciv.logic.civilization.GreatPersonManager
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.PathsToTilesWithinTurn
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.GameBasics
import com.unciv.models.gamebasics.unit.UnitType import com.unciv.models.gamebasics.unit.UnitType
@ -114,7 +115,7 @@ class UnitAutomation{
} }
fun tryHealUnit(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>):Boolean { fun tryHealUnit(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn):Boolean {
val tilesInDistance = unitDistanceToTiles.keys.filter { unit.movement.canMoveTo(it) } val tilesInDistance = unitDistanceToTiles.keys.filter { unit.movement.canMoveTo(it) }
if(unitDistanceToTiles.isEmpty()) return true // can't move, so... if(unitDistanceToTiles.isEmpty()) return true // can't move, so...
val unitTile = unit.getTile() val unitTile = unit.getTile()
@ -145,13 +146,14 @@ class UnitAutomation{
return true return true
} }
fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) : Boolean { fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) : Boolean {
if(unit.type.isCivilian()) return false if(unit.type.isCivilian()) return false
val tilesInDistance = unitDistanceToTiles.filter {it.value < unit.currentMovement}.keys val tilesThatCanWalkToAndThenPillage = unitDistanceToTiles
.filter {it.value.totalDistance < unit.currentMovement}.keys
.filter { unit.movement.canMoveTo(it) && UnitActions().canPillage(unit,it) } .filter { unit.movement.canMoveTo(it) && UnitActions().canPillage(unit,it) }
if (tilesInDistance.isEmpty()) return false if (tilesThatCanWalkToAndThenPillage.isEmpty()) return false
val tileToPillage = tilesInDistance.maxBy { it.getDefensiveBonus() }!! val tileToPillage = tilesThatCanWalkToAndThenPillage.maxBy { it.getDefensiveBonus() }!!
if (unit.getTile()!=tileToPillage) if (unit.getTile()!=tileToPillage)
unit.movement.moveToTile(tileToPillage) unit.movement.moveToTile(tileToPillage)
@ -189,7 +191,7 @@ class UnitAutomation{
class AttackableTile(val tileToAttackFrom:TileInfo, val tileToAttack:TileInfo) class AttackableTile(val tileToAttackFrom:TileInfo, val tileToAttack:TileInfo)
fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): ArrayList<AttackableTile> { fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): ArrayList<AttackableTile> {
val tilesWithEnemies = unit.civInfo.viewableTiles val tilesWithEnemies = unit.civInfo.viewableTiles
.filter { containsAttackableEnemy(it, MapUnitCombatant(unit)) } .filter { containsAttackableEnemy(it, MapUnitCombatant(unit)) }
@ -208,7 +210,7 @@ class UnitAutomation{
val movementPointsToExpendAfterMovement = if(unitMustBeSetUp) 1 else 0 val movementPointsToExpendAfterMovement = if(unitMustBeSetUp) 1 else 0
val movementPointsToExpendHere = if(unitMustBeSetUp && unit.action != "Set Up") 1 else 0 val movementPointsToExpendHere = if(unitMustBeSetUp && unit.action != "Set Up") 1 else 0
val movementPointsToExpendBeforeAttack = if(it.key==unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement val movementPointsToExpendBeforeAttack = if(it.key==unit.currentTile) movementPointsToExpendHere else movementPointsToExpendAfterMovement
unit.currentMovement - it.value - movementPointsToExpendBeforeAttack > 0.1 } // still got leftover movement points after all that, to attack (0.1 is because of Float nensense, see MapUnit.moveToTile(...) unit.currentMovement - it.value.totalDistance - movementPointsToExpendBeforeAttack > 0.1 } // still got leftover movement points after all that, to attack (0.1 is because of Float nensense, see MapUnit.moveToTile(...)
.map { it.key } .map { it.key }
.filter { unit.movement.canMoveTo(it) || it==unit.getTile() } .filter { unit.movement.canMoveTo(it) || it==unit.getTile() }
@ -314,7 +316,7 @@ class UnitAutomation{
// move into position far away enough that the bombard doesn't hurt // move into position far away enough that the bombard doesn't hurt
if(tilesCanAttackFromButNotInBombardRange.any()) if(tilesCanAttackFromButNotInBombardRange.any())
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!) unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!)
} }
else { else {
// calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once) // calculate total damage of units in surrounding 4-spaces from enemy city (so we can attack a city from 2 directions at once)
@ -336,7 +338,7 @@ class UnitAutomation{
return false return false
} }
private fun tryDisembarkUnitToAttackPosition(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean { private fun tryDisembarkUnitToAttackPosition(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false if (!unit.type.isMelee() || !unit.type.isLandUnit() || !unit.isEmbarked()) return false
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles) val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying // Only take enemies we can fight without dying
@ -457,7 +459,7 @@ class UnitAutomation{
return true return true
} }
fun tryGoToRuin(unit:MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean { fun tryGoToRuin(unit:MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if(!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins if(!unit.civInfo.isMajorCiv()) return false // barbs don't have anything to do in ruins
val tileWithRuin = unitDistanceToTiles.keys.firstOrNull{unit.movement.canMoveTo(it) && it.improvement == Constants.ancientRuins} val tileWithRuin = unitDistanceToTiles.keys.firstOrNull{unit.movement.canMoveTo(it) && it.improvement == Constants.ancientRuins}
if(tileWithRuin==null) return false if(tileWithRuin==null) return false
@ -465,7 +467,7 @@ class UnitAutomation{
return true return true
} }
internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>): Boolean { internal fun tryExplore(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): Boolean {
if(tryGoToRuin(unit,unitDistanceToTiles)) if(tryGoToRuin(unit,unitDistanceToTiles))
{ {
if(unit.currentMovement==0f) return true if(unit.currentMovement==0f) return true
@ -502,11 +504,11 @@ class UnitAutomation{
} }
fun wander(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) { fun wander(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) {
val reachableTiles= unitDistanceToTiles val reachableTiles= unitDistanceToTiles
.filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) } .filter { unit.movement.canMoveTo(it.key) && unit.movement.canReach(it.key) }
val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value == unit.currentMovement } val reachableTilesMaxWalkingDistance = reachableTiles.filter { it.value.totalDistance == unit.currentMovement }
if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first) if (reachableTilesMaxWalkingDistance.any()) unit.movement.moveToTile(reachableTilesMaxWalkingDistance.toList().random().first)
else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first) else if (reachableTiles.any()) unit.movement.moveToTile(reachableTiles.toList().random().first)

View File

@ -41,12 +41,13 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return to.getLastTerrain().movementCost.toFloat() // no road return to.getLastTerrain().movementCost.toFloat() // no road
} }
class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> { fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
if(unitMovement==0f) return hashMapOf() if(unitMovement==0f) return PathsToTilesWithinTurn()
val distanceToTiles = LinkedHashMap<TileInfo, Float>() val distanceToTiles = PathsToTilesWithinTurn()
val unitTile = unit.getTile().tileMap[origin] val unitTile = unit.getTile().tileMap[origin]
distanceToTiles[unitTile] = 0f distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f)
var tilesToCheck = listOf(unitTile) var tilesToCheck = listOf(unitTile)
while (!tilesToCheck.isEmpty()) { while (!tilesToCheck.isEmpty()) {
@ -63,10 +64,10 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
else { else {
val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor, unit.civInfo) val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor, unit.civInfo)
totalDistanceToTile = distanceToTiles[tileToCheck]!! + distanceBetweenTiles totalDistanceToTile = distanceToTiles[tileToCheck]!!.totalDistance + distanceBetweenTiles
} }
if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!! > totalDistanceToTile) { // this is the new best path if (!distanceToTiles.containsKey(neighbor) || distanceToTiles[neighbor]!!.totalDistance > totalDistanceToTile) { // this is the new best path
if (totalDistanceToTile < unitMovement) // We can still keep moving from here! if (totalDistanceToTile < unitMovement) // We can still keep moving from here!
updatedTiles += neighbor updatedTiles += neighbor
else else
@ -74,7 +75,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
// In Civ V, you can always travel between adjacent tiles, even if you don't technically // In Civ V, you can always travel between adjacent tiles, even if you don't technically
// have enough movement points - it simple depletes what you have // have enough movement points - it simple depletes what you have
distanceToTiles[neighbor] = totalDistanceToTile distanceToTiles[neighbor] = ParentTileAndTotalDistance(tileToCheck,totalDistanceToTile)
} }
} }
@ -101,7 +102,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn) val distanceToTilesThisTurn = getDistanceToTilesWithinTurn(tileToCheck.position, movementThisTurn)
for (reachableTile in distanceToTilesThisTurn.keys) { for (reachableTile in distanceToTilesThisTurn.keys) {
if (reachableTile == destination) if (reachableTile == destination)
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!! distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!.totalDistance
else { else {
if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing... if (movementTreeParents.containsKey(reachableTile)) continue // We cannot be faster than anything existing...
if (!canMoveTo(reachableTile)) continue // This is a tile that we can''t actually enter - either an intermediary tile containing our unit, or an enemy unit/city if (!canMoveTo(reachableTile)) continue // This is a tile that we can''t actually enter - either an intermediary tile containing our unit, or an enemy unit/city
@ -163,7 +164,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer... if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
return currentTile return currentTile
destinationTileThisTurn = reachableDestinationNeighbors.minBy { distanceToTiles[it]!! }!! destinationTileThisTurn = reachableDestinationNeighbors.minBy { distanceToTiles[it]!!.totalDistance }!!
} }
} else { // If the tile is far away, we need to build a path how to get there, and then take the first step } else { // If the tile is far away, we need to build a path how to get there, and then take the first step
val path = getShortestPath(destination) val path = getShortestPath(destination)
@ -182,24 +183,6 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return getShortestPath(destination).isNotEmpty() return getShortestPath(destination).isNotEmpty()
} }
fun getFullPathToCloseTile(destination: TileInfo): List<TileInfo> {
val currentUnitTile = unit.getTile()
val distanceToTiles = getDistanceToTiles()
val reversedList = ArrayList<TileInfo>()
var currentTile = destination
while(currentTile != currentUnitTile){
reversedList.add(currentTile)
val distanceToCurrentTile = distanceToTiles[currentTile]!!
if(currentUnitTile in currentTile.neighbors
&& getMovementCostBetweenAdjacentTiles(currentUnitTile,currentTile,unit.civInfo) == distanceToCurrentTile)
return reversedList.reversed()
for(tile in currentTile.neighbors)
currentTile = currentTile.neighbors.first{it in distanceToTiles
&& getMovementCostBetweenAdjacentTiles(it,currentTile,unit.civInfo) == distanceToCurrentTile - distanceToTiles[it]!!}
}
throw Exception("We couldn't get the path between the two tiles")
}
fun teleportToClosestMoveableTile(){ fun teleportToClosestMoveableTile(){
var allowedTile:TileInfo? = null var allowedTile:TileInfo? = null
@ -225,35 +208,39 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
} }
fun moveToTile(otherTile: TileInfo) { fun moveToTile(destination: TileInfo) {
if(otherTile==unit.getTile()) return // already here! if(destination==unit.getTile()) return // already here!
class CantEnterThisTileException(msg: String) : Exception(msg) class CantEnterThisTileException(msg: String) : Exception(msg)
if(!canMoveTo(otherTile)) if(!canMoveTo(destination))
throw CantEnterThisTileException("$this can't enter $otherTile") throw CantEnterThisTileException("$this can't enter $destination")
if(unit.type.isAirUnit()){ // they move differently from all other units if(unit.type.isAirUnit()){ // they move differently from all other units
unit.action=null unit.action=null
unit.removeFromTile() unit.removeFromTile()
unit.putInTile(otherTile) unit.putInTile(destination)
unit.currentMovement=0f unit.currentMovement=0f
return return
} }
val distanceToTiles = getDistanceToTiles() val distanceToTiles = getDistanceToTiles()
class YouCantGetThereFromHereException(msg: String) : Exception(msg) class YouCantGetThereFromHereException(msg: String) : Exception(msg)
if (!distanceToTiles.containsKey(otherTile)) if (!distanceToTiles.containsKey(destination))
throw YouCantGetThereFromHereException("$unit can't get from ${unit.currentTile.position} to ${otherTile.position}.") throw YouCantGetThereFromHereException("$unit can't get from ${unit.currentTile.position} to ${destination.position}.")
if(otherTile.isCityCenter() && otherTile.getOwner()!=unit.civInfo) if(destination.isCityCenter() && destination.getOwner()!=unit.civInfo)
throw Exception("This is an enemy city, you can't go here!") throw Exception("This is an enemy city, you can't go here!")
unit.currentMovement -= distanceToTiles[otherTile]!! unit.currentMovement -= distanceToTiles[destination]!!.totalDistance
if (unit.currentMovement < 0.1) unit.currentMovement = 0f // silly floats which are "almost zero" if (unit.currentMovement < 0.1) unit.currentMovement = 0f // silly floats which are "almost zero"
if(unit.isFortified() || unit.action=="Set Up" || unit.action=="Sleep") if(unit.isFortified() || unit.action=="Set Up" || unit.action=="Sleep")
unit.action=null // unfortify/setup after moving unit.action=null // unfortify/setup after moving
val pathToFinalTile = distanceToTiles.getPathToTile(destination)
for(tile in pathToFinalTile){
unit.removeFromTile() unit.removeFromTile()
unit.putInTile(otherTile) unit.putInTile(tile)
}
} }
@ -350,3 +337,16 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
return pathsToCities return pathsToCities
} }
} }
class PathsToTilesWithinTurn : LinkedHashMap<TileInfo, UnitMovementAlgorithms.ParentTileAndTotalDistance>(){
fun getPathToTile(tile: TileInfo): List<TileInfo> {
if(!containsKey(tile)) throw Exception("Can't reach this tile!")
val reversePathList = ArrayList<TileInfo>()
var currentTile = tile
while(get(currentTile)!!.parentTile!=currentTile){
reversePathList.add(currentTile)
currentTile = get(currentTile)!!.parentTile
}
return reversePathList.reversed()
}
}