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
This commit is contained in:
SomeTroglodyte 2022-03-21 20:05:30 +01:00 committed by GitHub
parent 984c4d9b2d
commit f6a989f1fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 258 additions and 182 deletions

View File

@ -283,15 +283,15 @@ class GameInfo {
} }
) )
} }
private fun checkForTimeVictory() { private fun checkForTimeVictory() {
if (turns != gameParameters.maxTurns || !gameParameters.victoryTypes.contains(VictoryType.Time)) return if (turns != gameParameters.maxTurns || !gameParameters.victoryTypes.contains(VictoryType.Time)) return
val winningCiv = civilizations val winningCiv = civilizations
.filter { it.isMajorCiv() && !it.isSpectator() && !it.isBarbarian() } .filter { it.isMajorCiv() && !it.isSpectator() && !it.isBarbarian() }
.maxByOrNull { it.calculateScoreBreakdown().values.sum() } .maxByOrNull { it.calculateTotalScore() }
?: return // Are there no civs left? ?: return // Are there no civs left?
winningCiv.victoryManager.hasWonTimeVictory = true winningCiv.victoryManager.hasWonTimeVictory = true
} }
@ -322,19 +322,18 @@ class GameInfo {
// Calling with `maxDistance = 0` removes distance limitation. // Calling with `maxDistance = 0` removes distance limitation.
data class CityTileAndDistance(val city: CityInfo, val tile: TileInfo, val distance: Int) data class CityTileAndDistance(val city: CityInfo, val tile: TileInfo, val distance: Int)
var exploredRevealTiles:Sequence<TileInfo> val exploredRevealTiles: Sequence<TileInfo> =
if (ruleSet.tileResources[resourceName]!!.hasUnique(UniqueType.CityStateOnlyResource)) {
if (ruleSet.tileResources[resourceName]!!.hasUnique(UniqueType.CityStateOnlyResource)) { // Look for matching mercantile CS centers
// Look for matching mercantile CS centers getAliveCityStates()
exploredRevealTiles = getAliveCityStates() .asSequence()
.asSequence() .filter { it.cityStateResource == resourceName }
.filter { it.cityStateResource == resourceName } .map { it.getCapital().getCenterTile() }
.map { it.getCapital().getCenterTile() } } else {
} else { tileMap.values
exploredRevealTiles = tileMap.values .asSequence()
.asSequence() .filter { it.resource == resourceName }
.filter { it.resource == resourceName } }
}
val exploredRevealInfo = exploredRevealTiles val exploredRevealInfo = exploredRevealTiles
.filter { it.position in civInfo.exploredTiles } .filter { it.position in civInfo.exploredTiles }

View File

@ -288,6 +288,20 @@ class CivilizationInfo {
fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName) fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName)
fun knows(otherCiv: CivilizationInfo) = knows(otherCiv.civName) 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<CivilizationInfo> { it.isMajorCiv() }
.thenBy (UncivGame.Current.settings.getCollatorFromLocale()) { it.civName.tr() }
)
fun getCapital() = cities.first { it.isCapital() } fun getCapital() = cities.first { it.isCapital() }
fun isPlayerCivilization() = playerType == PlayerType.Human fun isPlayerCivilization() = playerType == PlayerType.Human
fun isOneCityChallenger() = ( fun isOneCityChallenger() = (
@ -611,7 +625,7 @@ class CivilizationInfo {
fun getStatForRanking(category: RankingType): Int { fun getStatForRanking(category: RankingType): Int {
return when (category) { return when (category) {
RankingType.Score -> calculateScoreBreakdown().values.sum().toInt() RankingType.Score -> calculateTotalScore().toInt()
RankingType.Population -> cities.sumOf { it.population.population } RankingType.Population -> cities.sumOf { it.population.population }
RankingType.Crop_Yield -> statsForNextTurn.food.roundToInt() RankingType.Crop_Yield -> statsForNextTurn.food.roundToInt()
RankingType.Production -> statsForNextTurn.production.roundToInt() RankingType.Production -> statsForNextTurn.production.roundToInt()
@ -673,7 +687,7 @@ class CivilizationInfo {
var mapSizeModifier = 1276 / gameInfo.tileMap.mapParameters.numberOfTiles().toDouble() var mapSizeModifier = 1276 / gameInfo.tileMap.mapParameters.numberOfTiles().toDouble()
if (mapSizeModifier > 1) if (mapSizeModifier > 1)
mapSizeModifier = (mapSizeModifier - 1) / 3 + 1 mapSizeModifier = (mapSizeModifier - 1) / 3 + 1
scoreBreakdown["Cities"] = cities.count() * 10 * mapSizeModifier scoreBreakdown["Cities"] = cities.count() * 10 * mapSizeModifier
scoreBreakdown["Population"] = cities.sumOf { it.population.population } * 3 * mapSizeModifier scoreBreakdown["Population"] = cities.sumOf { it.population.population } * 3 * mapSizeModifier
scoreBreakdown["Tiles"] = cities.sumOf { city -> city.getTiles().filter { !it.isWater}.count() } * 1 * mapSizeModifier scoreBreakdown["Tiles"] = cities.sumOf { city -> city.getTiles().filter { !it.isWater}.count() } * 1 * mapSizeModifier
@ -683,10 +697,12 @@ class CivilizationInfo {
}.toDouble() }.toDouble()
scoreBreakdown["Techs"] = tech.getNumberOfTechsResearched() * 4.toDouble() scoreBreakdown["Techs"] = tech.getNumberOfTechsResearched() * 4.toDouble()
scoreBreakdown["Future Tech"] = tech.repeatingTechsResearched * 10.toDouble() scoreBreakdown["Future Tech"] = tech.repeatingTechsResearched * 10.toDouble()
return scoreBreakdown return scoreBreakdown
} }
fun calculateTotalScore() = calculateScoreBreakdown().values.sum()
//endregion //endregion
//region state-changing functions //region state-changing functions

View File

@ -95,14 +95,13 @@ data class CityAction(val city: Vector2 = Vector2.Zero): NotificationAction {
/** enter diplomacy screen */ /** enter diplomacy screen */
data class DiplomacyAction(val otherCivName: String = ""): NotificationAction { data class DiplomacyAction(val otherCivName: String = ""): NotificationAction {
override fun execute(worldScreen: WorldScreen) { override fun execute(worldScreen: WorldScreen) {
val screen = DiplomacyScreen(worldScreen.viewingCiv) val otherCiv = worldScreen.gameInfo.getCivilization(otherCivName)
screen.updateRightSide(worldScreen.gameInfo.getCivilization(otherCivName)) worldScreen.game.setScreen(DiplomacyScreen(worldScreen.viewingCiv, otherCiv))
worldScreen.game.setScreen(screen)
} }
} }
/** enter Maya Long Count popup */ /** enter Maya Long Count popup */
class MayaLongCountAction() : NotificationAction { class MayaLongCountAction : NotificationAction {
override fun execute(worldScreen: WorldScreen) { override fun execute(worldScreen: WorldScreen) {
MayaCalendar.openPopup(worldScreen, worldScreen.selectedCiv, worldScreen.gameInfo.getYear()) MayaCalendar.openPopup(worldScreen, worldScreen.selectedCiv, worldScreen.gameInfo.getYear())
} }

View File

@ -5,12 +5,17 @@ import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.Group import com.badlogic.gdx.scenes.scene2d.Group
import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table 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.UncivGame
import com.unciv.logic.HexMath import com.unciv.logic.HexMath
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.ui.trade.DiplomacyScreen import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import kotlin.math.roundToInt
class DiplomacyOverviewTab ( class DiplomacyOverviewTab (
viewingPlayer: CivilizationInfo, viewingPlayer: CivilizationInfo,
@ -24,41 +29,91 @@ class DiplomacyOverviewTab (
} }
override val persistableData = (persistedData as? DiplomacyTabPersistableData) ?: DiplomacyTabPersistableData() 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<CivilizationInfo>()
private var defeatedCivs = sequenceOf<CivilizationInfo>()
private var relevantCivsCount = 0 // includes unknown civs
private var showDiplomacyGroup = false
private var portraitMode = false
init { init {
update() update()
} }
fun update() { override fun getFixedContent(): WidgetGroup {
clear() return fixedContent
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)
} }
// 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 { private fun getCivMiniTable(civInfo: CivilizationInfo): Table {
val table = Table() val table = Table()
@ -67,62 +122,56 @@ class DiplomacyOverviewTab (
table.touchable = Touchable.enabled table.touchable = Touchable.enabled
table.onClick { table.onClick {
if (civInfo.isDefeated() || viewingPlayer.isSpectator() || civInfo == viewingPlayer) return@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 return table
} }
private fun getCivTableScroll(relevantCivs: List<CivilizationInfo>, titleTable: Table, private fun Table.addTitleInfo(columns: Int) {
playerKnowsAndUndefeatedCivs: List<CivilizationInfo>, add("[$relevantCivsCount] Civilizations in the game".toLabel()).colspan(columns).row()
playerKnowsAndDefeatedCivs: List<CivilizationInfo>): AutoScrollPane { add("Our Civilization:".toLabel()).colspan(columns).left().padLeft(10f).padTop(10f).row()
val civTable = Table() add(getCivMiniTable(viewingPlayer)).left()
civTable.defaults().pad(5f) add(viewingPlayer.calculateTotalScore().toInt().toLabel()).left().row()
civTable.background = ImageGetter.getBackground(Color.BLACK) val turnsTillNextDiplomaticVote = viewingPlayer.getTurnsTillNextDiplomaticVote() ?: return
civTable.add("[${relevantCivs.size}] Civilizations in the game".toLabel()).pad(5f).colspan(2).row() add("Turns until the next\ndiplomacy victory vote: [$turnsTillNextDiplomaticVote]".toLabel()).colspan(columns).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 class DiplomacyGroup(val viewingPlayer: CivilizationInfo, freeHeight: Float, includeCityStates: Boolean): Group() { private fun Table.addCivsCategory(columns: Int, aliveOrDefeated: String, civs: Sequence<CivilizationInfo>) {
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<CivilizationInfo>,
freeSize: Float
): Group() {
private fun onCivClicked(civLines: HashMap<String, MutableSet<Actor>>, name: String) { private fun onCivClicked(civLines: HashMap<String, MutableSet<Actor>>, name: String) {
// ignore the clicks on "dead" civilizations, and remember the selected one // ignore the clicks on "dead" civilizations, and remember the selected one
val selectedLines = civLines[name] ?: return val selectedLines = civLines[name] ?: return
@ -146,61 +195,59 @@ class DiplomacyOverviewTab (
} }
} }
if (selectedLines.first().isVisible) if (selectedLines.first().isVisible) {
// invert visibility of all lines except selected one // invert visibility of all lines except selected one
civLines.filter { it.key != name }.forEach { it.value.forEach { line -> line.isVisible = !line.isVisible } } civLines.filter { it.key != name }
else .forEach { it.value.forEach { line -> line.isVisible = !line.isVisible } }
// it happens only when all are visible except selected one } else {
// invert visibility of the selected civ's lines // it happens only when all are visible except selected one
// invert visibility of the selected civ's lines
selectedLines.forEach { it.isVisible = !it.isVisible } selectedLines.forEach { it.isVisible = !it.isVisible }
}
} }
fun playerKnows(civ: CivilizationInfo) = civ == viewingPlayer ||
viewingPlayer.diplomacy.containsKey(civ.civName)
init { init {
val relevantCivs = viewingPlayer.gameInfo.civilizations.filter { !it.isBarbarian() && (includeCityStates || !it.isCityState()) } setSize(freeSize, freeSize)
val playerKnowsAndUndefeatedCivs = relevantCivs.filter { playerKnows(it) && !it.isDefeated() }
setSize(freeHeight, freeHeight)
val civGroups = HashMap<String, Actor>() val civGroups = HashMap<String, Actor>()
val civLines = HashMap<String, MutableSet<Actor>>() val civLines = HashMap<String, MutableSet<Actor>>()
for (i in 0..playerKnowsAndUndefeatedCivs.lastIndex) { val civCount = undefeatedCivs.count()
val civ = playerKnowsAndUndefeatedCivs[i]
for ((i, civ) in undefeatedCivs.withIndex()) {
val civGroup = ImageGetter.getNationIndicator(civ.nation, 30f) 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.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.touchable = Touchable.enabled
civGroup.onClick { civGroup.onClick {
onCivClicked(civLines, civ.civName) onCivClicked(civLines, civ.civName)
} }
civGroup.addTooltip(civ.civName, tipAlign = Align.bottomLeft)
civGroups[civ.civName] = civGroup civGroups[civ.civName] = civGroup
addActor(civGroup) addActor(civGroup)
} }
for (civ in relevantCivs.filter { playerKnows(it) && !it.isDefeated() }) for (civ in undefeatedCivs)
for (diplomacy in civ.diplomacy.values.filter { for (diplomacy in civ.diplomacy.values) {
(it.otherCiv().isMajorCiv() || includeCityStates) && playerKnows(it.otherCiv()) && !it.otherCiv().isDefeated() if (diplomacy.otherCiv() !in undefeatedCivs) continue
}) {
val civGroup = civGroups[civ.civName]!! val civGroup = civGroups[civ.civName]!!
val otherCivGroup = civGroups[diplomacy.otherCivName]!! val otherCivGroup = civGroups[diplomacy.otherCivName]!!
if (!civLines.containsKey(civ.civName)) val statusLine = ImageGetter.getLine(
civLines[civ.civName] = mutableSetOf() 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, statusLine.color = if (diplomacy.diplomaticStatus == DiplomaticStatus.War) Color.RED
otherCivGroup.x + otherCivGroup.width / 2, otherCivGroup.y + otherCivGroup.height / 2, 2f) else diplomacy.relationshipLevel().color
statusLine.color = if (diplomacy.diplomaticStatus == DiplomaticStatus.War) Color.RED else Color.GREEN
if (!civLines.containsKey(civ.civName)) civLines[civ.civName] = mutableSetOf()
civLines[civ.civName]!!.add(statusLine) civLines[civ.civName]!!.add(statusLine)
addActor(statusLine) addActorAt(0, statusLine)
statusLine.toBack()
} }
} }
} }

View File

@ -415,9 +415,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
private fun foreignCityInfoPopup() { private fun foreignCityInfoPopup() {
fun openDiplomacy() { fun openDiplomacy() {
// If city doesn't belong to you, go directly to its owner's diplomacy screen. // If city doesn't belong to you, go directly to its owner's diplomacy screen.
val screen = DiplomacyScreen(worldScreen.viewingCiv) worldScreen.game.setScreen(DiplomacyScreen(worldScreen.viewingCiv, city.civInfo))
screen.updateRightSide(city.civInfo)
worldScreen.game.setScreen(screen)
} }
// If there's nothing to display cuz no Religion - skip popup // If there's nothing to display cuz no Religion - skip popup

View File

@ -10,14 +10,15 @@ import com.unciv.UncivGame
import com.unciv.logic.civilization.* import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.* import com.unciv.logic.civilization.diplomacy.*
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.*
import com.unciv.logic.trade.Trade
import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType import com.unciv.logic.trade.TradeType
import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Quest 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.Unique
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.tr 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.tilegroups.CityButton
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
import kotlin.collections.ArrayList
import kotlin.math.floor import kotlin.math.floor
import kotlin.math.roundToInt import kotlin.math.roundToInt
import com.unciv.ui.utils.AutoScrollPane as ScrollPane 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 rightSideTable = Table()
private val closeButton = Constants.close.toTextButton()
private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen.canChangeState private fun isNotPlayersTurn() = !UncivGame.Current.worldScreen.canChangeState
init { init {
onBackButtonClicked { UncivGame.Current.setWorldScreen() } onBackButtonClicked { UncivGame.Current.setWorldScreen() }
val splitPane = SplitPane(ScrollPane(leftSideTable), rightSideTable, false, skin) val splitPane = SplitPane(leftSideScroll, rightSideTable, false, skin)
splitPane.splitAmount = 0.2f splitPane.splitAmount = 0.2f
updateLeftSideTable() updateLeftSideTable(selectCiv)
splitPane.setFillParent(true) splitPane.setFillParent(true)
stage.addActor(splitPane) stage.addActor(splitPane)
val closeButton = Constants.close.toTextButton()
closeButton.onClick { UncivGame.Current.setWorldScreen() } closeButton.onClick { UncivGame.Current.setWorldScreen() }
closeButton.label.setFontSize(Constants.headingFontSize) closeButton.label.setFontSize(Constants.headingFontSize)
closeButton.labelCell.pad(10f) closeButton.labelCell.pad(10f)
closeButton.pack() closeButton.pack()
closeButton.y = stage.height - closeButton.height - 10 positionCloseButton()
closeButton.x =
(stage.width * 0.2f - closeButton.width) / 2 // center, leftSideTable.width not known yet
stage.addActor(closeButton) // This must come after the split pane so it will be above, that the button will be clickable 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.clear()
leftSideTable.add().padBottom(60f).row() // room so the close button does not cover the first leftSideTable.add().padBottom(60f).row() // room so the close button does not cover the first
val civsToDisplay = viewingCiv.gameInfo.civilizations.asSequence() var selectCivY = 0f
.filterNot {
it.isDefeated() || it == viewingCiv || it.isBarbarian() || it.isSpectator() || for (civ in viewingCiv.getKnownCivsSorted()) {
!viewingCiv.knows(it) if (civ == selectCiv) {
selectCivY = leftSideTable.prefHeight
} }
.sortedWith(
compareByDescending<CivilizationInfo>{ it.isMajorCiv() }
.thenBy (UncivGame.Current.settings.getCollatorFromLocale(), { it.civName.tr() })
)
for (civ in civsToDisplay) { val civIndicator = ImageGetter.getNationIndicator(civ.nation, nationIconSize)
val civIndicator = ImageGetter.getNationIndicator(civ.nation, 100f)
val relationLevel = civ.getDiplomacyManager(viewingCiv).relationshipLevel() val relationLevel = civ.getDiplomacyManager(viewingCiv).relationshipLevel()
val relationshipIcon = if (civ.isCityState() && relationLevel == RelationshipLevel.Ally) val relationshipIcon = if (civ.isCityState() && relationLevel == RelationshipLevel.Ally)
@ -114,9 +135,15 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
civIndicator.onClick { updateRightSide(civ) } 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() rightSideTable.clear()
if (otherCiv.isCityState()) rightSideTable.add( if (otherCiv.isCityState()) rightSideTable.add(
ScrollPane(getCityStateDiplomacyTable(otherCiv)) ScrollPane(getCityStateDiplomacyTable(otherCiv))
@ -125,7 +152,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
.height(stage.height) .height(stage.height)
} }
fun setTrade(civ: CivilizationInfo): TradeTable { private fun setTrade(civ: CivilizationInfo): TradeTable {
rightSideTable.clear() rightSideTable.clear()
val tradeTable = TradeTable(civ, this) val tradeTable = TradeTable(civ, this)
rightSideTable.add(tradeTable) rightSideTable.add(tradeTable)
@ -303,7 +330,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
revokeProtectionButton.onClick { revokeProtectionButton.onClick {
YesNoPopup("Revoke protection for [${otherCiv.civName}]?", { YesNoPopup("Revoke protection for [${otherCiv.civName}]?", {
otherCiv.removeProtectorCiv(viewingCiv) otherCiv.removeProtectorCiv(viewingCiv)
updateLeftSideTable() updateLeftSideTable(otherCiv)
updateRightSide(otherCiv) updateRightSide(otherCiv)
}, this).open() }, this).open()
} }
@ -314,7 +341,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
protectionButton.onClick { protectionButton.onClick {
YesNoPopup("Declare Protection of [${otherCiv.civName}]?", { YesNoPopup("Declare Protection of [${otherCiv.civName}]?", {
otherCiv.addProtectorCiv(viewingCiv) otherCiv.addProtectorCiv(viewingCiv)
updateLeftSideTable() updateLeftSideTable(otherCiv)
updateRightSide(otherCiv) updateRightSide(otherCiv)
}, this).open() }, this).open()
} }
@ -350,7 +377,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
) )
) )
tradeLogic.acceptTrade() tradeLogic.acceptTrade()
updateLeftSideTable() updateLeftSideTable(otherCiv)
updateRightSide(otherCiv) updateRightSide(otherCiv)
}, this).open() }, this).open()
} }
@ -443,7 +470,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
"Gift [$giftAmount] gold (+[$influenceAmount] influence)".toTextButton() "Gift [$giftAmount] gold (+[$influenceAmount] influence)".toTextButton()
giftButton.onClick { giftButton.onClick {
otherCiv.receiveGoldGift(viewingCiv, giftAmount) otherCiv.receiveGoldGift(viewingCiv, giftAmount)
updateLeftSideTable() updateLeftSideTable(otherCiv)
updateRightSide(otherCiv) updateRightSide(otherCiv)
} }
diplomacyTable.add(giftButton).row() diplomacyTable.add(giftButton).row()
@ -694,7 +721,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
denounceButton.onClick { denounceButton.onClick {
YesNoPopup("Denounce [${otherCiv.civName}]?", { YesNoPopup("Denounce [${otherCiv.civName}]?", {
diplomacyManager.denounce() diplomacyManager.denounce()
updateLeftSideTable() updateLeftSideTable(otherCiv)
setRightSideFlavorText(otherCiv, "We will remember this.", "Very well.") setRightSideFlavorText(otherCiv, "We will remember this.", "Very well.")
}, this).open() }, this).open()
} }
@ -864,7 +891,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
YesNoPopup("Declare war on [${otherCiv.civName}]?", { YesNoPopup("Declare war on [${otherCiv.civName}]?", {
diplomacyManager.declareWar() diplomacyManager.declareWar()
setRightSideFlavorText(otherCiv, otherCiv.nation.attacked, "Very well.") setRightSideFlavorText(otherCiv, otherCiv.nation.attacked, "Very well.")
updateLeftSideTable() updateLeftSideTable(otherCiv)
UncivGame.Current.musicController.chooseTrack(otherCiv.civName, MusicMood.War, MusicTrackChooserFlags.setSpecific) UncivGame.Current.musicController.chooseTrack(otherCiv.civName, MusicMood.War, MusicTrackChooserFlags.setSpecific)
}, this).open() }, this).open()
} }
@ -897,4 +924,8 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo): BaseScreen() {
rightSideTable.add(diplomacyTable) rightSideTable.add(diplomacyTable)
} }
override fun resize(width: Int, height: Int) {
super.resize(width, height)
positionCloseButton()
}
} }

