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"
minSdkVersion 14
targetSdkVersion 28
versionCode 274
versionName "2.18.3"
versionCode 275
versionName "2.18.4"
}
// 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.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.PathsToTilesWithinTurn
import com.unciv.logic.map.TileInfo
import com.unciv.models.gamebasics.GameBasics
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) }
if(unitDistanceToTiles.isEmpty()) return true // can't move, so...
val unitTile = unit.getTile()
@ -145,13 +146,14 @@ class UnitAutomation{
return true
}
fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: HashMap<TileInfo, Float>) : Boolean {
fun tryPillageImprovement(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn) : Boolean {
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) }
if (tilesInDistance.isEmpty()) return false
val tileToPillage = tilesInDistance.maxBy { it.getDefensiveBonus() }!!
if (tilesThatCanWalkToAndThenPillage.isEmpty()) return false
val tileToPillage = tilesThatCanWalkToAndThenPillage.maxBy { it.getDefensiveBonus() }!!
if (unit.getTile()!=tileToPillage)
unit.movement.moveToTile(tileToPillage)
@ -189,7 +191,7 @@ class UnitAutomation{
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
.filter { containsAttackableEnemy(it, MapUnitCombatant(unit)) }
@ -208,7 +210,7 @@ class UnitAutomation{
val movementPointsToExpendAfterMovement = if(unitMustBeSetUp) 1 else 0
val movementPointsToExpendHere = if(unitMustBeSetUp && unit.action != "Set Up") 1 else 0
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 }
.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
if(tilesCanAttackFromButNotInBombardRange.any())
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!! }!!)
unit.movement.headTowards(tilesCanAttackFromButNotInBombardRange.minBy { unitDistanceToTiles[it]!!.totalDistance }!!)
}
else {
// 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
}
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
val attackableEnemiesNextTurn = getAttackableEnemies(unit, unitDistanceToTiles)
// Only take enemies we can fight without dying
@ -457,7 +459,7 @@ class UnitAutomation{
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
val tileWithRuin = unitDistanceToTiles.keys.firstOrNull{unit.movement.canMoveTo(it) && it.improvement == Constants.ancientRuins}
if(tileWithRuin==null) return false
@ -465,7 +467,7 @@ class UnitAutomation{
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(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
.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)
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
}
class ParentTileAndTotalDistance(val parentTile:TileInfo, val totalDistance: Float)
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): HashMap<TileInfo, Float> {
if(unitMovement==0f) return hashMapOf()
val distanceToTiles = LinkedHashMap<TileInfo, Float>()
fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn {
if(unitMovement==0f) return PathsToTilesWithinTurn()
val distanceToTiles = PathsToTilesWithinTurn()
val unitTile = unit.getTile().tileMap[origin]
distanceToTiles[unitTile] = 0f
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile,0f)
var tilesToCheck = listOf(unitTile)
while (!tilesToCheck.isEmpty()) {
@ -63,10 +64,10 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
else {
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!
updatedTiles += neighbor
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
// 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)
for (reachableTile in distanceToTilesThisTurn.keys) {
if (reachableTile == destination)
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!
distanceToDestination[tileToCheck] = distanceToTilesThisTurn[reachableTile]!!.totalDistance
else {
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
@ -163,7 +164,7 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
if (reachableDestinationNeighbors.isEmpty()) // We can't get closer...
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
val path = getShortestPath(destination)
@ -182,24 +183,6 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
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(){
var allowedTile:TileInfo? = null
@ -225,35 +208,39 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
}
fun moveToTile(otherTile: TileInfo) {
if(otherTile==unit.getTile()) return // already here!
fun moveToTile(destination: TileInfo) {
if(destination==unit.getTile()) return // already here!
class CantEnterThisTileException(msg: String) : Exception(msg)
if(!canMoveTo(otherTile))
throw CantEnterThisTileException("$this can't enter $otherTile")
if(!canMoveTo(destination))
throw CantEnterThisTileException("$this can't enter $destination")
if(unit.type.isAirUnit()){ // they move differently from all other units
unit.action=null
unit.removeFromTile()
unit.putInTile(otherTile)
unit.putInTile(destination)
unit.currentMovement=0f
return
}
val distanceToTiles = getDistanceToTiles()
class YouCantGetThereFromHereException(msg: String) : Exception(msg)
if (!distanceToTiles.containsKey(otherTile))
throw YouCantGetThereFromHereException("$unit can't get from ${unit.currentTile.position} to ${otherTile.position}.")
if (!distanceToTiles.containsKey(destination))
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!")
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.isFortified() || unit.action=="Set Up" || unit.action=="Sleep")
unit.action=null // unfortify/setup after moving
unit.removeFromTile()
unit.putInTile(otherTile)
val pathToFinalTile = distanceToTiles.getPathToTile(destination)
for(tile in pathToFinalTile){
unit.removeFromTile()
unit.putInTile(tile)
}
}
@ -349,4 +336,17 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
}
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()
}
}