From 86f500b266a2712f9270a5e0ec49ec25da0f5d1b Mon Sep 17 00:00:00 2001 From: Alexander Korolyov <49795502+alkorolyov@users.noreply.github.com> Date: Fri, 31 Jul 2020 10:32:44 +0200 Subject: [PATCH] Fog of war implementation (#2887) * Initial implementation fog of war for spectators * Corrected fog of war for main map * Fix borders seen in unexplored tiles during spectating * Small refactor * Fixes after code review * Fixes after code review * wierd bug fix, android studio "eats" spaces --- .../jsons/translations/template.properties | 1 + core/src/com/unciv/ui/tilegroups/TileGroup.kt | 42 +++++++++++--- .../com/unciv/ui/tilegroups/WorldTileGroup.kt | 13 +++-- .../com/unciv/ui/worldscreen/WorldScreen.kt | 55 ++++++++++++++----- 4 files changed, 84 insertions(+), 27 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 15ff9962..ffd69ab3 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -509,6 +509,7 @@ Turn = turns = turn = Next unit = +Fog of War = Pick a policy = Movement = Strength = diff --git a/core/src/com/unciv/ui/tilegroups/TileGroup.kt b/core/src/com/unciv/ui/tilegroups/TileGroup.kt index c71d8c44..bbe81a5c 100644 --- a/core/src/com/unciv/ui/tilegroups/TileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/TileGroup.kt @@ -251,6 +251,7 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings) private fun updateTileImage(viewingCiv: CivilizationInfo?) { val tileBaseImageLocations = getTileBaseImageLocations(viewingCiv) + val identifier = tileBaseImageLocations.joinToString(";") if (identifier == tileImagesIdentifier) return @@ -285,11 +286,31 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings) fun isViewable(viewingCiv: CivilizationInfo) = showEntireMap || viewingCiv.viewableTiles.contains(tileInfo) + || viewingCiv.isSpectator() + + fun isExplored(viewingCiv: CivilizationInfo) = showEntireMap + || viewingCiv.exploredTiles.contains(tileInfo.position) + || viewingCiv.isSpectator() open fun update(viewingCiv: CivilizationInfo? = null, showResourcesAndImprovements: Boolean = true) { + + fun clearUnexploredTiles() { + updateTileImage(null) + updateRivers(false,false, false) + + updatePixelMilitaryUnit(false) + updatePixelCivilianUnit(false) + + if (borderImages.isNotEmpty()) clearBorders() + + icons.update(false, false, false, null) + + fogImage.isVisible = true + } + hideCircle() - if (viewingCiv != null && !showEntireMap - && !viewingCiv.exploredTiles.contains(tileInfo.position)) { + if (viewingCiv != null && !isExplored(viewingCiv)) { + clearUnexploredTiles() for(image in tileBaseImages) image.color = Color.DARK_GRAY return } @@ -393,19 +414,24 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings) } } + private fun clearBorders() { + for (images in borderImages.values) + for (image in images) + image.remove() + + borderImages.clear() + } + var previousTileOwner: CivilizationInfo? = null private fun updateBorderImages() { // This is longer than it could be, because of performance - // before fixing, about half (!) the time of update() was wasted on // removing all the border images and putting them back again! val tileOwner = tileInfo.getOwner() - if (previousTileOwner != tileOwner) { - for (images in borderImages.values) - for (image in images) - image.remove() - borderImages.clear() - } + + if (previousTileOwner != tileOwner) clearBorders() + previousTileOwner = tileOwner if (tileOwner == null) return diff --git a/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt b/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt index e2bee653..40195db9 100644 --- a/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt +++ b/core/src/com/unciv/ui/tilegroups/WorldTileGroup.kt @@ -25,14 +25,17 @@ class WorldTileGroup(internal val worldScreen: WorldScreen, tileInfo: TileInfo, icons.removePopulationIcon() val tileIsViewable = isViewable(viewingCiv) + val showEntireMap = UncivGame.Current.viewEntireMapForDebug + if (tileIsViewable && tileInfo.isWorked() && UncivGame.Current.settings.showWorkedTiles && city!!.civInfo == viewingCiv) icons.addPopulationIcon() - - val currentPlayerCiv = worldScreen.viewingCiv - if (UncivGame.Current.viewEntireMapForDebug - || currentPlayerCiv.exploredTiles.contains(tileInfo.position)) - updateCityButton(city, tileIsViewable || UncivGame.Current.viewEntireMapForDebug) // needs to be before the update so the units will be above the city button + // update city buttons in explored tiles or entire map + if (showEntireMap || viewingCiv.exploredTiles.contains(tileInfo.position)) + updateCityButton(city, tileIsViewable || showEntireMap) // needs to be before the update so the units will be above the city button + else if (worldScreen.viewingCiv.isSpectator()) + // remove city buttons in unexplored tiles during spectating/fog of war + updateCityButton(null, showEntireMap) super.update(viewingCiv, UncivGame.Current.settings.showResourcesAndImprovements) } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 89840be2..ca793fcd 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -40,8 +40,10 @@ import kotlin.concurrent.thread class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { val gameInfo = game.gameInfo + var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv // todo this should be updated when passing turns - var selectedCiv = viewingCiv // Selected civilization, used only in spectator mode + var selectedCiv = viewingCiv // Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games + var fogOfWar = true val canChangeState = isPlayersTurn && !viewingCiv.isSpectator() private var waitingForAutosave = false @@ -56,7 +58,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { private val techPolicyAndVictoryHolder = Table() private val techButtonHolder = Table() - private val diplomacyButtonWrapper = Table() + private val diplomacyButtonHolder = Table() + private val fogOfWarButton = createFogOfWarButton() private val nextTurnButton = createNextTurnButton() private var nextTurnAction:()->Unit= {} private val tutorialTaskTable = Table().apply { background=ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK, 0.5f)) } @@ -70,6 +73,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { // An initialized val always turned out to illegally be null... lateinit var keyPressDispatcher: HashMap Unit)> + init { topBar.setPosition(0f, stage.height - topBar.height) topBar.width = stage.width @@ -88,6 +92,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { } techPolicyAndVictoryHolder.add(techButtonHolder) + fogOfWarButton.isVisible = viewingCiv.isSpectator() + fogOfWarButton.setPosition(10f, topBar.y - fogOfWarButton.height - 10f) + // Don't show policies until they become relevant if(viewingCiv.policies.adoptedPolicies.isNotEmpty() || viewingCiv.policies.canAdoptPolicy()) { val policyScreenButton = Button(skin) @@ -106,8 +113,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { stage.addActor(tutorialTaskTable) - diplomacyButtonWrapper.defaults().pad(5f) - stage.addActor(diplomacyButtonWrapper) + diplomacyButtonHolder.defaults().pad(5f) + stage.addActor(fogOfWarButton) + stage.addActor(diplomacyButtonHolder) stage.addActor(bottomUnitTable) stage.addActor(bottomTileInfoTable) battleTable.width = stage.width/3 @@ -284,6 +292,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { updateSelectedCiv() + fogOfWarButton.isEnabled = !selectedCiv.isSpectator() + tutorialTaskTable.clear() val tutorialTask = getCurrentTutorialTask() if (tutorialTask == "" || !game.settings.showTutorials || viewingCiv.isDefeated()) { @@ -297,7 +307,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { tutorialTaskTable.y = topBar.y - tutorialTaskTable.height } - minimapWrapper.update(viewingCiv) + if (fogOfWar) minimapWrapper.update(selectedCiv) + else minimapWrapper.update(viewingCiv) + cleanupKeyDispatcher() unitActionsTable.update(bottomUnitTable.selectedUnit) unitActionsTable.y = bottomUnitTable.height @@ -305,18 +317,17 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { // if we use the clone, then when we update viewable tiles // it doesn't update the explored tiles of the civ... need to think about that harder // it causes a bug when we move a unit to an unexplored tile (for instance a cavalry unit which can move far) - mapHolder.updateTiles(viewingCiv) + if (fogOfWar) mapHolder.updateTiles(selectedCiv) + else mapHolder.updateTiles(viewingCiv) - if (viewingCiv.isSpectator()) - topBar.update(selectedCiv) - else - topBar.update(viewingCiv) + topBar.update(selectedCiv) updateTechButton() techPolicyAndVictoryHolder.pack() techPolicyAndVictoryHolder.setPosition(10f, topBar.y - techPolicyAndVictoryHolder.height - 5f) updateDiplomacyButton(viewingCiv) + if (!hasOpenPopups() && isPlayersTurn) { when { !gameInfo.oneMoreTurnMode && (viewingCiv.isDefeated() || gameInfo.civilizations.any { it.victoryManager.hasWon() }) -> @@ -408,7 +419,7 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { } private fun updateDiplomacyButton(civInfo: CivilizationInfo) { - diplomacyButtonWrapper.clear() + diplomacyButtonHolder.clear() if(!civInfo.isDefeated() && !civInfo.isSpectator() && civInfo.getKnownCivs() .filterNot { it==viewingCiv || it.isBarbarian() } .any()) { @@ -417,10 +428,10 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { btn.onClick { game.setScreen(DiplomacyScreen(viewingCiv)) } btn.label.setFontSize(30) btn.labelCell.pad(10f) - diplomacyButtonWrapper.add(btn) + diplomacyButtonHolder.add(btn) } - diplomacyButtonWrapper.pack() - diplomacyButtonWrapper.y = techPolicyAndVictoryHolder.y - 20 - diplomacyButtonWrapper.height + diplomacyButtonHolder.pack() + diplomacyButtonHolder.y = techPolicyAndVictoryHolder.y - 20 - diplomacyButtonHolder.height } private fun updateTechButton() { @@ -460,6 +471,19 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { else viewingCiv } + private fun createFogOfWarButton(): TextButton { + val fogOfWarButton = "Fog of War".toTextButton() + fogOfWarButton.label.setFontSize(30) + fogOfWarButton.labelCell.pad(10f) + fogOfWarButton.pack() + fogOfWarButton.onClick { + fogOfWar = !fogOfWar + shouldUpdate = true + } + return fogOfWarButton + + } + private fun createNextTurnButton(): TextButton { val nextTurnButton = TextButton("", skin) // text is set in update() @@ -520,7 +544,10 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { newWorldScreen.mapHolder.scaleX = mapHolder.scaleX newWorldScreen.mapHolder.scaleY = mapHolder.scaleY newWorldScreen.mapHolder.updateVisualScroll() + newWorldScreen.selectedCiv = gameInfoClone.getCivilization(selectedCiv.civName) + newWorldScreen.fogOfWar = fogOfWar + game.worldScreen = newWorldScreen game.setWorldScreen() }