Trade UI - Leader portraits, keys, layout (#4073)

* Trade UI - Leader portraits, keys, layout

* Trade UI - Leader portraits, keys, layout - fix1

* Trade UI - Leader portraits, keys, layout - fix2
This commit is contained in:
SomeTroglodyte 2021-06-09 19:56:26 +02:00 committed by GitHub
parent db1604b0bc
commit e0a8de9eb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 85 deletions

View File

@ -367,15 +367,24 @@ class CivilizationInfo {
fun isAtWar() = diplomacy.values.any { it.diplomaticStatus == DiplomaticStatus.War && !it.otherCiv().isDefeated() }
/**
* Returns a civilization caption suitable for greetings including player type info:
* Like "Milan" if the nation is a city state, "Caesar of Rome" otherwise, with an added
* " (AI)", " (Human - Hotseat)", or " (Human - Multiplayer)" if the game is multiplayer.
*/
fun getLeaderDisplayName(): String {
val severalHumans = gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1
val online = gameInfo.gameParameters.isOnlineMultiplayer
return nation.getLeaderDisplayName().tr() +
when {
!online && !severalHumans ->
"" // offline single player will know everybody else is AI
playerType == PlayerType.AI ->
" (" + "AI".tr() + ")"
gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1 ->
" (" + "Human".tr() + " - " + "Hotseat".tr() + ")"
else ->
online ->
" (" + "Human".tr() + " - " + "Multiplayer".tr() + ")"
else ->
" (" + "Human".tr() + " - " + "Hotseat".tr() + ")"
}
}

View File

@ -49,7 +49,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
closeButton.labelCell.pad(10f)
closeButton.pack()
closeButton.y = stage.height - closeButton.height - 10
closeButton.x = 10f
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
}
@ -99,11 +99,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
val diplomacyTable = Table()
diplomacyTable.defaults().pad(10f)
val displayNameTable = Table()
displayNameTable.add(ImageGetter.getNationIndicator(otherCiv.nation, 24f))
.pad(0f, 0f, 5f, 10f)
displayNameTable.add(otherCiv.getLeaderDisplayName().toLabel(fontSize = 24))
diplomacyTable.add(displayNameTable).row()
diplomacyTable.add(LeaderIntroTable(otherCiv)).row()
diplomacyTable.add("{Type}: {${otherCiv.cityStateType}}".toLabel()).row()
diplomacyTable.add("{Personality}: {${otherCiv.cityStatePersonality}}".toLabel()).row()
@ -248,19 +244,10 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
val diplomacyTable = Table()
diplomacyTable.defaults().pad(10f)
val displayNameTable = Table()
displayNameTable.add(ImageGetter.getNationIndicator(otherCiv.nation, 24f)).pad(0f, 0f, 5f, 5f)
displayNameTable.add(otherCiv.getLeaderDisplayName().toLabel(fontSize = 24)).row()
val helloText = if (otherCivDiplomacyManager.relationshipLevel() <= RelationshipLevel.Enemy) otherCiv.nation.hateHello
else otherCiv.nation.neutralHello
displayNameTable.add(helloText.toLabel()).colspan(2)
val leaderIntroTable = Table()
val leaderPortraitImage = "LeaderIcons/" + otherCiv.nation.leaderName
if (ImageGetter.imageExists(leaderPortraitImage))
leaderIntroTable.add(ImageGetter.getImage(leaderPortraitImage)).size(100f).padRight(10f)
leaderIntroTable.add(displayNameTable)
val helloText = if (otherCivDiplomacyManager.relationshipLevel() <= RelationshipLevel.Enemy)
otherCiv.nation.hateHello
else otherCiv.nation.neutralHello
val leaderIntroTable = LeaderIntroTable(otherCiv, helloText)
diplomacyTable.add(leaderIntroTable).row()
diplomacyTable.addSeparator()
@ -471,12 +458,17 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
private fun setRightSideFlavorText(otherCiv: CivilizationInfo, flavorText: String, response: String) {
val diplomacyTable = Table()
diplomacyTable.defaults().pad(10f)
diplomacyTable.add(otherCiv.getLeaderDisplayName().toLabel())
diplomacyTable.add(LeaderIntroTable(otherCiv))
diplomacyTable.addSeparator()
diplomacyTable.add(flavorText.toLabel()).row()
val responseButton = response.toTextButton()
responseButton.onClick { updateRightSide(otherCiv) }
val action = {
keyPressDispatcher.remove(KeyCharAndCode.SPACE)
updateRightSide(otherCiv)
}
responseButton.onClick(action)
keyPressDispatcher[KeyCharAndCode.SPACE] = action
diplomacyTable.add(responseButton)
rightSideTable.clear()

View File

@ -0,0 +1,49 @@
package com.unciv.ui.trade
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.ui.utils.CameraStageBaseScreen
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.toLabel
/**
* This is meant to be used for any kind of civ introduction - [DiplomacyScreen],
* [AlertPopup][com.unciv.ui.worldscreen.AlertPopup] [types][com.unciv.logic.civilization.AlertType] WarDeclaration, FirstContact etc.
*
* @param civInfo The civilization to display
* @param hello Optional additional message
*/
class LeaderIntroTable (
civInfo: CivilizationInfo,
hello: String = ""
): Table(CameraStageBaseScreen.skin) {
/**
* Build either a Table(icon, leaderName <br> hello) or
* a Table(Portrait, Table(leaderName, icon <br> hello))
*
* City states in vanilla have leaderName=="" - but don't test CS, test leaderName to allow modding CS to have portraits
*/
init {
defaults().align(Align.center)
val nation = civInfo.nation
val leaderPortraitFile = "LeaderIcons/" + nation.leaderName
val leaderLabel = civInfo.getLeaderDisplayName().toLabel(fontSize = 24)
val nationIndicator = ImageGetter.getNationIndicator(nation, 24f)
if (nation.leaderName.isNotEmpty() && ImageGetter.imageExists(leaderPortraitFile)) {
val nameTable = Table()
nameTable.add(leaderLabel)
nameTable.add(nationIndicator).pad(0f, 10f, 5f, 0f).row()
if (hello.isNotEmpty())
nameTable.add(hello.toLabel()).colspan(2)
add(ImageGetter.getImage(leaderPortraitFile)).size(100f)
.padRight(10f)
add(nameTable)
} else {
add(nationIndicator).pad(0f, 0f, 5f, 10f)
add(leaderLabel).row()
if (hello.isNotEmpty())
add(hello.toLabel()).colspan(2)
}
}
}

View File

@ -8,22 +8,41 @@ import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.PopupAlert
import com.unciv.models.translations.fillPlaceholders
import com.unciv.ui.trade.LeaderIntroTable
import com.unciv.ui.utils.*
/**
* [Popup] communicating events other than trade offers to the player.
* (e.g. First Contact, Wonder built, Tech researched,...)
*
* Called in [WorldScreen].update, which pulls them from viewingCiv.popupAlerts.
*
* @param worldScreen The parent screen
* @param popupAlert The [PopupAlert] entry to present
*
* @see AlertType
*/
class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popup(worldScreen) {
fun getCloseButton(text: String, action: (() -> Unit)? = null): TextButton {
fun getCloseButton(text: String, key: Char = Char.MIN_VALUE, action: (() -> Unit)? = null): TextButton {
// Popup.addCloseButton is close but AlertPopup needs the flexibility to add these inside a wrapper
val button = text.toTextButton()
button.onClick {
val buttonAction = {
if (action != null) action()
worldScreen.shouldUpdate = true
close()
}
button.onClick(buttonAction)
if (key == Char.MIN_VALUE) {
keyPressDispatcher[KeyCharAndCode.BACK] = buttonAction
keyPressDispatcher[KeyCharAndCode.SPACE] = buttonAction
} else {
keyPressDispatcher[key] = buttonAction
}
return button
}
fun addLeaderName(civInfo: CivilizationInfo) {
val otherCivLeaderName = civInfo.getLeaderDisplayName()
add(otherCivLeaderName.toLabel())
add(LeaderIntroTable(civInfo))
addSeparator()
}
@ -35,6 +54,7 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
addLeaderName(civInfo)
addGoodSizedLabel(civInfo.nation.declaringWar).row()
val responseTable = Table()
responseTable.defaults().pad(0f, 5f)
responseTable.add(getCloseButton("You'll pay for this!"))
responseTable.add(getCloseButton("Very well."))
add(responseTable)
@ -48,12 +68,11 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
AlertType.FirstContact -> {
val civInfo = worldScreen.gameInfo.getCivilization(popupAlert.value)
val nation = civInfo.nation
addLeaderName(civInfo)
if (civInfo.isCityState()) {
addLeaderName(civInfo)
addGoodSizedLabel("We have encountered the City-State of [${nation.name}]!").row()
add(getCloseButton("Excellent!"))
} else {
addLeaderName(civInfo)
addGoodSizedLabel(nation.introduction).row()
add(getCloseButton("A pleasure to meet you."))
}
@ -67,54 +86,66 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
if (city.foundingCiv != ""
&& city.civInfo.civName != city.foundingCiv // can't liberate if the city actually belongs to those guys
&& conqueringCiv.civName != city.foundingCiv) { // or belongs originally to us
add("Liberate (city returns to [originalOwner])".fillPlaceholders(city.foundingCiv).toTextButton().onClick {
val liberateAction = {
city.liberateCity(conqueringCiv)
worldScreen.shouldUpdate = true
close()
}).row()
}
val liberateText = "Liberate (city returns to [originalOwner])".fillPlaceholders(city.foundingCiv)
add(liberateText.toTextButton().onClick(function = liberateAction)).row()
keyPressDispatcher['l'] = liberateAction
addGoodSizedLabel("Liberating a city returns it to its original owner, giving you a massive relationship boost with them!")
addSeparator()
}
if (conqueringCiv.isOneCityChallenger()) {
add("Destroy".toTextButton().onClick {
val destroyAction = {
city.puppetCity(conqueringCiv)
city.destroyCity()
worldScreen.shouldUpdate = true
close()
}).row()
}
add("Destroy".toTextButton().onClick(function = destroyAction)).row()
keyPressDispatcher['d'] = destroyAction
addGoodSizedLabel("Destroying the city instantly razes the city to the ground.").row()
} else {
add("Annex".toTextButton().onClick {
val annexAction = {
city.puppetCity(conqueringCiv)
city.annexCity()
worldScreen.shouldUpdate = true
close()
}).row()
}
add("Annex".toTextButton().onClick(function = annexAction)).row()
keyPressDispatcher['a'] = annexAction
addGoodSizedLabel("Annexed cities become part of your regular empire.").row()
addGoodSizedLabel("Their citizens generate 2x the unhappiness, unless you build a courthouse.").row()
addSeparator()
add("Puppet".toTextButton().onClick {
val puppetAction = {
city.puppetCity(conqueringCiv)
worldScreen.shouldUpdate = true
close()
}).row()
}
add("Puppet".toTextButton().onClick(function = puppetAction) ).row()
keyPressDispatcher['p'] = puppetAction
addGoodSizedLabel("Puppeted cities do not increase your tech or policy cost, but their citizens generate 1.5x the regular unhappiness.").row()
addGoodSizedLabel("You have no control over the the production of puppeted cities.").row()
addGoodSizedLabel("Puppeted cities also generate 25% less Gold and Science.").row()
addGoodSizedLabel("A puppeted city can be annexed at any time.").row()
addSeparator()
val razeAction = {
city.puppetCity(conqueringCiv)
city.annexCity()
city.isBeingRazed = true
worldScreen.shouldUpdate = true
close()
}
add("Raze".toTextButton().apply {
if (city.isOriginalCapital) disable()
else onClick {
city.puppetCity(conqueringCiv)
city.annexCity()
city.isBeingRazed = true
worldScreen.shouldUpdate = true
close()
else {
onClick(function = razeAction)
keyPressDispatcher['r'] = razeAction
}
}).row()
addGoodSizedLabel("Razing the city annexes it, and starts razing the city to the ground.").row()
@ -126,6 +157,7 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
addLeaderName(civInfo)
addGoodSizedLabel("Remove your troops in our border immediately!").row()
val responseTable = Table()
responseTable.defaults().pad(0f, 5f)
responseTable.add(getCloseButton("Sorry."))
responseTable.add(getCloseButton("Never!"))
add(responseTable)
@ -135,10 +167,10 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
val playerDiploManager = worldScreen.viewingCiv.getDiplomacyManager(otherciv)
addLeaderName(otherciv)
addGoodSizedLabel("Please don't settle new cities near us.").row()
add(getCloseButton("Very well, we shall look for new lands to settle.") {
add(getCloseButton("Very well, we shall look for new lands to settle.", 'y') {
playerDiploManager.agreeNotToSettleNear()
}).row()
add(getCloseButton("We shall do as we please.") {
add(getCloseButton("We shall do as we please.", 'n') {
playerDiploManager.refuseDemandNotToSettleNear()
}).row()
}
@ -184,8 +216,8 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu
val playerDiploManager = worldScreen.viewingCiv.getDiplomacyManager(otherciv)
addLeaderName(otherciv)
addGoodSizedLabel("My friend, shall we declare our friendship to the world?").row()
add(getCloseButton("We are not interested.")).row()
add(getCloseButton("Declare Friendship ([30] turns)") {
add(getCloseButton("We are not interested.", 'n')).row()
add(getCloseButton("Declare Friendship ([30] turns)", 'y') {
playerDiploManager.signDeclarationOfFriendship()
}).row()
}

View File

@ -1,5 +1,6 @@
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.logic.civilization.NotificationIcon
@ -10,13 +11,26 @@ import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.models.translations.tr
import com.unciv.ui.trade.DiplomacyScreen
import com.unciv.ui.utils.Popup
import com.unciv.ui.utils.disable
import com.unciv.ui.utils.toLabel
import com.unciv.ui.trade.LeaderIntroTable
import com.unciv.ui.utils.*
import kotlin.math.max
import kotlin.math.min
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
/* TODO:
different Notification wording for peace treaties?
Let Notification jump to Diplomacy.trade with empty offers (accepted)
or a clone of the rejected offer (denied) ...?
Unify separator implementations (here, Table.addSeparator, ModManagementScreen)
*/
/**
* [Popup] communicating trade offers of others to the player.
*
* Called in [WorldScreen].update, which checks if there are any in viewingCiv.tradeRequests.
*
* @param worldScreen The parent screen
*/
class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
val viewingCiv = worldScreen.viewingCiv
val tradeRequest = viewingCiv.tradeRequests.first()
@ -24,14 +38,16 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
init{
val requestingCiv = worldScreen.gameInfo.getCivilization(tradeRequest.requestingCiv)
val nation = requestingCiv.nation
val otherCivLeaderName = "[${nation.leaderName}] of [${nation.name}]".tr()
add(otherCivLeaderName.toLabel())
val leaderIntroTable = LeaderIntroTable(requestingCiv)
add(leaderIntroTable)
addSeparator()
val trade = tradeRequest.trade
val tradeOffersTable = Table().apply { defaults().pad(10f) }
tradeOffersTable.add("[${nation.name}]'s trade offer".toLabel())
// empty column to separate offers columns better
tradeOffersTable.add().pad(0f, 15f)
tradeOffersTable.add("Our trade offer".toLabel())
tradeOffersTable.row()
val ourResources = viewingCiv.getCivResourcesByName()
@ -43,11 +59,12 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
return tradeText
}
for(i in 0..max(trade.theirOffers.lastIndex, trade.ourOffers.lastIndex)){
if(trade.theirOffers.lastIndex>=i) tradeOffersTable.add(getOfferText(trade.theirOffers[i]).toLabel())
else tradeOffersTable.add()
if(trade.ourOffers.lastIndex>=i) tradeOffersTable.add(getOfferText(trade.ourOffers[i]).toLabel())
else tradeOffersTable.add()
for (i in 0..max(trade.theirOffers.lastIndex, trade.ourOffers.lastIndex)) {
if (trade.theirOffers.lastIndex < i) tradeOffersTable.add()
else tradeOffersTable.add(getOfferText(trade.theirOffers[i]).toLabel())
tradeOffersTable.add()
if (trade.ourOffers.lastIndex < i) tradeOffersTable.add()
else tradeOffersTable.add(getOfferText(trade.ourOffers[i]).toLabel())
tradeOffersTable.row()
}
tradeOffersTable.pack()
@ -55,25 +72,18 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
val scrollHeight = min(tradeOffersTable.height, worldScreen.stage.height/2)
add(ScrollPane(tradeOffersTable)).height(scrollHeight).row()
addGoodSizedLabel(nation.tradeRequest).colspan(2).row()
val separator = Table()
separator.background = ImageGetter.getBackground(Color.DARK_GRAY)
add(separator).minHeight(1f).fillX().row()
val soundsGoodButton = addButton("Sounds good!"){
addGoodSizedLabel(nation.tradeRequest).pad(15f).row()
val soundsGoodButton = addButton("Sounds good!", 'y') {
val tradeLogic = TradeLogic(viewingCiv, requestingCiv)
tradeLogic.currentTrade.set(trade)
tradeLogic.acceptTrade()
close()
Popup(worldScreen).apply {
add(otherCivLeaderName.toLabel()).colspan(2)
addSeparator()
addGoodSizedLabel("Excellent!").row()
addButton("Farewell."){
close()
worldScreen.shouldUpdate=true
// in all cases, worldScreen.shouldUpdate should be set to true when we remove the last of the popups
// in order for the next trade to appear immediately
}
open()
}
TradeThanksPopup(leaderIntroTable, worldScreen)
requestingCiv.addNotification("[${viewingCiv.civName}] has accepted your trade request", viewingCiv.civName, NotificationIcon.Trade)
}
@ -81,30 +91,30 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
if(!TradeEvaluation().isTradeValid(trade,viewingCiv,requestingCiv))
soundsGoodButton.actor.disable()
addButton("Not this time.".tr()){
addButton("Not this time.", 'n') {
val diplomacyManager = requestingCiv.getDiplomacyManager(viewingCiv)
if(trade.ourOffers.all { it.type==TradeType.Luxury_Resource } && trade.theirOffers.all { it.type==TradeType.Luxury_Resource })
if(trade.ourOffers.all { it.type == TradeType.Luxury_Resource } && trade.theirOffers.all { it.type==TradeType.Luxury_Resource })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedLuxExchange,20) // offer again in 20 turns
if(trade.ourOffers.any { it.name==Constants.researchAgreement })
if(trade.ourOffers.any { it.name == Constants.researchAgreement })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,20) // offer again in 20 turns
if(trade.ourOffers.any{ it.type==TradeType.Treaty && it.name== Constants.peaceTreaty })
if(trade.ourOffers.any { it.type == TradeType.Treaty && it.name == Constants.peaceTreaty })
diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace,5)
close()
requestingCiv.addNotification("[${viewingCiv.civName}] has denied your trade request", viewingCiv.civName, NotificationIcon.Trade)
worldScreen.shouldUpdate=true
worldScreen.shouldUpdate = true
}
addButton("How about something else...".tr()){
addButton("How about something else...", 'e') {
close()
val diplomacyScreen= DiplomacyScreen(viewingCiv)
val tradeTable = diplomacyScreen.setTrade(requestingCiv)
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
}
}
@ -112,4 +122,18 @@ class TradePopup(worldScreen: WorldScreen): Popup(worldScreen){
viewingCiv.tradeRequests.remove(tradeRequest)
super.close()
}
class TradeThanksPopup(leaderIntroTable: LeaderIntroTable, worldScreen: WorldScreen): Popup(worldScreen) {
init {
add(leaderIntroTable)
addSeparator().padBottom(15f)
addGoodSizedLabel("Excellent!").row()
addCloseButton("Farewell.", KeyCharAndCode.SPACE) {
worldScreen.shouldUpdate=true
// in all cases, worldScreen.shouldUpdate should be set to true when we remove the last of the popups
// in order for the next trade to appear immediately
}
open()
}
}
}