View File

@ -2,17 +2,11 @@ package com.unciv.ui.worldscreen
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table 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.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.TradeLogic
import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType import com.unciv.logic.trade.TradeType
import com.unciv.models.translations.tr 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.DiplomacyScreen
import com.unciv.ui.trade.LeaderIntroTable import com.unciv.ui.trade.LeaderIntroTable
import com.unciv.ui.utils.* import com.unciv.ui.utils.*
@ -90,23 +84,15 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
} }
addButton("Not this time.", 'n') { addButton("Not this time.", 'n') {
tradeRequest.decline(viewingCiv) tradeRequest.decline(viewingCiv)
close() close()
requestingCiv.addNotification("[${viewingCiv.civName}] has denied your trade request", viewingCiv.civName, NotificationIcon.Trade) requestingCiv.addNotification("[${viewingCiv.civName}] has denied your trade request", viewingCiv.civName, NotificationIcon.Trade)
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
} }
addButton("How about something else...", 'e') { addButton("How about something else...", 'e') {
close() close()
worldScreen.game.setScreen(DiplomacyScreen(viewingCiv, requestingCiv, trade))
val diplomacyScreen = DiplomacyScreen(viewingCiv)
val tradeTable = diplomacyScreen.setTrade(requestingCiv)
tradeTable.tradeLogic.currentTrade.set(trade)
tradeTable.offerColumnsTable.update()
worldScreen.game.setScreen(diplomacyScreen)
worldScreen.shouldUpdate = true worldScreen.shouldUpdate = true
} }
} }