Victory screen reorg/cleanup (#9111)

* VictoryScreen reorg

* VictoryScreen reorg - use TabbedPager

* VictoryScreen reorg - make helpers reusable

* VictoryScreen reorg - fixed headers

* VictoryScreen reorg - debug access to Replay

* Victories reorg - more cleanup

* VictoryScreen reorg - icons and keys

* VictoryScreen reorg - RankingType

* VictoryScreen reorg - RecreateOnResize

* VictoryScreen reorg - Avoid floating text overlapping buttons

* VictoryScreen reorg - remove obsolete todo
This commit is contained in:
SomeTroglodyte 2023-04-04 09:49:58 +02:00 committed by GitHub
parent 8b01498227
commit cd4e25a4f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 461 additions and 322 deletions

View File

@ -27,7 +27,6 @@
},
{
"name": "Diplomatic",
"hiddenInVictoryScreen": false,
"victoryScreenHeader": "Build the UN and be voted\nworld leader to win!",
"milestones": ["Anyone should build [United Nations]", "Win diplomatic vote"],
"victoryString": "You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world!",
@ -40,4 +39,4 @@
"victoryString": "The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph!",
"defeatString": "You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory!"
}
]
]

View File

@ -27,7 +27,6 @@
},
{
"name": "Diplomatic",
"hiddenInVictoryScreen": false,
"victoryScreenHeader": "Build the UN and be voted\nworld leader to win!",
"milestones": ["Anyone should build [United Nations]", "Win diplomatic vote"],
"victoryString": "You have triumphed over your foes through the art of diplomacy! Your cunning and wisdom have earned you great friends - and divided and sown confusion among your enemies! Forever will you be remembered as the leader who brought peace to this weary world!",
@ -40,4 +39,4 @@
"victoryString": "The world has been convulsed by war. Many great and powerful civilizations have fallen, but you have survived - and emerged victorious! The world will long remember your glorious triumph!",
"defeatString": "You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory!"
}
]
]

View File

@ -45,7 +45,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private val isAtWar = civInfo.isAtWar()
private val buildingsForVictory = civInfo.gameInfo.getEnabledVictories().values
.mapNotNull { civInfo.victoryManager.getNextMilestone(it.name) }
.mapNotNull { civInfo.victoryManager.getNextMilestone(it) }
.filter { it.type == MilestoneType.BuiltBuilding || it.type == MilestoneType.BuildingBuiltGlobally }
.map { it.params[0] }

View File

