From 30e5722c24f4b59a74e21db52b2656e0cec774de Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Wed, 27 Jan 2021 17:59:07 +0200 Subject: [PATCH] Added experimental movment which assumes unknown tiles are impassible - hopefully will resolve #3009 --- .../unciv/logic/map/UnitMovementAlgorithms.kt | 56 ++++++++++++++++++- .../com/unciv/models/metadata/GameSettings.kt | 1 + .../unciv/ui/worldscreen/WorldMapHolder.kt | 6 +- .../ui/worldscreen/mainmenu/OptionsPopup.kt | 2 + 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index d58f209659..94f14c98bb 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -2,6 +2,7 @@ package com.unciv.logic.map import com.badlogic.gdx.math.Vector2 import com.unciv.Constants +import com.unciv.UncivGame import com.unciv.logic.civilization.CivilizationInfo class UnitMovementAlgorithms(val unit:MapUnit) { @@ -57,7 +58,12 @@ class UnitMovementAlgorithms(val unit:MapUnit) { class ParentTileAndTotalDistance(val parentTile: TileInfo, val totalDistance: Float) + val MOVEMENT_TESTING = UncivGame.Current.settings.unitMovementIncludesImpassibles + fun isUnknownTileWeShouldAssumeToBePassable(tileInfo: TileInfo) = MOVEMENT_TESTING && !unit.civInfo.exploredTiles.contains(tileInfo.position) + fun getDistanceToTilesWithinTurn(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { + if(MOVEMENT_TESTING) return getDistanceToTilesWithinTurnIncludingUnknownImpassibles(origin, unitMovement) + val distanceToTiles = PathsToTilesWithinTurn() if (unitMovement == 0f) return distanceToTiles @@ -102,6 +108,53 @@ class UnitMovementAlgorithms(val unit:MapUnit) { return distanceToTiles } + fun getDistanceToTilesWithinTurnIncludingUnknownImpassibles(origin: Vector2, unitMovement: Float): PathsToTilesWithinTurn { + val distanceToTiles = PathsToTilesWithinTurn() + if (unitMovement == 0f) return distanceToTiles + + val currentUnitTile = unit.currentTile + // This is for performance, because this is called all the time + val unitTile = if(origin==currentUnitTile.position) currentUnitTile else currentUnitTile.tileMap[origin] + distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile, 0f) + var tilesToCheck = listOf(unitTile) + + while (tilesToCheck.isNotEmpty()) { + val updatedTiles = ArrayList() + for (tileToCheck in tilesToCheck) + for (neighbor in tileToCheck.neighbors) { + var totalDistanceToTile: Float + + if (unit.civInfo.exploredTiles.contains(neighbor.position)) { + if (!canPassThrough(neighbor)) + totalDistanceToTile = unitMovement // Can't go here. + // The reason that we don't just "return" is so that when calculating how to reach an enemy, + // You need to assume his tile is reachable, otherwise all movement algs on reaching enemy + // cities and units goes kaput. + + else { + val distanceBetweenTiles = getMovementCostBetweenAdjacentTiles(tileToCheck, neighbor, unit.civInfo) + totalDistanceToTile = distanceToTiles[tileToCheck]!!.totalDistance + distanceBetweenTiles + } + } else totalDistanceToTile = distanceToTiles[tileToCheck]!!.totalDistance + 1f // If we don't know then we just guess it to be 1. + + 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 + totalDistanceToTile = unitMovement + // 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] = ParentTileAndTotalDistance(tileToCheck, totalDistanceToTile) + } + } + + tilesToCheck = updatedTiles + } + + return distanceToTiles + } + /** Returns an empty list if there's no way to get there */ fun getShortestPath(destination: TileInfo): List { val currentTile = unit.getTile() @@ -127,7 +180,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) { 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 + if (!isUnknownTileWeShouldAssumeToBePassable(reachableTile) && + !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 movementTreeParents[reachableTile] = tileToCheck newTilesToCheck.add(reachableTile) } diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 45337d7cba..486a44f650 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -12,6 +12,7 @@ class GameSettings { var showTileYields: Boolean = false var checkForDueUnits: Boolean = true var singleTapMove: Boolean = false + var unitMovementIncludesImpassibles: Boolean = false var language: String = "English" var resolution: String = "900x600" // Auto-detecting resolution was a BAD IDEA since it needs to be based on DPI AND resolution. var tutorialsShown = HashSet() diff --git a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt index a958c12ff3..c3306467a4 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldMapHolder.kt @@ -112,7 +112,9 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap if (previousSelectedUnits.isNotEmpty() && previousSelectedUnits.any { it.getTile() != tileInfo } && worldScreen.isPlayersTurn - && previousSelectedUnits.any { it.movement.canMoveTo(tileInfo) }) { + && previousSelectedUnits.any { it.movement.canMoveTo(tileInfo) || + it.movement.isUnknownTileWeShouldAssumeToBePassable(tileInfo) + }) { // this can take a long time, because of the unit-to-tile calculation needed, so we put it in a different thread addTileOverlaysWithUnitMovement(previousSelectedUnits, tileInfo) } else addTileOverlays(tileInfo) // no unit movement but display the units in the tile etc. @@ -376,7 +378,7 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap // The tile is within move range tileToColor.showCircle(Color.BLUE, 0.3f) } - if (unit.movement.canMoveTo(tile)) + if (unit.movement.canMoveTo(tile) || unit.movement.isUnknownTileWeShouldAssumeToBePassable(tile)) tileToColor.showCircle(Color.WHITE, if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f) } diff --git a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt index 696db2384c..4ee6bfbe1d 100644 --- a/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/mainmenu/OptionsPopup.kt @@ -108,6 +108,8 @@ class OptionsPopup(val previousScreen:CameraStageBaseScreen) : Popup(previousScr addYesNoRow ("Check for idle units", settings.checkForDueUnits, true) { settings.checkForDueUnits = it } addYesNoRow ("Move units with a single tap", settings.singleTapMove) { settings.singleTapMove = it } + addYesNoRow ("Movement assumes unknown tiles to be passable", settings.unitMovementIncludesImpassibles) + { settings.unitMovementIncludesImpassibles = it } addYesNoRow ("Auto-assign city production", settings.autoAssignCityProduction, true) { settings.autoAssignCityProduction = it if (it && previousScreen is WorldScreen &&