mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-10 15:59:33 +07:00
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:
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user