From 892b54f6518a15f0b23794797a93f116700336f9 Mon Sep 17 00:00:00 2001 From: Oskar Niesen Date: Tue, 9 Apr 2024 15:11:12 -0500 Subject: [PATCH] Civs with a spy set up in a foreign city can view some information (#11396) * Civs with a spy set up can view some foreign city information * Spying Civs can only see city tiles that they are aware of * Refactored EspionageManager getCitiesWithOurSpies Co-authored-by: Yair Morgenstern * Removed extra parenthesis --------- Co-authored-by: Yair Morgenstern --- .../logic/civilization/NotificationActions.kt | 2 +- .../civilization/managers/EspionageManager.kt | 6 +++++ .../ui/components/tilegroups/CityButton.kt | 3 ++- .../unciv/ui/screens/cityscreen/CityScreen.kt | 26 ++++++++++++++----- .../cityscreen/CityScreenCityPickerTable.kt | 4 +-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/core/src/com/unciv/logic/civilization/NotificationActions.kt b/core/src/com/unciv/logic/civilization/NotificationActions.kt index 9bb11b2790..4220ddacd2 100644 --- a/core/src/com/unciv/logic/civilization/NotificationActions.kt +++ b/core/src/com/unciv/logic/civilization/NotificationActions.kt @@ -96,7 +96,7 @@ class DiplomacyAction( if (showTrade && currentCiv.isAtWarWith(otherCiv)) showTrade = false // Can't trade right now - + worldScreen.game.pushScreen(DiplomacyScreen(currentCiv, otherCiv, showTrade = showTrade)) } } diff --git a/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt b/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt index 046182a78b..7d6c5b540d 100644 --- a/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt @@ -70,5 +70,11 @@ class EspionageManager : IsPartOfGameInfoSerialization { return spyList.filter { it.getLocation() == city }.toMutableList() } + /** + * Returns a list of all cities with our spies in them. + * The list needs to be stable accross calls on the same turn. + */ + fun getCitiesWithOurSpies(): List = spyList.filter { it.isSetUp() }.mapNotNull { it.getLocation() } + fun getSpyAssignedToCity(city: City): Spy? = spyList.firstOrNull {it.getLocation() == city} } diff --git a/core/src/com/unciv/ui/components/tilegroups/CityButton.kt b/core/src/com/unciv/ui/components/tilegroups/CityButton.kt index dee01430a6..2f42dd97ae 100644 --- a/core/src/com/unciv/ui/components/tilegroups/CityButton.kt +++ b/core/src/com/unciv/ui/components/tilegroups/CityButton.kt @@ -531,7 +531,8 @@ class CityButton(val city: City, private val tileGroup: TileGroup) : Table(BaseS // second tap on the button will go to the city screen // if this city belongs to you and you are not iterating though the air units if (DebugUtils.VISIBLE_MAP || viewingPlayer.isSpectator() - || (belongsToViewingCiv() && !tileGroup.tile.airUnits.contains(unitTable.selectedUnit))) { + || belongsToViewingCiv() && !tileGroup.tile.airUnits.contains(unitTable.selectedUnit) + || city.civ.gameInfo.isEspionageEnabled() && viewingPlayer.espionageManager.getSpyAssignedToCity(city)?.isSetUp() == true) { GUI.pushScreen(CityScreen(city)) } else if (viewingPlayer.knows(city.civ)) { foreignCityInfoPopup() diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt index 311c62079f..24760b170d 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityScreen.kt @@ -8,6 +8,7 @@ import com.unciv.GUI import com.unciv.UncivGame import com.unciv.logic.automation.Automation import com.unciv.logic.city.City +import com.unciv.logic.civilization.Civilization import com.unciv.logic.map.tile.Tile import com.unciv.models.TutorialTrigger import com.unciv.models.UncivSound @@ -60,8 +61,19 @@ class CityScreen( const val wltkIconSize = 40f } + private val selectedCiv: Civilization = GUI.getWorldScreen().selectedCiv + + private val isSpying = selectedCiv.gameInfo.isEspionageEnabled() && selectedCiv != city.civ + + /** + * This is the regular civ city list if we are not spying, if we are spying then it is every foreign city that our spies are in + */ + val viewableCities = if (isSpying) selectedCiv.espionageManager.getCitiesWithOurSpies() + .filter { it.civ != GUI.getWorldScreen().selectedCiv } + else city.civ.cities + /** Toggles or adds/removes all state changing buttons */ - val canChangeState = GUI.isAllowedChangeState() + val canChangeState = GUI.isAllowedChangeState() && !isSpying // Clockwise from the top-left @@ -136,6 +148,7 @@ class CityScreen( //stage.setDebugTableUnderMouse(true) stage.addActor(cityStatsTable) + // If we are spying then we shoulden't be able to see their construction screen. constructionsTable.addActorsToStage() stage.addActor(selectedConstructionTable) stage.addActor(tileTable) @@ -151,7 +164,7 @@ class CityScreen( // Recalculate Stats city.cityStats.update() - constructionsTable.isVisible = true + constructionsTable.isVisible = !isSpying constructionsTable.update(selectedConstruction) updateWithoutConstructionAndMap() @@ -313,7 +326,7 @@ class CityScreen( private fun addTiles() { val tileSetStrings = TileSetStrings() val cityTileGroups = city.getCenterTile().getTilesInDistance(5) - .filter { city.civ.hasExplored(it) } + .filter { selectedCiv.hasExplored(it) } .map { CityTileGroup(city, it, tileSetStrings) } for (tileGroup in cityTileGroups) { @@ -501,12 +514,11 @@ class CityScreen( // Normal order is create new, then dispose old. But CityAmbiencePlayer delegates to a single instance of MusicController, // leading to one extra play followed by a stop for the city ambience sounds. To avoid that, we pass our player on and relinquish control. - val civInfo = city.civ - val numCities = civInfo.cities.size + val numCities = viewableCities.size if (numCities == 0) return - val indexOfCity = civInfo.cities.indexOf(city) + val indexOfCity = viewableCities.indexOf(city) val indexOfNextCity = (indexOfCity + delta + numCities) % numCities - val newCityScreen = CityScreen(civInfo.cities[indexOfNextCity], ambiencePlayer = passOnCityAmbiencePlayer()) + val newCityScreen = CityScreen(viewableCities[indexOfNextCity], ambiencePlayer = passOnCityAmbiencePlayer()) newCityScreen.update() game.replaceCurrentScreen(newCityScreen) } diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityScreenCityPickerTable.kt b/core/src/com/unciv/ui/screens/cityscreen/CityScreenCityPickerTable.kt index 9ad3626c05..e84d6567de 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityScreenCityPickerTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityScreenCityPickerTable.kt @@ -21,7 +21,7 @@ class CityScreenCityPickerTable(private val cityScreen: CityScreen) : Table() { background = BaseScreen.skinStrings.getUiBackground("CityScreen/CityPickerTable", BaseScreen.skinStrings.roundedEdgeRectangleShape, civInfo.nation.getOuterColor()) clear() - if (civInfo.cities.size > 1) { + if (cityScreen.viewableCities.size > 1) { val prevCityButton = Table() // so we get a wider clickable area than just the image itself val image = ImageGetter.getImage("OtherIcons/BackArrow") image.color = civInfo.nation.getInnerColor() @@ -74,7 +74,7 @@ class CityScreenCityPickerTable(private val cityScreen: CityScreen) : Table() { add(cityNameTable).width(stage.width / 4) - if (civInfo.cities.size > 1) { + if (cityScreen.viewableCities.size > 1) { val nextCityButton = Table() // so we gt a wider clickable area than just the image itself val image = ImageGetter.getImage("OtherIcons/BackArrow") image.setSize(25f, 25f)