@ -572,7 +572,7 @@ object NextTurnAutomation {
* a unit and selling a building to make room. Can happen due to trades etc */
private fun freeUpSpaceResources(civInfo: Civilization) {
// No need to build spaceship parts just yet
if (civInfo.gameInfo.ruleset.victories.none { civInfo.victoryManager.getNextMilestone(it.key)?.type == MilestoneType.AddedSSPartsInCapital } )
if (civInfo.gameInfo.ruleset.victories.none { civInfo.victoryManager.getNextMilestone(it.value)?.type == MilestoneType.AddedSSPartsInCapital } )
return
for (resource in civInfo.gameInfo.spaceResources) {

View File

@ -541,7 +541,7 @@ class Civilization : IsPartOfGameInfoSerialization {
else when (category) {
RankingType.Score -> calculateTotalScore().toInt()
RankingType.Population -> cities.sumOf { it.population.population }
RankingType.Crop_Yield -> stats.statsForNextTurn.food.roundToInt()
RankingType.CropYield -> stats.statsForNextTurn.food.roundToInt()
RankingType.Production -> stats.statsForNextTurn.production.roundToInt()
RankingType.Gold -> gold
RankingType.Territory -> cities.sumOf { it.tiles.size }

View File

@ -5,6 +5,7 @@ import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.civilization.Civilization
import com.unciv.models.Counter
import com.unciv.models.ruleset.Milestone
import com.unciv.models.ruleset.Victory
import com.unciv.models.ruleset.unique.UniqueType
class VictoryManager : IsPartOfGameInfoSerialization {
@ -57,27 +58,28 @@ class VictoryManager : IsPartOfGameInfoSerialization {
fun getVictoryTypeAchieved(): String? {
if (!civInfo.isMajorCiv()) return null
for (victoryName in civInfo.gameInfo.gameParameters.victoryTypes
.filter { it != Constants.neutralVictoryType && it in civInfo.gameInfo.ruleset.victories}) {
if (getNextMilestone(victoryName) == null)
return victoryName
}
val enabledVictories = civInfo.gameInfo.gameParameters.victoryTypes
val victory = civInfo.gameInfo.ruleset.victories
.filter { it.key != Constants.neutralVictoryType && it.key in enabledVictories }
.map { it.value }
.firstOrNull { getNextMilestone(it) == null }
if (victory != null) return victory.name
if (civInfo.hasUnique(UniqueType.TriggersVictory))
return Constants.neutralVictoryType
return null
}
fun getNextMilestone(victory: String): Milestone? {
for (milestone in civInfo.gameInfo.ruleset.victories[victory]!!.milestoneObjects) {
fun getNextMilestone(victory: Victory): Milestone? {
for (milestone in victory.milestoneObjects) {
if (!milestone.hasBeenCompletedBy(civInfo))
return milestone
}
return null
}
fun amountMilestonesCompleted(victory: String): Int {
fun amountMilestonesCompleted(victory: Victory): Int {
var completed = 0
for (milestone in civInfo.gameInfo.ruleset.victories[victory]!!.milestoneObjects) {
for (milestone in victory.milestoneObjects) {
if (milestone.hasBeenCompletedBy(civInfo))
++completed
else

View File

@ -9,6 +9,7 @@ import com.badlogic.gdx.scenes.scene2d.InputListener
import com.badlogic.gdx.scenes.scene2d.ui.Button
import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup
@ -24,6 +25,7 @@ import com.unciv.ui.components.extensions.keyShortcuts
import com.unciv.ui.components.extensions.onActivation
import com.unciv.ui.components.extensions.packIfNeeded
import com.unciv.ui.components.extensions.pad
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.IconTextButton
import com.unciv.ui.popups.Popup
import com.unciv.ui.screens.basescreen.BaseScreen
@ -64,7 +66,7 @@ open class TabbedPager(
backgroundColor: Color = BaseScreen.skinStrings.skinConfig.baseColor.darken(0.5f),
private val headerPadding: Float = 10f,
separatorColor: Color = Color.CLEAR,
private val shorcutScreen: BaseScreen? = null,
private val shortcutScreen: BaseScreen? = null,
capacity: Int = 4
) : Table() {
@ -80,7 +82,7 @@ open class TabbedPager(
private set
private val header = Table(BaseScreen.skin)
protected val headerScroll = LinkedScrollPane(horizontalOnly = true, header)
val headerScroll = LinkedScrollPane(horizontalOnly = true, header)
protected var headerHeight = 0f
private val fixedContentScroll = LinkedScrollPane(horizontalOnly = true)
@ -105,6 +107,40 @@ open class TabbedPager(
/** @return Optional second content [Actor], will be placed outside the tab's main [ScrollPane] between header and `content`. Scrolls horizontally only. */
fun getFixedContent(): Actor? = null
/** Sets first row cell's minWidth to the max of the widths of that column over all given tables
*
* Notes:
* - This aligns columns only if the tables are arranged vertically with equal X coordinates.
* - first table determines columns processed, all others must have at least the same column count.
* - Tables are left as needsLayout==true, so while equal width is ensured, you may have to pack if you want to see the value before this is rendered.
*/
fun equalizeColumns(vararg tables: Table) {
for (table in tables)
table.packIfNeeded()
val columns = tables.first().columns
if (tables.any { it.columns < columns })
throw IllegalStateException("IPageExtensions.equalizeColumns needs all tables to have at least the same number of columns as the first one")
val widths = (0 until columns)
.mapTo(ArrayList(columns)) { column ->
tables.maxOf { it.getColumnWidth(column) }
}
for (table in tables) {
for (column in 0 until columns)
table.cells[column].run {
if (actor == null)
// Empty cells ignore minWidth, so just doing Table.add() for an empty cell in the top row will break this. Fix!
setActor<Label>("".toLabel())
else if (Align.isCenterHorizontal(align)) (actor as? Label)?.run {
// minWidth acts like fillX, so Labels will fill and then left-align by default. Fix!
if (!Align.isCenterHorizontal(labelAlign))
setAlignment(Align.center)
}
minWidth(widths[column] - padLeft - padRight)
}
table.invalidate()
}
}
}
//endregion
@ -472,8 +508,8 @@ open class TabbedPager(
if (index !in 0 until pages.size) return false
if (index == activePage) selectPage(-1)
val page = pages.removeAt(index)
header.getCell(page.button).clearActor()
header.cells.removeIndex(index)
val cell = header.getCell(page.button).clearActor()
header.cells.removeValue(cell, true)
return true
}
@ -620,15 +656,9 @@ open class TabbedPager(
val buttonCell: Cell<Button>
if (insertBefore >= 0 && insertBefore < pages.size) {
newIndex = insertBefore
val cellIndex = header.cells.indexOf(header.getCell(pages[insertBefore].button))
pages.add(insertBefore, page)
// Table.addActorAt breaks the Table, it's a Group method that updates children but not cells
// So we add an empty cell and move cell actors around
header.add()
for (i in header.cells.size - 1 downTo insertBefore + 1) {
val actor = header.removeActorAt(i - 1, true) as Button
header.cells[i].setActor<Button>(actor)
}
header.cells[insertBefore].setActor<Button>(page.button)
insertHeaderCellAt(cellIndex).setActor(page.button)
buttonCell = header.getCell(page.button)
} else {
newIndex = pages.size
@ -645,10 +675,40 @@ open class TabbedPager(
return newIndex
}
private fun insertHeaderCellAt(insertBefore: Int): Cell<Actor?> {
if (insertBefore < 0 || insertBefore >= header.cells.size) return header.add()
// Table.addActorAt breaks the Table, it's a Group method that updates children but not cells
// So we add an empty cell and move cell actors around
header.add()
for (i in header.cells.size - 1 downTo insertBefore + 1) {
val actor = header.removeActorAt(i - 1, true)
header.cells[i].setActor<Actor>(actor)
}
return header.cells[insertBefore]
}
private fun addDeferredSecrets() {
while (true) {
val page = deferredSecretPages.removeFirstOrNull() ?: return
addAndShowPage(page, -1)
}
}
/** Gets total width of the header buttons including their padding.
* Header will be scrollable if getHeaderPrefWidth > width. */
fun getHeaderPrefWidth() = header.prefWidth
/** Adds any Actor to the header, e.g. informative labels.
* Must be called _after_ all pages are final, otherwise effects not guaranteed.
* @param leftSide If `true` then [actor] is inserted on the left, otherwise on the right of the page buttons.
*/
fun decorateHeader(actor: Actor, leftSide: Boolean) {
val cell = insertHeaderCellAt(if (leftSide) 0 else -1)
cell.setActor(actor)
if (!leftSide) return
val addWidth = actor.width
for (page in pages) {
page.buttonX += addWidth
}
}
}

View File

@ -597,3 +597,6 @@ object GdxKeyCodeFixes {
else -> Input.Keys.valueOf(name)
}
}
fun Input.areSecretKeysPressed() = isKeyPressed(Input.Keys.SHIFT_RIGHT) &&
(isKeyPressed(Input.Keys.CONTROL_RIGHT) || isKeyPressed(Input.Keys.ALT_RIGHT))

View File

@ -9,6 +9,7 @@ import com.unciv.UncivGame
import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.areSecretKeysPressed
import com.unciv.ui.components.extensions.center
import com.unciv.ui.components.extensions.toCheckBox
import com.unciv.ui.images.ImageGetter
@ -110,7 +111,7 @@ class OptionsPopup(
val content = ModCheckTab(screen)
tabs.addPage("Locate mod errors", content, ImageGetter.getImage("OtherIcons/Mods"), 24f)
}
if (Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT) && (Gdx.input.isKeyPressed(Input.Keys.CONTROL_RIGHT) || Gdx.input.isKeyPressed(Input.Keys.ALT_RIGHT))) {
if (Gdx.input.areSecretKeysPressed()) {
tabs.addPage("Debug", debugTab(this), ImageGetter.getImage("OtherIcons/SecretOptions"), 24f, secret = true)
}

View File

@ -160,7 +160,7 @@ class DetailedStatsPopup(
}
totalTable.row()
// Mini version of EmpireOverviewTab.equalizeColumns - the number columns work thanks to statColMinWidth
// Mini version of IPageExtensions.equalizeColumns - the number columns work thanks to statColMinWidth
headerTable.packIfNeeded()
totalTable.packIfNeeded()
val firstColumnWidth = max(totalTable.getColumnWidth(0), headerTable.getColumnWidth(0))

View File

@ -1,13 +1,9 @@
package com.unciv.ui.screens.overviewscreen
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.logic.civilization.Civilization
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.packIfNeeded
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
abstract class EmpireOverviewTab (
val viewingPlayer: Civilization,
@ -33,37 +29,4 @@ abstract class EmpireOverviewTab (
val gameInfo = viewingPlayer.gameInfo
/** Sets first row cell's minWidth to the max of the widths of that column over all given tables
*
* Notes:
* - This aligns columns only if the tables are arranged vertically with equal X coordinates.
* - first table determines columns processed, all others must have at least the same column count.
* - Tables are left as needsLayout==true, so while equal width is ensured, you may have to pack if you want to see the value before this is rendered.
*/
protected fun equalizeColumns(vararg tables: Table) {
for (table in tables)
table.packIfNeeded()
val columns = tables.first().columns
if (tables.any { it.columns < columns })
throw IllegalStateException("EmpireOverviewTab.equalizeColumns needs all tables to have at least the same number of columns as the first one")
val widths = (0 until columns)
.mapTo(ArrayList(columns)) { column ->
tables.maxOf { it.getColumnWidth(column) }
}
for (table in tables) {
for (column in 0 until columns)
table.cells[column].run {
if (actor == null)
// Empty cells ignore minWidth, so just doing Table.add() for an empty cell in the top row will break this. Fix!
setActor<Label>("".toLabel())
else if (Align.isCenterHorizontal(align)) (actor as? Label)?.run {
// minWidth acts like fillX, so Labels will fill and then left-align by default. Fix!
if (!Align.isCenterHorizontal(labelAlign))
setAlignment(Align.center)
}
minWidth(widths[column] - padLeft - padRight)
}
table.invalidate()
}
}
}

View File

@ -4,21 +4,26 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.unciv.ui.images.ImageGetter
enum class RankingType(val getImage: () -> Image?, val idForSerialization: String) {
enum class RankingType(
label: String?,
val getImage: () -> Image?,
val idForSerialization: String
) {
// production, gold, happiness, and culture already have icons added when the line is `tr()`anslated
Score(
{ ImageGetter.getImage("CityStateIcons/Cultured").apply { color = Color.FIREBRICK } },
"S"
),
Score({ ImageGetter.getImage("CityStateIcons/Cultured").apply { color = Color.FIREBRICK } }, "S"),
Population({ ImageGetter.getStatIcon("Population") }, "N"),
Crop_Yield({ ImageGetter.getStatIcon("Food") }, "C"),
Production({ null }, "P"),
Gold({ null }, "G"),
CropYield("Crop Yield", { ImageGetter.getStatIcon("Food") }, "C"),
Production("P"),
Gold("G"),
Territory({ ImageGetter.getImage("OtherIcons/Hexagon") }, "T"),
Force({ ImageGetter.getImage("OtherIcons/Shield") }, "F"),
Happiness({ null }, "H"),
Happiness("H"),
Technologies({ ImageGetter.getStatIcon("Science") }, "W"),
Culture({ null }, "A");
Culture("A")
;
val label = label ?: name
constructor(getImage: () -> Image?, idForSerialization: String) : this(null, getImage, idForSerialization)
constructor(idForSerialization: String) : this(null, { null }, idForSerialization)
companion object {
fun fromIdForSerialization(s: String): RankingType? =

View File

@ -1,7 +1,9 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
@ -9,58 +11,99 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.ruleset.Victory
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.KeyCharAndCode
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.areSecretKeysPressed
import com.unciv.ui.components.extensions.enable
import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.newgamescreen.NewGameScreen
import com.unciv.ui.screens.pickerscreens.PickerScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
class VictoryScreen(private val worldScreen: WorldScreen) : PickerScreen() {
//TODO someoneHasWon should look at gameInfo.victoryData
//TODO replay slider
class VictoryScreen(
private val worldScreen: WorldScreen,
pageNumber: Int = 0
) : PickerScreen(), RecreateOnResize {
private val gameInfo = worldScreen.gameInfo
private val playerCivInfo = worldScreen.viewingCiv
private val playerCiv = worldScreen.viewingCiv
private val tabs = TabbedPager(separatorColor = Color.WHITE, shortcutScreen = this)
private val headerTable = Table()
private val contentsTable = Table()
internal class CivWithStat(val civ: Civilization, val value: Int) {
constructor(civ: Civilization, category: RankingType) : this(civ, civ.getStatForRanking(category))
}
private var replayTab: VictoryScreenReplay? = null
private enum class VictoryTabs(
val key: Char,
val iconName: String = "",
val caption: String? = null,
val align: Int = Align.topLeft,
val syncScroll: Boolean = true,
val allowAsSecret: Boolean = false
) {
OurStatus('O', "StatIcons/Specialist", caption = "Our status") {
override fun getContent(worldScreen: WorldScreen) = VictoryScreenOurVictory(worldScreen)
override fun isHidden(playerCiv: Civilization) = playerCiv.isSpectator()
},
Global('G', "OtherIcons/Nations", caption = "Global status") {
override fun getContent(worldScreen: WorldScreen) = VictoryScreenGlobalVictory(worldScreen)
},
Demographics('D', "CityStateIcons/Cultured", allowAsSecret = true) {
override fun getContent(worldScreen: WorldScreen) = VictoryScreenDemographics(worldScreen)
override fun isHidden(playerCiv: Civilization) = !UncivGame.Current.settings.useDemographics
},
Rankings('R', "CityStateIcons/Cultured", allowAsSecret = true) {
override fun getContent(worldScreen: WorldScreen) = VictoryScreenCivRankings(worldScreen)
override fun isHidden(playerCiv: Civilization) = UncivGame.Current.settings.useDemographics
},
Replay('P', "OtherIcons/Load", align = Align.top, syncScroll = false, allowAsSecret = true) {
override fun getContent(worldScreen: WorldScreen) = VictoryScreenReplay(worldScreen)
override fun isHidden(playerCiv: Civilization) =
!playerCiv.isSpectator() && playerCiv.gameInfo.victoryData == null && playerCiv.isAlive()
};
abstract fun getContent(worldScreen: WorldScreen): Table
open fun isHidden(playerCiv: Civilization) = false
}
init {
val difficultyLabel = ("{Difficulty}: {${gameInfo.difficulty}}").toLabel()
//**************** Set up the tabs ****************
splitPane.setFirstWidget(tabs)
val iconSize = Constants.defaultFontSize.toFloat()
val tabsTable = Table().apply { defaults().pad(10f) }
val setMyVictoryButton = "Our status".toTextButton().onClick { setOurVictoryTable() }
if (!playerCivInfo.isSpectator()) tabsTable.add(setMyVictoryButton)
val setGlobalVictoryButton = "Global status".toTextButton().onClick { setGlobalVictoryTable() }
tabsTable.add(setGlobalVictoryButton)
val rankingLabel = if (UncivGame.Current.settings.useDemographics) "Demographics" else "Rankings"
val setCivRankingsButton = rankingLabel.toTextButton().onClick { setCivRankingsTable() }
tabsTable.add(setCivRankingsButton)
if (playerCivInfo.isSpectator())
setGlobalVictoryTable()
else
setOurVictoryTable()
for (tab in VictoryTabs.values()) {
val tabHidden = tab.isHidden(playerCiv)
if (tabHidden && !(tab.allowAsSecret && Gdx.input.areSecretKeysPressed()))
continue
val icon = if (tab.iconName.isEmpty()) null else ImageGetter.getImage(tab.iconName)
tabs.addPage(
tab.caption ?: tab.name,
tab.getContent(worldScreen),
icon, iconSize,
scrollAlign = tab.align, syncScroll = tab.syncScroll,
shortcutKey = KeyCharAndCode(tab.key),
secret = tabHidden && tab.allowAsSecret
)
}
tabs.selectPage(pageNumber)
//**************** Set up bottom area - buttons and description label ****************
rightSideButton.isVisible = false
//TODO the following should look at gameInfo.victoryData
var someoneHasWon = false
val playerVictoryType = playerCivInfo.victoryManager.getVictoryTypeAchieved()
val playerVictoryType = playerCiv.victoryManager.getVictoryTypeAchieved()
if (playerVictoryType != null) {
someoneHasWon = true
wonOrLost("You have won a [$playerVictoryType] Victory!", playerVictoryType, true)
}
for (civ in gameInfo.civilizations.filter { it.isMajorCiv() && it != playerCivInfo }) {
for (civ in gameInfo.civilizations.filter { it.isMajorCiv() && it != playerCiv }) {
val civVictoryType = civ.victoryManager.getVictoryTypeAchieved()
if (civVictoryType != null) {
someoneHasWon = true
@ -68,44 +111,41 @@ class VictoryScreen(private val worldScreen: WorldScreen) : PickerScreen() {
}
}
if (playerCivInfo.isDefeated()) {
if (playerCiv.isDefeated()) {
wonOrLost("", null, false)
} else if (!someoneHasWon) {
setDefaultCloseAction()
}
if (playerCivInfo.isSpectator() || someoneHasWon || playerCivInfo.isDefeated()) {
val replayLabel = "Replay"
val replayButton = replayLabel.toTextButton().onClick { setReplayTable() }
tabsTable.add(replayButton)
//**************** Set up floating info panels ****************
// When horizontal screen space is scarce so they would overlap, insert
// them into the scrolling portion of the TabbedPager header instead
tabs.pack()
val topRightPanel = VerticalGroup().apply {
space(5f)
align(Align.right)
addActor("{Game Speed}: {${gameInfo.gameParameters.speed}}".toLabel())
if ("Time" in gameInfo.gameParameters.victoryTypes)
addActor("{Max Turns}: ${gameInfo.gameParameters.maxTurns}".toLabel())
pack()
}
val headerTableRightCell = Table()
val gameSpeedLabel = "{Game Speed}: {${gameInfo.gameParameters.speed}}".toLabel()
headerTableRightCell.add(gameSpeedLabel).row()
if (gameInfo.gameParameters.victoryTypes.contains("Time")) {
val maxTurnsLabel = "{Max Turns}: ${gameInfo.gameParameters.maxTurns}".toLabel()
headerTableRightCell.add(maxTurnsLabel).padTop(5f)
val difficultyLabel = "{Difficulty}: {${gameInfo.difficulty}}".toLabel()
val neededSpace = topRightPanel.width.coerceAtLeast(difficultyLabel.width) * 2 + tabs.getHeaderPrefWidth()
if (neededSpace > stage.width) {
tabs.decorateHeader(difficultyLabel, true)
tabs.decorateHeader(topRightPanel, false)
tabs.headerScroll.fadeScrollBars = false
} else {
val panelY = stage.height - tabs.getRowHeight(0) * 0.5f
stage.addActor(topRightPanel)
topRightPanel.setPosition(stage.width - 10f, panelY, Align.right)
stage.addActor(difficultyLabel)
difficultyLabel.setPosition(10f, panelY, Align.left)
}
val leftCell = headerTable.add(difficultyLabel).padLeft(10f).left()
headerTable.add(tabsTable).expandX().center()
val rightCell = headerTable.add(headerTableRightCell).padRight(10f).right()
headerTable.addSeparator()
headerTable.pack()
// Make the outer cells the same so that the middle one is properly centered
if (leftCell.actorWidth > rightCell.actorWidth) rightCell.width(leftCell.actorWidth)
else leftCell.width(rightCell.actorWidth)
pickerPane.clearChildren()
pickerPane.add(headerTable).growX().row()
pickerPane.add(splitPane).expand().fill()
topTable.add(contentsTable)
}
private fun wonOrLost(description: String, victoryType: String?, hasWon: Boolean) {
val victory = playerCivInfo.gameInfo.ruleset.victories[victoryType]
val victory = playerCiv.gameInfo.ruleset.victories[victoryType]
?: Victory() // This contains our default victory/defeat texts
val endGameMessage = when {
hasWon -> victory.victoryString
@ -130,72 +170,15 @@ class VictoryScreen(private val worldScreen: WorldScreen) : PickerScreen() {
}
}
private fun setOurVictoryTable() {
resetContent(VictoryScreenOurVictory(worldScreen))
}
private fun setGlobalVictoryTable() {
resetContent(VictoryScreenGlobalVictory(worldScreen))
}
private fun setCivRankingsTable() {
resetContent(VictoryScreenCivRankings(worldScreen))
}
private fun setReplayTable() {
if (replayTab == null) replayTab = VictoryScreenReplay(worldScreen)
resetContent(replayTab!!)
replayTab!!.restartTimer()
}
private fun resetContent(newContent: Table) {
replayTab?.resetTimer()
contentsTable.clear()
contentsTable.add(newContent)
override fun show() {
super.show()
tabs.askForPassword(secretHashCode = 2747985)
}
override fun dispose() {
tabs.selectPage(-1) // Tells Replay page to stop its timer
super.dispose()
replayTab?.resetTimer()
}
open class VictoryScreenTab(worldScreen: WorldScreen) : Table(skin) {
protected val gameInfo = worldScreen.gameInfo
protected val playerCivInfo = worldScreen.viewingCiv
// Common "service" for VictoryScreenGlobalVictory and VictoryScreenCivRankings
protected fun getCivGroup(civ: Civilization, afterCivNameText: String, currentPlayer: Civilization): Table {
val civGroup = Table()
var labelText = "{${civ.civName.tr()}}{${afterCivNameText.tr()}}"
var labelColor = Color.WHITE
val backgroundColor: Color
if (civ.isDefeated()) {
civGroup.add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f)
backgroundColor = Color.LIGHT_GRAY
labelColor = Color.BLACK
} else if (currentPlayer == civ // || game.viewEntireMapForDebug
|| currentPlayer.knows(civ)
|| currentPlayer.isDefeated()
|| currentPlayer.victoryManager.hasWon()
) {
civGroup.add(ImageGetter.getNationPortrait(civ.nation, 30f))
backgroundColor = civ.nation.getOuterColor()
labelColor = civ.nation.getInnerColor()
} else {
civGroup.add(ImageGetter.getRandomNationPortrait(30f))
backgroundColor = Color.DARK_GRAY
labelText = Constants.unknownNationName
}
civGroup.background = skinStrings.getUiBackground("VictoryScreen/CivGroup", skinStrings.roundedEdgeRectangleShape, backgroundColor)
val label = labelText.toLabel(labelColor)
label.setAlignment(Align.center)
civGroup.add(label).padLeft(10f)
civGroup.pack()
return civGroup
}
}
override fun recreate(): BaseScreen = VictoryScreen(worldScreen, tabs.activePage)
}

View File

@ -0,0 +1,67 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.logic.civilization.Civilization
import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen
/** Element displaying one Civilization as seen by another Civilization on a rounded-edge background.
* @param civ Civilization to show, with nation icon (depending on alive / known) and name, optionally followed by
* @param separator a separator (untranslated, only if additionalInfo isn't empty) and
* @param additionalInfo some additional info, auto-translated.
* @param currentPlayer Viewing Civilization
*/
internal class VictoryScreenCivGroup(
civ: Civilization,
separator: String,
additionalInfo: String,
currentPlayer: Civilization
) : Table() {
// Note this Table has no skin - works as long as no element tries to get its skin from the parent
constructor(civEntry: VictoryScreen.CivWithStat, currentPlayer: Civilization)
: this(civEntry.civ, ": ", civEntry.value.toString(), currentPlayer)
constructor(civ: Civilization, additionalInfo: String, currentPlayer: Civilization)
// That tr() is only needed to support additionalInfo containing {} because tr() doesn't support nested ones.
: this(civ, "\n", additionalInfo.tr(), currentPlayer)
init {
var labelText = if (additionalInfo.isEmpty()) civ.civName
else "{${civ.civName}}$separator{$additionalInfo}"
val labelColor: Color
val backgroundColor: Color
when {
civ.isDefeated() -> {
add(ImageGetter.getImage("OtherIcons/DisbandUnit")).size(30f)
backgroundColor = Color.LIGHT_GRAY
labelColor = Color.BLACK
}
currentPlayer == civ // || game.viewEntireMapForDebug
|| currentPlayer.knows(civ)
|| currentPlayer.isDefeated()
|| currentPlayer.victoryManager.hasWon() -> {
add(ImageGetter.getNationPortrait(civ.nation, 30f))
backgroundColor = civ.nation.getOuterColor()
labelColor = civ.nation.getInnerColor()
}
else -> {
add(ImageGetter.getRandomNationPortrait(30f))
backgroundColor = Color.DARK_GRAY
labelColor = Color.WHITE
labelText = Constants.unknownNationName
}
}
background = BaseScreen.skinStrings.getUiBackground("VictoryScreen/CivGroup", BaseScreen.skinStrings.roundedEdgeRectangleShape, backgroundColor)
val label = labelText.toLabel(labelColor)
label.setAlignment(Align.center)
add(label).padLeft(10f)
}
}

View File

@ -1,87 +1,48 @@
package com.unciv.ui.screens.victoryscreen
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.Civilization
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
class VictoryScreenCivRankings(
private val worldScreen: WorldScreen
) : VictoryScreen.VictoryScreenTab(worldScreen) {
worldScreen: WorldScreen
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
private val header = Table()
init {
defaults().pad(5f)
defaults().pad(10f)
val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() }
if (UncivGame.Current.settings.useDemographics) buildDemographicsTable(majorCivs)
else buildRankingsTable(majorCivs)
}
enum class RankLabels { Rank, Value, Best, Average, Worst}
private fun buildDemographicsTable(majorCivs: List<Civilization>) {
buildDemographicsHeaders()
for (rankLabel in RankLabels.values()) {
row()
add(rankLabel.name.toLabel())
for (category in RankingType.values()) {
val aliveMajorCivsSorted = majorCivs.filter{ it.isAlive() }.sortedByDescending { it.getStatForRanking(category) }
fun addRankCivGroup(civ: Civilization) { // local function for reuse of getting and formatting civ stats
add(getCivGroup(civ, ": " + civ.getStatForRanking(category).toString(), playerCivInfo)).fillX()
}
@Suppress("NON_EXHAUSTIVE_WHEN") // RankLabels.Demographic treated above
when (rankLabel) {
RankLabels.Rank -> add((aliveMajorCivsSorted.indexOfFirst { it == worldScreen.viewingCiv } + 1).toLabel())
RankLabels.Value -> addRankCivGroup(worldScreen.viewingCiv)
RankLabels.Best -> addRankCivGroup(aliveMajorCivsSorted.firstOrNull()!!)
RankLabels.Average -> add((aliveMajorCivsSorted.sumOf { it.getStatForRanking(category) } / aliveMajorCivsSorted.size).toLabel())
RankLabels.Worst -> addRankCivGroup(aliveMajorCivsSorted.lastOrNull()!!)
}
}
}
}
private fun buildDemographicsHeaders() {
val demoLabel = Table().apply { defaults().pad(5f) }
demoLabel.add("Demographic".toLabel()).row()
demoLabel.addSeparator().fillX()
add(demoLabel)
val majorCivs = worldScreen.gameInfo.civilizations.filter { it.isMajorCiv() }
for (category in RankingType.values()) {
val headers = Table().apply { defaults().pad(5f) }
val textAndIcon = Table().apply { defaults() }
val textAndIcon = Table()
val columnImage = category.getImage()
if (columnImage != null) textAndIcon.add(columnImage).center().size(Constants.defaultFontSize.toFloat() * 0.75f).padRight(2f).padTop(-2f)
textAndIcon.add(category.name.replace('_', ' ').toLabel()).row()
headers.add(textAndIcon)
headers.addSeparator()
add(headers)
}
}
if (columnImage != null)
textAndIcon.add(columnImage).size(Constants.defaultFontSize.toFloat() * 0.75f)
.padRight(2f).padTop(-2f)
textAndIcon.add(category.label.toLabel()).row()
header.add(textAndIcon).pad(10f)
private fun buildRankingsTable(majorCivs: List<Civilization>) {
for (category in RankingType.values()) {
val column = Table().apply { defaults().pad(5f) }
val textAndIcon = Table().apply { defaults() }
val columnImage = category.getImage()
if (columnImage != null) textAndIcon.add(columnImage).size(Constants.defaultFontSize.toFloat() * 0.75f).padRight(2f).padTop(-2f)
textAndIcon.add(category.name.replace('_' , ' ').toLabel()).row()
column.add(textAndIcon)
column.addSeparator()
for (civ in majorCivs.sortedByDescending { it.getStatForRanking(category) }) {
column.add(getCivGroup(civ, ": " + civ.getStatForRanking(category).toString(), playerCivInfo)).fillX().row()
val column = Table().apply { defaults().space(10f) }
val civData = majorCivs
.map { VictoryScreen.CivWithStat(it, category) }
.sortedByDescending { it.value }
for (civEntry in civData) {
column.add(VictoryScreenCivGroup(civEntry, worldScreen.viewingCiv)).fillX().row()
}
add(column)
}
header.addSeparator(Color.GRAY)
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {
equalizeColumns(header, this)
}
override fun getFixedContent() = header
}

View File

@ -0,0 +1,70 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.Constants
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
import kotlin.math.roundToInt
class VictoryScreenDemographics(
worldScreen: WorldScreen
) : Table(BaseScreen.skin) {
private val playerCiv = worldScreen.viewingCiv
private enum class RankLabels { Rank, Value, Best, Average, Worst }
init {
defaults().pad(5f)
val majorCivs = worldScreen.gameInfo.civilizations.filter { it.isMajorCiv() }
buildDemographicsHeaders()
for (rankLabel in RankLabels.values()) {
row()
add(rankLabel.name.toLabel())
for (category in RankingType.values()) {
val aliveMajorCivsSorted = majorCivs.filter { it.isAlive() || it == playerCiv }
.map { VictoryScreen.CivWithStat(it, category) }
.sortedByDescending { it.value }
fun addRankCivGroup(civEntry: VictoryScreen.CivWithStat) {
add(VictoryScreenCivGroup(civEntry, playerCiv)).fillX()
}
@Suppress("NON_EXHAUSTIVE_WHEN") // RankLabels.Demographic treated above
when (rankLabel) {
RankLabels.Rank -> add((aliveMajorCivsSorted.indexOfFirst { it.civ == playerCiv } + 1).toLabel())
RankLabels.Value -> addRankCivGroup(aliveMajorCivsSorted.first { it.civ == playerCiv })
RankLabels.Best -> addRankCivGroup(aliveMajorCivsSorted.first())
RankLabels.Average -> add((aliveMajorCivsSorted.sumOf { it.value }.toFloat() / aliveMajorCivsSorted.size).roundToInt().toLabel())
RankLabels.Worst -> addRankCivGroup(aliveMajorCivsSorted.last())
}
}
}
}
private fun buildDemographicsHeaders() {
val demoLabel = Table().apply { defaults().pad(5f) }
demoLabel.add("Demographic".toLabel()).row()
demoLabel.addSeparator().fillX()
add(demoLabel)
for (category in RankingType.values()) {
val headers = Table().apply { defaults().pad(5f) }
val textAndIcon = Table().apply { defaults() }
val columnImage = category.getImage()
if (columnImage != null)
textAndIcon.add(columnImage).center()
.size(Constants.defaultFontSize.toFloat() * 0.75f)
.padRight(2f).padTop(-2f)
textAndIcon.add(category.label.toLabel()).row()
headers.add(textAndIcon)
headers.addSeparator()
add(headers)
}
}
}

View File

@ -1,47 +1,54 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.civilization.Civilization
import com.unciv.models.translations.tr
import com.unciv.models.ruleset.Victory
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
class VictoryScreenGlobalVictory(
worldScreen: WorldScreen
) : VictoryScreen.VictoryScreenTab(worldScreen) {
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
private val header = Table()
init {
val gameInfo = worldScreen.gameInfo
val majorCivs = gameInfo.civilizations.asSequence().filter { it.isMajorCiv() }
val victoriesToShow = gameInfo.getEnabledVictories()
defaults().pad(10f)
val majorCivs = gameInfo.civilizations.filter { it.isMajorCiv() }
val enabledVictoryTypes = gameInfo.gameParameters.victoryTypes
val victoriesToShow = gameInfo.ruleset.victories.filter {
!it.value.hiddenInVictoryScreen && enabledVictoryTypes.contains(it.key)
for ((victoryName, victory) in victoriesToShow) {
header.add("[$victoryName] Victory".toLabel()).pad(10f)
add(getColumn(majorCivs, victory, worldScreen.viewingCiv))
}
for (victory in victoriesToShow) {
add(getGlobalVictoryColumn(majorCivs, victory.key))
}
header.addSeparator(Color.GRAY)
}
private fun getGlobalVictoryColumn(majorCivs: List<Civilization>, victory: String): Table {
val victoryColumn = Table().apply { defaults().pad(10f) }
victoryColumn.add("[$victory] Victory".toLabel()).row()
victoryColumn.addSeparator()
for (civ in majorCivs.filter { !it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) {
val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!"
victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row()
private fun getColumn(
majorCivs: Sequence<Civilization>,
victory: Victory,
playerCiv: Civilization
) = Table().apply {
defaults().pad(10f)
val sortedCivs = majorCivs.sortedWith(
compareBy<Civilization> { it.isDefeated() }
.thenBy { it.victoryManager.amountMilestonesCompleted(victory) }
)
for (civ in sortedCivs) {
val buttonText = civ.victoryManager.getNextMilestone(victory)
?.getVictoryScreenButtonHeaderText(false, civ)
?: "Done!"
add(VictoryScreenCivGroup(civ, buttonText, playerCiv)).fillX().row()
}
for (civ in majorCivs.filter { it.isDefeated() }.sortedByDescending { it.victoryManager.amountMilestonesCompleted(victory) }) {
val buttonText = civ.victoryManager.getNextMilestone(victory)?.getVictoryScreenButtonHeaderText(false, civ) ?: "Done!"
victoryColumn.add(getCivGroup(civ, "\n" + buttonText.tr(), playerCivInfo)).fillX().row()
}
return victoryColumn
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {
equalizeColumns(header, this)
}
override fun getFixedContent() = header
}

View File

@ -1,54 +1,61 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.Victory
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
class VictoryScreenOurVictory(
worldScreen: WorldScreen
) : VictoryScreen.VictoryScreenTab(worldScreen) {
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
private val header = Table()
init {
defaults().pad(10f)
val gameInfo = worldScreen.gameInfo
val victoriesToShow = gameInfo.getEnabledVictories()
for (victory in victoriesToShow) {
add("[${victory.key}] Victory".toLabel())
defaults().pad(10f)
for ((victoryName, victory) in victoriesToShow) {
header.add("[$victoryName] Victory".toLabel()).pad(10f)
add(getColumn(victory, worldScreen.viewingCiv))
}
row()
for (victory in victoriesToShow) {
add(getOurVictoryColumn(victory.key))
}
row()
for (victory in victoriesToShow) {
add(victory.value.victoryScreenHeader.toLabel())
for (victory in victoriesToShow.values) {
add(victory.victoryScreenHeader.toLabel())
}
header.addSeparator(Color.GRAY)
}
private fun getOurVictoryColumn(victory: String): Table {
val victoryObject = gameInfo.ruleset.victories[victory]!!
private fun getColumn(victory: Victory, playerCiv: Civilization): Table {
val table = Table()
table.defaults().pad(5f)
table.defaults().space(10f)
var firstIncomplete = true
for (milestone in victoryObject.milestoneObjects) {
val completionStatus =
when {
milestone.hasBeenCompletedBy(playerCivInfo) -> Victory.CompletionStatus.Completed
firstIncomplete -> {
firstIncomplete = false
Victory.CompletionStatus.Partially
}
else -> Victory.CompletionStatus.Incomplete
}
for (button in milestone.getVictoryScreenButtons(completionStatus, playerCivInfo)) {
for (milestone in victory.milestoneObjects) {
val completionStatus = when {
milestone.hasBeenCompletedBy(playerCiv) -> Victory.CompletionStatus.Completed
firstIncomplete -> {
firstIncomplete = false
Victory.CompletionStatus.Partially
}
else -> Victory.CompletionStatus.Incomplete
}
for (button in milestone.getVictoryScreenButtons(completionStatus, playerCiv)) {
table.add(button).row()
}
}
return table
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {
equalizeColumns(header, this)
}
override fun getFixedContent() = header
}

View File

@ -1,27 +1,29 @@
package com.unciv.ui.screens.victoryscreen
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Timer
import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.YearTextUtil
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
class VictoryScreenReplay(
worldScreen: WorldScreen
) : VictoryScreen.VictoryScreenTab(worldScreen) {
) : Table(BaseScreen.skin), TabbedPager.IPageExtensions {
private val gameInfo = worldScreen.gameInfo
private var replayTimer : Timer.Task? = null
private val yearLabel = "".toLabel()
private val replayMap = ReplayMap(gameInfo.tileMap)
private val header = Table()
init {
defaults().pad(10f)
add(yearLabel).row()
add(replayMap).row()
// restartTimer() - done later!
header.add(yearLabel).pad(10f)
add(replayMap).pad(10f)
}
internal fun restartTimer() {
private fun restartTimer() {
replayTimer?.cancel()
val firstTurn = gameInfo.historyStartTurn
val finalTurn = gameInfo.turns
@ -39,7 +41,7 @@ class VictoryScreenReplay(
)
}
internal fun resetTimer() {
private fun resetTimer() {
replayTimer?.cancel()
replayTimer = null
}
@ -54,4 +56,14 @@ class VictoryScreenReplay(
)
replayMap.update(turn)
}
override fun activated(index: Int, caption: String, pager: TabbedPager) {
restartTimer()
}
override fun deactivated(index: Int, caption: String, pager: TabbedPager) {
resetTimer()
}
override fun getFixedContent() = header
}