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
This commit is contained in:
Alexander Korolyov 2020-07-31 10:32:44 +02:00 committed by GitHub
parent 275348c70e
commit 86f500b266
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 27 deletions

View file

@ -509,6 +509,7 @@ Turn =
turns = turns =
turn = turn =
Next unit = Next unit =
Fog of War =
Pick a policy = Pick a policy =
Movement = Movement =
Strength = Strength =

View file

@ -251,6 +251,7 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings)
private fun updateTileImage(viewingCiv: CivilizationInfo?) { private fun updateTileImage(viewingCiv: CivilizationInfo?) {
val tileBaseImageLocations = getTileBaseImageLocations(viewingCiv) val tileBaseImageLocations = getTileBaseImageLocations(viewingCiv)
val identifier = tileBaseImageLocations.joinToString(";") val identifier = tileBaseImageLocations.joinToString(";")
if (identifier == tileImagesIdentifier) return if (identifier == tileImagesIdentifier) return
@ -285,11 +286,31 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings)
fun isViewable(viewingCiv: CivilizationInfo) = showEntireMap fun isViewable(viewingCiv: CivilizationInfo) = showEntireMap
|| viewingCiv.viewableTiles.contains(tileInfo) || 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) { 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() hideCircle()
if (viewingCiv != null && !showEntireMap if (viewingCiv != null && !isExplored(viewingCiv)) {
&& !viewingCiv.exploredTiles.contains(tileInfo.position)) { clearUnexploredTiles()
for(image in tileBaseImages) image.color = Color.DARK_GRAY for(image in tileBaseImages) image.color = Color.DARK_GRAY
return return
} }
@ -393,19 +414,24 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings)
} }
} }
var previousTileOwner: CivilizationInfo? = null private fun clearBorders() {
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 (images in borderImages.values)
for (image in images) for (image in images)
image.remove() image.remove()
borderImages.clear() 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) clearBorders()
previousTileOwner = tileOwner previousTileOwner = tileOwner
if (tileOwner == null) return if (tileOwner == null) return

View file

@ -25,14 +25,17 @@ class WorldTileGroup(internal val worldScreen: WorldScreen, tileInfo: TileInfo,
icons.removePopulationIcon() icons.removePopulationIcon()
val tileIsViewable = isViewable(viewingCiv) val tileIsViewable = isViewable(viewingCiv)
val showEntireMap = UncivGame.Current.viewEntireMapForDebug
if (tileIsViewable && tileInfo.isWorked() && UncivGame.Current.settings.showWorkedTiles if (tileIsViewable && tileInfo.isWorked() && UncivGame.Current.settings.showWorkedTiles
&& city!!.civInfo == viewingCiv) && city!!.civInfo == viewingCiv)
icons.addPopulationIcon() icons.addPopulationIcon()
// update city buttons in explored tiles or entire map
val currentPlayerCiv = worldScreen.viewingCiv if (showEntireMap || viewingCiv.exploredTiles.contains(tileInfo.position))
if (UncivGame.Current.viewEntireMapForDebug updateCityButton(city, tileIsViewable || showEntireMap) // needs to be before the update so the units will be above the city button
|| currentPlayerCiv.exploredTiles.contains(tileInfo.position)) else if (worldScreen.viewingCiv.isSpectator())
updateCityButton(city, tileIsViewable || UncivGame.Current.viewEntireMapForDebug) // needs to be before the update so the units will be above the city button // remove city buttons in unexplored tiles during spectating/fog of war
updateCityButton(null, showEntireMap)
super.update(viewingCiv, UncivGame.Current.settings.showResourcesAndImprovements) super.update(viewingCiv, UncivGame.Current.settings.showResourcesAndImprovements)
} }

View file

