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 =
turn =
Next unit =
Fog of War =
Pick a policy =
Movement =
Strength =

View file

@ -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

View file

@ -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)
}

View file

@ -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<Char,(() -> 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()
}