diff --git a/android/build.gradle b/android/build.gradle index 11e3b34cc8..2766f9fa85 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -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 diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 264a198843..f002fc3cd3 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -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):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) : 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): ArrayList { + fun getAttackableEnemies(unit: MapUnit, unitDistanceToTiles: PathsToTilesWithinTurn): ArrayList { 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): 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): 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): 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) { + 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) diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index f6078f7774..d78c4c927f 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -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 { - if(unitMovement==0f) return hashMapOf() - val distanceToTiles = LinkedHashMap() + 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 { - val currentUnitTile = unit.getTile() - val distanceToTiles = getDistanceToTiles() - val reversedList = ArrayList() - 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(){ + fun getPathToTile(tile: TileInfo): List { + if(!containsKey(tile)) throw Exception("Can't reach this tile!") + val reversePathList = ArrayList() + var currentTile = tile + while(get(currentTile)!!.parentTile!=currentTile){ + reversePathList.add(currentTile) + currentTile = get(currentTile)!!.parentTile + } + return reversePathList.reversed() + } } \ No newline at end of file