@ -40,8 +40,10 @@ import kotlin.concurrent.thread
class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() { class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
val gameInfo = game.gameInfo val gameInfo = game.gameInfo
var isPlayersTurn = viewingCiv == gameInfo.currentPlayerCiv // todo this should be updated when passing turns 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() val canChangeState = isPlayersTurn && !viewingCiv.isSpectator()
private var waitingForAutosave = false private var waitingForAutosave = false
@ -56,7 +58,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
private val techPolicyAndVictoryHolder = Table() private val techPolicyAndVictoryHolder = Table()
private val techButtonHolder = Table() private val techButtonHolder = Table()
private val diplomacyButtonWrapper = Table() private val diplomacyButtonHolder = Table()
private val fogOfWarButton = createFogOfWarButton()
private val nextTurnButton = createNextTurnButton() private val nextTurnButton = createNextTurnButton()
private var nextTurnAction:()->Unit= {} private var nextTurnAction:()->Unit= {}
private val tutorialTaskTable = Table().apply { background=ImageGetter.getBackground(ImageGetter.getBlue().lerp(Color.BLACK, 0.5f)) } 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... // An initialized val always turned out to illegally be null...
lateinit var keyPressDispatcher: HashMap<Char,(() -> Unit)> lateinit var keyPressDispatcher: HashMap<Char,(() -> Unit)>
init { init {
topBar.setPosition(0f, stage.height - topBar.height) topBar.setPosition(0f, stage.height - topBar.height)
topBar.width = stage.width topBar.width = stage.width
@ -88,6 +92,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
} }
techPolicyAndVictoryHolder.add(techButtonHolder) techPolicyAndVictoryHolder.add(techButtonHolder)
fogOfWarButton.isVisible = viewingCiv.isSpectator()
fogOfWarButton.setPosition(10f, topBar.y - fogOfWarButton.height - 10f)
// Don't show policies until they become relevant // Don't show policies until they become relevant
if(viewingCiv.policies.adoptedPolicies.isNotEmpty() || viewingCiv.policies.canAdoptPolicy()) { if(viewingCiv.policies.adoptedPolicies.isNotEmpty() || viewingCiv.policies.canAdoptPolicy()) {
val policyScreenButton = Button(skin) val policyScreenButton = Button(skin)
@ -106,8 +113,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
stage.addActor(tutorialTaskTable) stage.addActor(tutorialTaskTable)
diplomacyButtonWrapper.defaults().pad(5f) diplomacyButtonHolder.defaults().pad(5f)
stage.addActor(diplomacyButtonWrapper) stage.addActor(fogOfWarButton)
stage.addActor(diplomacyButtonHolder)
stage.addActor(bottomUnitTable) stage.addActor(bottomUnitTable)
stage.addActor(bottomTileInfoTable) stage.addActor(bottomTileInfoTable)
battleTable.width = stage.width/3 battleTable.width = stage.width/3
@ -284,6 +292,8 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
updateSelectedCiv() updateSelectedCiv()
fogOfWarButton.isEnabled = !selectedCiv.isSpectator()
tutorialTaskTable.clear() tutorialTaskTable.clear()
val tutorialTask = getCurrentTutorialTask() val tutorialTask = getCurrentTutorialTask()
if (tutorialTask == "" || !game.settings.showTutorials || viewingCiv.isDefeated()) { if (tutorialTask == "" || !game.settings.showTutorials || viewingCiv.isDefeated()) {
@ -297,7 +307,9 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
tutorialTaskTable.y = topBar.y - tutorialTaskTable.height tutorialTaskTable.y = topBar.y - tutorialTaskTable.height
} }
minimapWrapper.update(viewingCiv) if (fogOfWar) minimapWrapper.update(selectedCiv)
else minimapWrapper.update(viewingCiv)
cleanupKeyDispatcher() cleanupKeyDispatcher()
unitActionsTable.update(bottomUnitTable.selectedUnit) unitActionsTable.update(bottomUnitTable.selectedUnit)
unitActionsTable.y = bottomUnitTable.height 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 // 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 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) // 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) topBar.update(selectedCiv)
else
topBar.update(viewingCiv)
updateTechButton() updateTechButton()
techPolicyAndVictoryHolder.pack() techPolicyAndVictoryHolder.pack()
techPolicyAndVictoryHolder.setPosition(10f, topBar.y - techPolicyAndVictoryHolder.height - 5f) techPolicyAndVictoryHolder.setPosition(10f, topBar.y - techPolicyAndVictoryHolder.height - 5f)
updateDiplomacyButton(viewingCiv) updateDiplomacyButton(viewingCiv)
if (!hasOpenPopups() && isPlayersTurn) { if (!hasOpenPopups() && isPlayersTurn) {
when { when {
!gameInfo.oneMoreTurnMode && (viewingCiv.isDefeated() || gameInfo.civilizations.any { it.victoryManager.hasWon() }) -> !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) { private fun updateDiplomacyButton(civInfo: CivilizationInfo) {
diplomacyButtonWrapper.clear() diplomacyButtonHolder.clear()
if(!civInfo.isDefeated() && !civInfo.isSpectator() && civInfo.getKnownCivs() if(!civInfo.isDefeated() && !civInfo.isSpectator() && civInfo.getKnownCivs()
.filterNot { it==viewingCiv || it.isBarbarian() } .filterNot { it==viewingCiv || it.isBarbarian() }
.any()) { .any()) {
@ -417,10 +428,10 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
btn.onClick { game.setScreen(DiplomacyScreen(viewingCiv)) } btn.onClick { game.setScreen(DiplomacyScreen(viewingCiv)) }
btn.label.setFontSize(30) btn.label.setFontSize(30)
btn.labelCell.pad(10f) btn.labelCell.pad(10f)
diplomacyButtonWrapper.add(btn) diplomacyButtonHolder.add(btn)
} }
diplomacyButtonWrapper.pack() diplomacyButtonHolder.pack()
diplomacyButtonWrapper.y = techPolicyAndVictoryHolder.y - 20 - diplomacyButtonWrapper.height diplomacyButtonHolder.y = techPolicyAndVictoryHolder.y - 20 - diplomacyButtonHolder.height
} }
private fun updateTechButton() { private fun updateTechButton() {
@ -460,6 +471,19 @@ class WorldScreen(val viewingCiv:CivilizationInfo) : CameraStageBaseScreen() {
else viewingCiv 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 { private fun createNextTurnButton(): TextButton {
val nextTurnButton = TextButton("", skin) // text is set in update() 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.scaleX = mapHolder.scaleX
newWorldScreen.mapHolder.scaleY = mapHolder.scaleY newWorldScreen.mapHolder.scaleY = mapHolder.scaleY
newWorldScreen.mapHolder.updateVisualScroll() newWorldScreen.mapHolder.updateVisualScroll()
newWorldScreen.selectedCiv = gameInfoClone.getCivilization(selectedCiv.civName) newWorldScreen.selectedCiv = gameInfoClone.getCivilization(selectedCiv.civName)
newWorldScreen.fogOfWar = fogOfWar
game.worldScreen = newWorldScreen game.worldScreen = newWorldScreen
game.setWorldScreen() game.setWorldScreen()
} }