diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index 86d178f646..fd2a5b3153 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -15,6 +15,7 @@ import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.utils.* +import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import kotlin.concurrent.thread import kotlin.math.max import kotlin.math.min @@ -29,6 +30,7 @@ import com.unciv.ui.utils.AutoScrollPane as ScrollPane class CityConstructionsTable(private val cityScreen: CityScreen) { /* -1 = Nothing, >= 0 queue entry (0 = current construction) */ private var selectedQueueEntry = -1 // None + private var preferredBuyStat = Stat.Gold // Used for keyboard buy var improvementBuildingToConstruct: Building? = null private val upperTable = Table(CameraStageBaseScreen.skin) @@ -405,7 +407,90 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { } } + private fun getBuyButtons(construction: INonPerpetualConstruction?): List { + return Stat.statsUsableToBuy.mapNotNull { getBuyButton(construction, it) } + } + + private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? { + if (stat !in Stat.statsUsableToBuy || construction == null) + return null + + val city = cityScreen.city + val button = "".toTextButton() + + if (!isConstructionPurchaseShown(construction, stat)) { + // This can't ever be bought with the given currency. + // We want one disabled "buy" button without a price for "priceless" buildings such as wonders + // We don't want such a button when the construction can be bought using a different currency + if (stat != Stat.Gold || construction.canBePurchasedWithAnyStat(city)) + return null + button.setText("Buy".tr()) + button.disable() + } else { + val constructionBuyCost = construction.getStatBuyCost(city, stat)!! + button.setText("Buy".tr() + " " + constructionBuyCost + stat.character) + + button.onClick { + button.disable() + askToBuyConstruction(construction, stat) + } + button.isEnabled = isConstructionPurchaseAllowed(construction, stat, constructionBuyCost) + button.addTooltip('B') // The key binding is done in CityScreen constructor + preferredBuyStat = stat // Not very intelligent, but the least common currency "wins" + } + + button.labelCell.pad(5f) + + return button + } + + /** Ask whether user wants to buy [construction] for [stat]. + * + * Used from onClick and keyboard dispatch, thus only minimal parameters are passed, + * and it needs to do all checks and the sound as appropriate. + */ + fun askToBuyConstruction(construction: INonPerpetualConstruction, stat: Stat = preferredBuyStat) { + if (!isConstructionPurchaseShown(construction, stat)) return + val city = cityScreen.city + val constructionBuyCost = construction.getStatBuyCost(city, stat)!! + if (!isConstructionPurchaseAllowed(construction, stat, constructionBuyCost)) return + + cityScreen.closeAllPopups() + + val purchasePrompt = "Currently you have [${city.getStatReserve(stat)}] [${stat.name}].".tr() + "\n\n" + + "Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.character}]?".tr() + YesNoPopup( + purchasePrompt, + action = { purchaseConstruction(construction, stat) }, + screen = cityScreen, + restoreDefault = { cityScreen.update() } + ).open() + } + + /** This tests whether the buy button should be _shown_ */ + private fun isConstructionPurchaseShown(construction: INonPerpetualConstruction, stat: Stat): Boolean { + val city = cityScreen.city + return construction.canBePurchasedWithStat(city, stat) || city.civInfo.gameInfo.gameParameters.godMode + } + + /** This tests whether the buy button should be _enabled_ */ + private fun isConstructionPurchaseAllowed(construction: INonPerpetualConstruction, stat: Stat, constructionBuyCost: Int): Boolean { + val city = cityScreen.city + return when { + city.isPuppet -> false + !cityScreen.canChangeState -> false + city.isInResistance() -> false + !construction.isPurchasable(city.cityConstructions) -> false // checks via 'rejection reason' + !city.canPurchase(construction) -> false // checks room on map for units + city.civInfo.gameInfo.gameParameters.godMode -> true + constructionBuyCost == 0 -> true + else -> city.getStatReserve(stat) >= constructionBuyCost + } +} + + // called only by askToBuyConstruction's Yes answer private fun purchaseConstruction(construction: INonPerpetualConstruction, stat: Stat = Stat.Gold) { + Sounds.play(stat.purchaseSound) val city = cityScreen.city if (!city.cityConstructions.purchaseConstruction(construction.name, selectedQueueEntry, false, stat)) { Popup(cityScreen).apply { @@ -418,64 +503,15 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { if (isSelectedQueueEntry() || cityScreen.selectedConstruction?.isBuildable(city.cityConstructions) != true) { selectedQueueEntry = -1 cityScreen.selectedConstruction = null + + // Allow buying next queued or auto-assigned construction right away + city.cityConstructions.chooseNextConstruction() + if (city.cityConstructions.currentConstructionFromQueue.isNotEmpty()) + cityScreen.selectedConstruction = city.cityConstructions.getCurrentConstruction().takeIf { it is INonPerpetualConstruction } } cityScreen.update() } - private fun getBuyButtons(construction: INonPerpetualConstruction?): List { - return Stat.statsUsableToBuy.mapNotNull { getBuyButton(construction, it) } - } - - private fun getBuyButton(construction: INonPerpetualConstruction?, stat: Stat = Stat.Gold): TextButton? { - if (stat !in Stat.statsUsableToBuy || construction == null) - return null - - val city = cityScreen.city - val button = "".toTextButton() - - if (!construction.canBePurchasedWithStat(city, stat) && !city.civInfo.gameInfo.gameParameters.godMode) { - // This can't ever be bought with the given currency. - // We want one disabled "buy" button without a price for "priceless" buildings such as wonders - // We don't want such a button when the construction can be bought using a different currency - if (stat != Stat.Gold || construction.canBePurchasedWithAnyStat(city)) - return null - button.setText("Buy".tr()) - button.disable() - } else { - val constructionBuyCost = construction.getStatBuyCost(city, stat)!! - button.setText("Buy".tr() + " " + constructionBuyCost + stat.character) - - button.onClick(stat.purchaseSound) { - button.disable() - cityScreen.closeAllPopups() - - val purchasePrompt = "Currently you have [${city.getStatReserve(stat)}] [${stat.name}].".tr() + "\n\n" + - "Would you like to purchase [${construction.name}] for [$constructionBuyCost] [${stat.character}]?".tr() - YesNoPopup( - purchasePrompt, - action = { purchaseConstruction(construction, stat) }, - screen = cityScreen, - restoreDefault = { cityScreen.update() } - ).apply { - promptLabel.setAlignment(Align.center) - open() - } - } - - if (!cityScreen.canChangeState - || !construction.isPurchasable(city.cityConstructions) - || city.isPuppet - || city.isInResistance() - || !city.canPurchase(construction) - || (constructionBuyCost > city.getStatReserve(stat) && !city.civInfo.gameInfo.gameParameters.godMode) - ) button.disable() - } - - button.labelCell.pad(5f) - - return button - } - private fun getRaisePriorityButton(constructionQueueIndex: Int, name: String, city: CityInfo): Table { val tab = Table() tab.add(ImageGetter.getImage("OtherIcons/Up").surroundWithCircle(40f)) diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index 8b64e0b071..87849efca8 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -7,21 +7,23 @@ import com.badlogic.gdx.utils.Align import com.unciv.UncivGame import com.unciv.logic.city.CityInfo import com.unciv.logic.city.IConstruction +import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.logic.map.TileInfo import com.unciv.ui.map.TileGroupMap import com.unciv.ui.tilegroups.TileSetStrings import com.unciv.ui.utils.* import java.util.* -class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() { +class CityScreen( + internal val city: CityInfo, + var selectedConstruction: IConstruction? = null, + var selectedTile: TileInfo? = null +): CameraStageBaseScreen() { companion object { /** Distance from stage edges to floating widgets */ const val posFromEdge = 5f } - var selectedTile: TileInfo? = null - var selectedConstruction: IConstruction? = null - /** Toggles or adds/removes all state changing buttons */ val canChangeState = UncivGame.Current.worldScreen.canChangeState @@ -84,6 +86,14 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() { keyPressDispatcher[Input.Keys.LEFT] = { page(-1) } keyPressDispatcher[Input.Keys.RIGHT] = { page(1) } + keyPressDispatcher['T'] = { + if (selectedTile != null) + tileTable.askToBuyTile(selectedTile!!) + } + keyPressDispatcher['B'] = { + if (selectedConstruction is INonPerpetualConstruction) + constructionsTable.askToBuyConstruction(selectedConstruction as INonPerpetualConstruction) + } } internal fun update() { diff --git a/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt b/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt index 455c468b07..ca671b1102 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreenTileTable.kt @@ -11,6 +11,7 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.MarkupRenderer import com.unciv.ui.utils.* +import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import kotlin.math.roundToInt class CityScreenTileTable(private val cityScreen: CityScreen): Table() { @@ -42,22 +43,15 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() { innerTable.row() innerTable.add(getTileStatsTable(stats)).row() - if (selectedTile.getOwner() == null && selectedTile.neighbors.any { it.getCity() == city } - && selectedTile in city.tilesInRange) { + if (isTilePurchaseShown(selectedTile)) { val goldCostOfTile = city.expansion.getGoldCostOfTile(selectedTile) - val buyTileButton = "Buy for [$goldCostOfTile] gold".toTextButton() - buyTileButton.onClick(UncivSound.Coin) { - val purchasePrompt = "Currently you have [${city.civInfo.gold}] [Gold].".tr() + "\n\n" + - "Would you like to purchase [Tile] for [$goldCostOfTile] [${Stat.Gold.character}]?".tr() - YesNoPopup(purchasePrompt, { city.expansion.buyTile(selectedTile);UncivGame.Current.setScreen(CityScreen(city)) }, cityScreen).open() - } - val canPurchase = goldCostOfTile == 0 || city.civInfo.gold >= goldCostOfTile - if (!canPurchase && !city.civInfo.gameInfo.gameParameters.godMode - || city.isPuppet - || !cityScreen.canChangeState) + buyTileButton.onClick { buyTileButton.disable() - + askToBuyTile(selectedTile) + } + buyTileButton.isEnabled = isTilePurchaseAllowed(goldCostOfTile) + buyTileButton.addTooltip('T') // The key binding is done in CityScreen constructor innerTable.add(buyTileButton).padTop(5f).row() } @@ -92,6 +86,49 @@ class CityScreenTileTable(private val cityScreen: CityScreen): Table() { pack() } + /** Ask whether user wants to buy [selectedTile] for gold. + * + * Used from onClick and keyboard dispatch, thus only minimal parameters are passed, + * and it needs to do all checks and the sound as appropriate. + */ + fun askToBuyTile(selectedTile: TileInfo) { + // These checks are redundant for the onClick action, but not for the keyboard binding + if (!isTilePurchaseShown(selectedTile)) return + val goldCostOfTile = city.expansion.getGoldCostOfTile(selectedTile) + if (!isTilePurchaseAllowed(goldCostOfTile)) return + + cityScreen.closeAllPopups() + + val purchasePrompt = "Currently you have [${city.civInfo.gold}] [Gold].".tr() + "\n\n" + + "Would you like to purchase [Tile] for [$goldCostOfTile] [${Stat.Gold.character}]?".tr() + YesNoPopup( + purchasePrompt, + action = { + Sounds.play(UncivSound.Coin) + city.expansion.buyTile(selectedTile) + // preselect the next tile on city screen rebuild so bulk buying can go faster + UncivGame.Current.setScreen(CityScreen(city, selectedTile = city.expansion.chooseNewTileToOwn())) + }, + screen = cityScreen, + restoreDefault = { cityScreen.update() } + ).open() + } + + /** This tests whether the buy button should be _shown_ */ + private fun isTilePurchaseShown(selectedTile: TileInfo) = when { + selectedTile.getOwner() != null -> false + selectedTile !in city.tilesInRange -> false + else -> selectedTile.neighbors.any { it.getCity() == city } + } + /** This tests whether the buy button should be _enabled_ */ + private fun isTilePurchaseAllowed(goldCostOfTile: Int) = when { + city.isPuppet -> false + !cityScreen.canChangeState -> false + city.civInfo.gameInfo.gameParameters.godMode -> true + goldCostOfTile == 0 -> true + else -> city.civInfo.gold >= goldCostOfTile + } + private fun getTileStatsTable(stats: Stats): Table { val statsTable = Table() statsTable.defaults().pad(2f)