From f6a989f1fc9c7cd3cfb6ebcf83191cdbfdbc2e9e Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Mon, 21 Mar 2022 20:05:30 +0100 Subject: [PATCH] Empire Overview Diplomacy (#6375) * Empire Overview Diplomacy: Preparations * Empire Overview Diplomacy: DiplomacyScreen linkable * Empire Overview Diplomacy: Overhaul * Empire Overview Diplomacy: List always begins a new row between major and minor civs --- core/src/com/unciv/logic/GameInfo.kt | 33 ++- .../logic/civilization/CivilizationInfo.kt | 24 +- .../unciv/logic/civilization/Notification.kt | 7 +- .../overviewscreen/DiplomacyOverviewTable.kt | 263 +++++++++++------- .../src/com/unciv/ui/tilegroups/CityButton.kt | 4 +- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 93 ++++--- .../com/unciv/ui/worldscreen/TradePopup.kt | 16 +- 7 files changed, 258 insertions(+), 182 deletions(-) diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index e5492011bb..c532dd1bf2 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -283,15 +283,15 @@ class GameInfo { } ) } - + private fun checkForTimeVictory() { if (turns != gameParameters.maxTurns || !gameParameters.victoryTypes.contains(VictoryType.Time)) return - + val winningCiv = civilizations .filter { it.isMajorCiv() && !it.isSpectator() && !it.isBarbarian() } - .maxByOrNull { it.calculateScoreBreakdown().values.sum() } + .maxByOrNull { it.calculateTotalScore() } ?: return // Are there no civs left? - + winningCiv.victoryManager.hasWonTimeVictory = true } @@ -322,19 +322,18 @@ class GameInfo { // Calling with `maxDistance = 0` removes distance limitation. data class CityTileAndDistance(val city: CityInfo, val tile: TileInfo, val distance: Int) - var exploredRevealTiles:Sequence - - if (ruleSet.tileResources[resourceName]!!.hasUnique(UniqueType.CityStateOnlyResource)) { - // Look for matching mercantile CS centers - exploredRevealTiles = getAliveCityStates() - .asSequence() - .filter { it.cityStateResource == resourceName } - .map { it.getCapital().getCenterTile() } - } else { - exploredRevealTiles = tileMap.values - .asSequence() - .filter { it.resource == resourceName } - } + val exploredRevealTiles: Sequence = + if (ruleSet.tileResources[resourceName]!!.hasUnique(UniqueType.CityStateOnlyResource)) { + // Look for matching mercantile CS centers + getAliveCityStates() + .asSequence() + .filter { it.cityStateResource == resourceName } + .map { it.getCapital().getCenterTile() } + } else { + tileMap.values + .asSequence() + .filter { it.resource == resourceName } + } val exploredRevealInfo = exploredRevealTiles .filter { it.position in civInfo.exploredTiles } diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index f4b441dd8b..2a94990033 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -288,6 +288,20 @@ class CivilizationInfo { fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName) fun knows(otherCiv: CivilizationInfo) = knows(otherCiv.civName) + /** A sorted Sequence of all other civs we know (excluding barbarians and spectators) */ + fun getKnownCivsSorted(includeCityStates: Boolean = true, includeDefeated: Boolean = false) = + gameInfo.civilizations.asSequence() + .filterNot { + it == this || + it.isBarbarian() || it.isSpectator() || + !this.knows(it) || + (!includeDefeated && it.isDefeated()) || + (!includeCityStates && it.isCityState()) + } + .sortedWith( + compareByDescending { it.isMajorCiv() } + .thenBy (UncivGame.Current.settings.getCollatorFromLocale()) { it.civName.tr() } + ) fun getCapital() = cities.first { it.isCapital() } fun isPlayerCivilization() = playerType == PlayerType.Human fun isOneCityChallenger() = ( @@ -611,7 +625,7 @@ class CivilizationInfo { fun getStatForRanking(category: RankingType): Int { return when (category) { - RankingType.Score -> calculateScoreBreakdown().values.sum().toInt() + RankingType.Score -> calculateTotalScore().toInt() RankingType.Population -> cities.sumOf { it.population.population } RankingType.Crop_Yield -> statsForNextTurn.food.roundToInt() RankingType.Production -> statsForNextTurn.production.roundToInt() @@ -673,7 +687,7 @@ class CivilizationInfo { var mapSizeModifier = 1276 / gameInfo.tileMap.mapParameters.numberOfTiles().toDouble() if (mapSizeModifier > 1) mapSizeModifier = (mapSizeModifier - 1) / 3 + 1 - + scoreBreakdown["Cities"] = cities.count() * 10 * mapSizeModifier scoreBreakdown["Population"] = cities.sumOf { it.population.population } * 3 * mapSizeModifier scoreBreakdown["Tiles"] = cities.sumOf { city -> city.getTiles().filter { !it.isWater}.count() } * 1 * mapSizeModifier @@ -683,10 +697,12 @@ class CivilizationInfo { }.toDouble() scoreBreakdown["Techs"] = tech.getNumberOfTechsResearched() * 4.toDouble() scoreBreakdown["Future Tech"] = tech.repeatingTechsResearched * 10.toDouble() - + return scoreBreakdown } - + + fun calculateTotalScore() = calculateScoreBreakdown().values.sum() + //endregion //region state-changing functions diff --git a/core/src/com/unciv/logic/civilization/Notification.kt b/core/src/com/unciv/logic/civilization/Notification.kt index eacb69b93a..5db2bdf40a 100644 --- a/core/src/com/unciv/logic/civilization/Notification.kt +++ b/core/src/com/unciv/logic/civilization/Notification.kt @@ -95,14 +95,13 @@ data class CityAction(val city: Vector2 = Vector2.Zero): NotificationAction { /** enter diplomacy screen */ data class DiplomacyAction(val otherCivName: String = ""): NotificationAction { override fun execute(worldScreen: WorldScreen) { - val screen = DiplomacyScreen(worldScreen.viewingCiv) - screen.updateRightSide(worldScreen.gameInfo.getCivilization(otherCivName)) - worldScreen.game.setScreen(screen) + val otherCiv = worldScreen.gameInfo.getCivilization(otherCivName) + worldScreen.game.setScreen(DiplomacyScreen(worldScreen.viewingCiv, otherCiv)) } } /** enter Maya Long Count popup */ -class MayaLongCountAction() : NotificationAction { +class MayaLongCountAction : NotificationAction { override fun execute(worldScreen: WorldScreen) { MayaCalendar.openPopup(worldScreen, worldScreen.selectedCiv, worldScreen.gameInfo.getYear()) } diff --git a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt index 6aec520585..5ff37ea9bf 100644 --- a/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt +++ b/core/src/com/unciv/ui/overviewscreen/DiplomacyOverviewTable.kt @@ -5,12 +5,17 @@ import com.badlogic.gdx.scenes.scene2d.Actor import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton +import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup +import com.badlogic.gdx.utils.Align import com.unciv.UncivGame import com.unciv.logic.HexMath import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.utils.* +import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip +import kotlin.math.roundToInt class DiplomacyOverviewTab ( viewingPlayer: CivilizationInfo, @@ -24,41 +29,91 @@ class DiplomacyOverviewTab ( } override val persistableData = (persistedData as? DiplomacyTabPersistableData) ?: DiplomacyTabPersistableData() + // Widgets that are kept between updates + private val fixedContent = Table() + private val civTable = Table().apply { + defaults().pad(5f) + background = ImageGetter.getBackground(Color.BLACK) + } + val toggleCityStatesButton: TextButton = "City-States".toTextButton().apply { + onClick { + persistableData.includeCityStates = !persistableData.includeCityStates + update() + } + } + private val civTableScroll = AutoScrollPane(civTable).apply { + setOverscroll(false, false) + } + private val floatingTable = Table().apply { + add(toggleCityStatesButton).row() + add(civTableScroll.addBorder(2f, Color.WHITE)).pad(10f) + } + + // Reusable sequences for the Civilizations to display + private var undefeatedCivs = sequenceOf() + private var defeatedCivs = sequenceOf() + + private var relevantCivsCount = 0 // includes unknown civs + private var showDiplomacyGroup = false + private var portraitMode = false + init { update() } - fun update() { - clear() - val relevantCivs = gameInfo.civilizations - .filter { !it.isBarbarian() && !it.isSpectator() && (persistableData.includeCityStates || !it.isCityState()) } - val diplomacyGroup = DiplomacyGroup(viewingPlayer, overviewScreen.centerAreaHeight, persistableData.includeCityStates) - val playerKnowsAndUndefeatedCivs = relevantCivs.filter { diplomacyGroup.playerKnows(it) && !it.isDefeated() } - val playerKnowsAndDefeatedCivs = relevantCivs.filter { diplomacyGroup.playerKnows(it) && it.isDefeated() } - if (playerKnowsAndUndefeatedCivs.size > 1) - add(diplomacyGroup).top() - - val titleTable = Table() - titleTable.add("Our Civilization:".toLabel()).colspan(2).row() - titleTable.add(ImageGetter.getNationIndicator(viewingPlayer.nation, 25f)).pad(5f) - titleTable.add(viewingPlayer.civName.toLabel()).left().padRight(10f) - titleTable.add(viewingPlayer.calculateScoreBreakdown().values.sum().toInt().toLabel()).row() - - val civTableScrollPane = getCivTableScroll(relevantCivs, titleTable, playerKnowsAndUndefeatedCivs, playerKnowsAndDefeatedCivs) - - val toggleCityStatesButton = "City-States".toTextButton() - toggleCityStatesButton.color = if (persistableData.includeCityStates) Color.RED else Color.GREEN - toggleCityStatesButton.onClick { - persistableData.includeCityStates = !persistableData.includeCityStates - update() - } - - val floatingTable = Table() - floatingTable.add(toggleCityStatesButton).row() - floatingTable.add(civTableScrollPane.addBorder(2f, Color.WHITE)).pad(10f) - add(floatingTable) + override fun getFixedContent(): WidgetGroup { + return fixedContent } + // Refresh content and determine landscape/portrait layout + private fun update() { + relevantCivsCount = gameInfo.civilizations.count { + !it.isSpectator() && !it.isBarbarian() && (persistableData.includeCityStates || !it.isCityState()) + } + undefeatedCivs = sequenceOf(viewingPlayer) + + viewingPlayer.getKnownCivsSorted(persistableData.includeCityStates) + defeatedCivs = viewingPlayer.getKnownCivsSorted(persistableData.includeCityStates, true) + .filter { it.isDefeated() } + + clear() + fixedContent.clear() + + showDiplomacyGroup = undefeatedCivs.any { it != viewingPlayer } + updateCivTable(2) + portraitMode = !showDiplomacyGroup || + civTable.minWidth > overviewScreen.stage.width / 2 || + overviewScreen.isPortrait() + val table = if (portraitMode) this else fixedContent + + if (showDiplomacyGroup) { + val diplomacySize = (overviewScreen.stage.width - (if (portraitMode) 0f else civTable.minWidth)) + .coerceAtMost(overviewScreen.centerAreaHeight) + val diplomacyGroup = DiplomacyGroup(undefeatedCivs, diplomacySize) + table.add(diplomacyGroup).top() + } + + if (portraitMode) { + if (showDiplomacyGroup) table.row() + val columns = 2 * (overviewScreen.stage.width / civTable.minWidth).roundToInt() + if (columns > 2) { + updateCivTable(columns) + if (civTable.minWidth > overviewScreen.stage.width) + updateCivTable(columns - 2) + } + } + + table.add(floatingTable) + toggleCityStatesButton.color = if (persistableData.includeCityStates) Color.RED else Color.GREEN + civTableScroll.setScrollingDisabled(portraitMode, portraitMode) + } + + private fun updateCivTable(columns: Int) = civTable.apply { + clear() + addTitleInfo(columns) + addCivsCategory(columns, "alive", undefeatedCivs.filter { it != viewingPlayer }) + addCivsCategory(columns, "defeated", defeatedCivs) + layout() + } private fun getCivMiniTable(civInfo: CivilizationInfo): Table { val table = Table() @@ -67,62 +122,56 @@ class DiplomacyOverviewTab ( table.touchable = Touchable.enabled table.onClick { if (civInfo.isDefeated() || viewingPlayer.isSpectator() || civInfo == viewingPlayer) return@onClick - UncivGame.Current.setScreen(DiplomacyScreen(viewingPlayer).apply { updateRightSide(civInfo) }) + overviewScreen.dispose() + UncivGame.Current.setScreen(DiplomacyScreen(viewingPlayer, civInfo)) } return table } - private fun getCivTableScroll(relevantCivs: List, titleTable: Table, - playerKnowsAndUndefeatedCivs: List, - playerKnowsAndDefeatedCivs: List): AutoScrollPane { - val civTable = Table() - civTable.defaults().pad(5f) - civTable.background = ImageGetter.getBackground(Color.BLACK) - civTable.add("[${relevantCivs.size}] Civilizations in the game".toLabel()).pad(5f).colspan(2).row() - civTable.add(titleTable).colspan(2).row() - val turnsTillNextDiplomaticVote = viewingPlayer.getTurnsTillNextDiplomaticVote() - if (turnsTillNextDiplomaticVote != null) - civTable.add("Turns until the next\ndiplomacy victory vote: [$turnsTillNextDiplomaticVote]".toLabel()).center().pad(5f).colspan(2).row() - civTable.addSeparator() - civTable.add("Known and alive ([${playerKnowsAndUndefeatedCivs.size - 1}])".toLabel()) - .pad(5f).colspan(2).row() - if (playerKnowsAndUndefeatedCivs.size > 1) { - civTable.addSeparator() - var cityStatesParsed = 0 - playerKnowsAndUndefeatedCivs.filter { it != viewingPlayer }.forEach { - civTable.add(getCivMiniTable(it)).left() - if (it.isCityState()) { - cityStatesParsed++ - } else { - civTable.add(it.calculateScoreBreakdown().values.sum().toInt().toLabel()).left() - } - if (!it.isCityState() || cityStatesParsed % 2 == 0) - civTable.row() - } - } - civTable.addSeparator() - civTable.add("Known and defeated ([${playerKnowsAndDefeatedCivs.size}])".toLabel()) - .pad(5f).colspan(2).row() - if (playerKnowsAndDefeatedCivs.isNotEmpty()) { - civTable.addSeparator() - var cityStatesParsed = 0 - playerKnowsAndDefeatedCivs.forEach { - civTable.add(getCivMiniTable(it)).left() - if (it.isCityState()) { - cityStatesParsed++ - } else { - civTable.add(it.calculateScoreBreakdown().values.sum().toInt().toLabel()).left() - } - if (!it.isCityState() || cityStatesParsed % 2 == 0) - civTable.row() - } - } - val civTableScrollPane = AutoScrollPane(civTable) - civTableScrollPane.setOverscroll(false, false) - return civTableScrollPane + private fun Table.addTitleInfo(columns: Int) { + add("[$relevantCivsCount] Civilizations in the game".toLabel()).colspan(columns).row() + add("Our Civilization:".toLabel()).colspan(columns).left().padLeft(10f).padTop(10f).row() + add(getCivMiniTable(viewingPlayer)).left() + add(viewingPlayer.calculateTotalScore().toInt().toLabel()).left().row() + val turnsTillNextDiplomaticVote = viewingPlayer.getTurnsTillNextDiplomaticVote() ?: return + add("Turns until the next\ndiplomacy victory vote: [$turnsTillNextDiplomaticVote]".toLabel()).colspan(columns).row() } - private class DiplomacyGroup(val viewingPlayer: CivilizationInfo, freeHeight: Float, includeCityStates: Boolean): Group() { + private fun Table.addCivsCategory(columns: Int, aliveOrDefeated: String, civs: Sequence) { + addSeparator() + val count = civs.count() + add("Known and $aliveOrDefeated ([$count])".toLabel()) + .pad(5f).colspan(columns).row() + if (count == 0) return + addSeparator() + var currentColumn = 0 + var lastCivWasMajor = false + fun advanceCols(delta: Int) { + currentColumn += delta + if (currentColumn >= columns) { + row() + currentColumn = 0 + } + lastCivWasMajor = delta == 2 + } + for (civ in civs) { + if (lastCivWasMajor && civ.isCityState()) + advanceCols(columns) + add(getCivMiniTable(civ)).left() + if (civ.isCityState()) { + advanceCols(1) + } else { + add(civ.calculateTotalScore().toInt().toLabel()).left() + advanceCols(2) + } + } + } + + /** This is the 'spider net'-like polygon showing one line per civ-civ relation */ + private class DiplomacyGroup( + undefeatedCivs: Sequence, + freeSize: Float + ): Group() { private fun onCivClicked(civLines: HashMap>, name: String) { // ignore the clicks on "dead" civilizations, and remember the selected one val selectedLines = civLines[name] ?: return @@ -146,61 +195,59 @@ class DiplomacyOverviewTab ( } } - if (selectedLines.first().isVisible) - // invert visibility of all lines except selected one - civLines.filter { it.key != name }.forEach { it.value.forEach { line -> line.isVisible = !line.isVisible } } - else - // it happens only when all are visible except selected one - // invert visibility of the selected civ's lines + if (selectedLines.first().isVisible) { + // invert visibility of all lines except selected one + civLines.filter { it.key != name } + .forEach { it.value.forEach { line -> line.isVisible = !line.isVisible } } + } else { + // it happens only when all are visible except selected one + // invert visibility of the selected civ's lines selectedLines.forEach { it.isVisible = !it.isVisible } + } } - - fun playerKnows(civ: CivilizationInfo) = civ == viewingPlayer || - viewingPlayer.diplomacy.containsKey(civ.civName) - init { - val relevantCivs = viewingPlayer.gameInfo.civilizations.filter { !it.isBarbarian() && (includeCityStates || !it.isCityState()) } - val playerKnowsAndUndefeatedCivs = relevantCivs.filter { playerKnows(it) && !it.isDefeated() } - setSize(freeHeight, freeHeight) + setSize(freeSize, freeSize) val civGroups = HashMap() val civLines = HashMap>() - for (i in 0..playerKnowsAndUndefeatedCivs.lastIndex) { - val civ = playerKnowsAndUndefeatedCivs[i] + val civCount = undefeatedCivs.count() + for ((i, civ) in undefeatedCivs.withIndex()) { val civGroup = ImageGetter.getNationIndicator(civ.nation, 30f) - val vector = HexMath.getVectorForAngle(2 * Math.PI.toFloat() * i / playerKnowsAndUndefeatedCivs.size) + val vector = HexMath.getVectorForAngle(2 * Math.PI.toFloat() * i / civCount) civGroup.center(this) - civGroup.moveBy(vector.x * freeHeight / 2.25f, vector.y * freeHeight / 2.25f) + civGroup.moveBy(vector.x * freeSize / 2.25f, vector.y * freeSize / 2.25f) civGroup.touchable = Touchable.enabled civGroup.onClick { onCivClicked(civLines, civ.civName) } + civGroup.addTooltip(civ.civName, tipAlign = Align.bottomLeft) civGroups[civ.civName] = civGroup addActor(civGroup) } - for (civ in relevantCivs.filter { playerKnows(it) && !it.isDefeated() }) - for (diplomacy in civ.diplomacy.values.filter { - (it.otherCiv().isMajorCiv() || includeCityStates) && playerKnows(it.otherCiv()) && !it.otherCiv().isDefeated() - }) { + for (civ in undefeatedCivs) + for (diplomacy in civ.diplomacy.values) { + if (diplomacy.otherCiv() !in undefeatedCivs) continue val civGroup = civGroups[civ.civName]!! val otherCivGroup = civGroups[diplomacy.otherCivName]!! - if (!civLines.containsKey(civ.civName)) - civLines[civ.civName] = mutableSetOf() + val statusLine = ImageGetter.getLine( + startX = civGroup.x + civGroup.width / 2, + startY = civGroup.y + civGroup.height / 2, + endX = otherCivGroup.x + otherCivGroup.width / 2, + endY = otherCivGroup.y + otherCivGroup.height / 2, + width = 2f) - val statusLine = ImageGetter.getLine(civGroup.x + civGroup.width / 2, civGroup.y + civGroup.height / 2, - otherCivGroup.x + otherCivGroup.width / 2, otherCivGroup.y + otherCivGroup.height / 2, 2f) - - statusLine.color = if (diplomacy.diplomaticStatus == DiplomaticStatus.War) Color.RED else Color.GREEN + statusLine.color = if (diplomacy.diplomaticStatus == DiplomaticStatus.War) Color.RED + else diplomacy.relationshipLevel().color + if (!civLines.containsKey(civ.civName)) civLines[civ.civName] = mutableSetOf() civLines[civ.civName]!!.add(statusLine) - addActor(statusLine) - statusLine.toBack() + addActorAt(0, statusLine) } } } diff --git a/core/src/com/unciv/ui/tilegroups/CityButton.kt b/core/src/com/unciv/ui/tilegroups/CityButton.kt index 6e4d8a682b..ddbf3aa3a7 100644 --- a/core/src/com/unciv/ui/tilegroups/CityButton.kt +++ b/core/src/com/unciv/ui/tilegroups/CityButton.kt @@ -415,9 +415,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab private fun foreignCityInfoPopup() { fun openDiplomacy() { // If city doesn't belong to you, go directly to its owner's diplomacy screen. - val screen = DiplomacyScreen(worldScreen.viewingCiv) - screen.updateRightSide(city.civInfo) - worldScreen.game.setScreen(screen) + worldScreen.game.setScreen(DiplomacyScreen(worldScreen.viewingCiv, city.civInfo)) } // If there's nothing to display cuz no Religion - skip popup diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 1238c66fec..5272e2ae51 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -10,14 +10,15 @@ import com.unciv.UncivGame import com.unciv.logic.civilization.* import com.unciv.logic.civilization.diplomacy.* import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* +import com.unciv.logic.trade.Trade import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Quest +import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.stats.Stat import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.tr @@ -27,57 +28,77 @@ import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.tilegroups.CityButton import com.unciv.ui.utils.* import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import kotlin.collections.ArrayList import kotlin.math.floor import kotlin.math.roundToInt import com.unciv.ui.utils.AutoScrollPane as ScrollPane -class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { +/** + * Creates the diplomacy screen for [viewingCiv]. + * + * When [selectCiv] is given and [selectTrade] is not, that Civilization is selected as if clicked on the left side. + * When [selectCiv] and [selectTrade] are supplied, that Trade for that Civilization is selected, used for the counter-offer option from `TradePopup`. + */ +@Suppress("KDocUnresolvedReference") // Mentioning non-field parameters is flagged, but they work anyway +class DiplomacyScreen( + val viewingCiv: CivilizationInfo, + selectCiv: CivilizationInfo? = null, + selectTrade: Trade? = null +): BaseScreen() { + companion object { + private const val nationIconSize = 100f + private const val nationIconPad = 10f + } - private val leftSideTable = Table().apply { defaults().pad(10f) } + private val leftSideTable = Table().apply { defaults().pad(nationIconPad) } + private val leftSideScroll = ScrollPane(leftSideTable) private val rightSideTable = Table() + private val closeButton = Constants.close.toTextButton() private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen.canChangeState init { onBackButtonClicked { UncivGame.Current.setWorldScreen() } - val splitPane = SplitPane(ScrollPane(leftSideTable), rightSideTable, false, skin) + val splitPane = SplitPane(leftSideScroll, rightSideTable, false, skin) splitPane.splitAmount = 0.2f - updateLeftSideTable() + updateLeftSideTable(selectCiv) splitPane.setFillParent(true) stage.addActor(splitPane) - - val closeButton = Constants.close.toTextButton() closeButton.onClick { UncivGame.Current.setWorldScreen() } closeButton.label.setFontSize(Constants.headingFontSize) closeButton.labelCell.pad(10f) closeButton.pack() - closeButton.y = stage.height - closeButton.height - 10 - closeButton.x = - (stage.width * 0.2f - closeButton.width) / 2 // center, leftSideTable.width not known yet + positionCloseButton() stage.addActor(closeButton) // This must come after the split pane so it will be above, that the button will be clickable + + if (selectCiv != null) { + if (selectTrade != null) { + val tradeTable = setTrade(selectCiv) + tradeTable.tradeLogic.currentTrade.set(selectTrade) + tradeTable.offerColumnsTable.update() + } else + updateRightSide(selectCiv) + } } - private fun updateLeftSideTable() { + private fun positionCloseButton() { + closeButton.setPosition(stage.width * 0.1f, stage.height - 10f, Align.top) + } + + private fun updateLeftSideTable(selectCiv: CivilizationInfo?) { leftSideTable.clear() leftSideTable.add().padBottom(60f).row() // room so the close button does not cover the first - val civsToDisplay = viewingCiv.gameInfo.civilizations.asSequence() - .filterNot { - it.isDefeated() || it == viewingCiv || it.isBarbarian() || it.isSpectator() || - !viewingCiv.knows(it) + var selectCivY = 0f + + for (civ in viewingCiv.getKnownCivsSorted()) { + if (civ == selectCiv) { + selectCivY = leftSideTable.prefHeight } - .sortedWith( - compareByDescending{ it.isMajorCiv() } - .thenBy (UncivGame.Current.settings.getCollatorFromLocale(), { it.civName.tr() }) - ) - for (civ in civsToDisplay) { - - val civIndicator = ImageGetter.getNationIndicator(civ.nation, 100f) + val civIndicator = ImageGetter.getNationIndicator(civ.nation, nationIconSize) val relationLevel = civ.getDiplomacyManager(viewingCiv).relationshipLevel() val relationshipIcon = if (civ.isCityState() && relationLevel == RelationshipLevel.Ally) @@ -114,9 +135,15 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { civIndicator.onClick { updateRightSide(civ) } } + + if (selectCivY != 0f) { + leftSideScroll.layout() + leftSideScroll.scrollY = selectCivY + (nationIconSize + 2 * nationIconPad - stage.height) / 2 + leftSideScroll.updateVisualScroll() + } } - fun updateRightSide(otherCiv: CivilizationInfo) { + private fun updateRightSide(otherCiv: CivilizationInfo) { rightSideTable.clear() if (otherCiv.isCityState()) rightSideTable.add( ScrollPane(getCityStateDiplomacyTable(otherCiv)) @@ -125,7 +152,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { .height(stage.height) } - fun setTrade(civ: CivilizationInfo): TradeTable { + private fun setTrade(civ: CivilizationInfo): TradeTable { rightSideTable.clear() val tradeTable = TradeTable(civ, this) rightSideTable.add(tradeTable) @@ -303,7 +330,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { revokeProtectionButton.onClick { YesNoPopup("Revoke protection for [${otherCiv.civName}]?", { otherCiv.removeProtectorCiv(viewingCiv) - updateLeftSideTable() + updateLeftSideTable(otherCiv) updateRightSide(otherCiv) }, this).open() } @@ -314,7 +341,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { protectionButton.onClick { YesNoPopup("Declare Protection of [${otherCiv.civName}]?", { otherCiv.addProtectorCiv(viewingCiv) - updateLeftSideTable() + updateLeftSideTable(otherCiv) updateRightSide(otherCiv) }, this).open() } @@ -350,7 +377,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { ) ) tradeLogic.acceptTrade() - updateLeftSideTable() + updateLeftSideTable(otherCiv) updateRightSide(otherCiv) }, this).open() } @@ -443,7 +470,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { "Gift [$giftAmount] gold (+[$influenceAmount] influence)".toTextButton() giftButton.onClick { otherCiv.receiveGoldGift(viewingCiv, giftAmount) - updateLeftSideTable() + updateLeftSideTable(otherCiv) updateRightSide(otherCiv) } diplomacyTable.add(giftButton).row() @@ -694,7 +721,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { denounceButton.onClick { YesNoPopup("Denounce [${otherCiv.civName}]?", { diplomacyManager.denounce() - updateLeftSideTable() + updateLeftSideTable(otherCiv) setRightSideFlavorText(otherCiv, "We will remember this.", "Very well.") }, this).open() } @@ -864,7 +891,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { YesNoPopup("Declare war on [${otherCiv.civName}]?", { diplomacyManager.declareWar() setRightSideFlavorText(otherCiv, otherCiv.nation.attacked, "Very well.") - updateLeftSideTable() + updateLeftSideTable(otherCiv) UncivGame.Current.musicController.chooseTrack(otherCiv.civName, MusicMood.War, MusicTrackChooserFlags.setSpecific) }, this).open() } @@ -897,4 +924,8 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() { rightSideTable.add(diplomacyTable) } + override fun resize(width: Int, height: Int) { + super.resize(width, height) + positionCloseButton() + } } diff --git a/core/src/com/unciv/ui/worldscreen/TradePopup.kt b/core/src/com/unciv/ui/worldscreen/TradePopup.kt index 7d94489a17..5cea4a183d 100644 --- a/core/src/com/unciv/ui/worldscreen/TradePopup.kt +++ b/core/src/com/unciv/ui/worldscreen/TradePopup.kt @@ -2,17 +2,11 @@ package com.unciv.ui.worldscreen import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.unciv.Constants -import com.unciv.UncivGame import com.unciv.logic.civilization.NotificationIcon -import com.unciv.logic.civilization.diplomacy.DiplomacyFlags -import com.unciv.logic.trade.TradeEvaluation import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType import com.unciv.models.translations.tr -import com.unciv.ui.audio.MusicMood -import com.unciv.ui.audio.MusicTrackChooserFlags import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.trade.LeaderIntroTable import com.unciv.ui.utils.* @@ -90,23 +84,15 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){ } addButton("Not this time.", 'n') { - tradeRequest.decline(viewingCiv) - close() requestingCiv.addNotification("[${viewingCiv.civName}] has denied your trade request", viewingCiv.civName, NotificationIcon.Trade) - worldScreen.shouldUpdate = true } addButton("How about something else...", 'e') { close() - - val diplomacyScreen = DiplomacyScreen(viewingCiv) - val tradeTable = diplomacyScreen.setTrade(requestingCiv) - tradeTable.tradeLogic.currentTrade.set(trade) - tradeTable.offerColumnsTable.update() - worldScreen.game.setScreen(diplomacyScreen) + worldScreen.game.setScreen(DiplomacyScreen(viewingCiv, requestingCiv, trade)) worldScreen.shouldUpdate = true } }