From 6104a25d91e0bb58e7f228f4eeb239bdbd93a396 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Sun, 18 Dec 2022 21:34:55 +0200 Subject: [PATCH] Readded stats drilldown --- .../com/unciv/ui/cityscreen/CityStatsTable.kt | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt index 1347183ee6..7b6ece8fef 100644 --- a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt @@ -2,6 +2,7 @@ package com.unciv.ui.cityscreen import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table @@ -10,11 +11,15 @@ import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.city.CityFlags import com.unciv.logic.city.CityFocus +import com.unciv.logic.city.CityInfo +import com.unciv.logic.city.CityStats +import com.unciv.logic.city.StatTreeNode import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.CivilopediaScreen +import com.unciv.ui.images.IconCircleGroup import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ExpanderTab @@ -24,6 +29,7 @@ import com.unciv.ui.utils.extensions.colorFromRGB import com.unciv.ui.utils.extensions.onClick import com.unciv.ui.utils.extensions.surroundWithCircle import com.unciv.ui.utils.extensions.toLabel +import java.text.DecimalFormat import kotlin.math.ceil import kotlin.math.round import com.unciv.ui.utils.AutoScrollPane as ScrollPane @@ -103,6 +109,9 @@ class CityStatsTable(val cityScreen: CityScreen): Table() { if (cityInfo.religion.getNumberOfFollowers().isNotEmpty() && cityInfo.civInfo.gameInfo.isReligionEnabled()) addReligionInfo() + + addStatInfo() + addGreatPersonPointInfo(cityInfo) addBuildingsInfo() upperTable.pack() @@ -292,4 +301,151 @@ class CityStatsTable(val cityScreen: CityScreen): Table() { lowerTable.add(expanderTab).growX().row() } + + private fun addStatsToHashmap( + statTreeNode: StatTreeNode, + hashMap: HashMap, + stat: Stat, + showDetails: Boolean, + indentation: Int = 0 + ) { + for ((name, child) in statTreeNode.children) { + val statAmount = child.totalStats[stat] + if (statAmount == 0f) continue + hashMap["- ".repeat(indentation) + name.tr()] = statAmount + if (showDetails) addStatsToHashmap(child, hashMap, stat, showDetails, indentation + 1) + } + } + + private fun Table.addStatInfo() { + val cityStats = cityScreen.city.cityStats + + val showFaith = cityScreen.city.civInfo.gameInfo.isReligionEnabled() + for (stat in Stat.values()) { + if (stat == Stat.Faith && !showFaith) continue + val statValuesTable = Table() + statValuesTable.touchable = Touchable.enabled + addCategory(stat.name, statValuesTable) + + updateStatValuesTable(stat, cityStats, statValuesTable) + } + } + + private fun updateStatValuesTable( + stat: Stat, + cityStats: CityStats, + statValuesTable: Table, + showDetails:Boolean = false + ) { + statValuesTable.clear() + statValuesTable.defaults().pad(2f) + statValuesTable.onClick { + updateStatValuesTable( + stat, + cityStats, + statValuesTable, + !showDetails + ) + onContentResize() + } + + val relevantBaseStats = LinkedHashMap() + + if (stat != Stat.Happiness) + addStatsToHashmap(cityStats.baseStatTree, relevantBaseStats, stat, showDetails) + else relevantBaseStats.putAll(cityStats.happinessList) + for (key in relevantBaseStats.keys.toList()) + if (relevantBaseStats[key] == 0f) relevantBaseStats.remove(key) + + if (relevantBaseStats.isEmpty()) return + + statValuesTable.add("Base values".toLabel(fontSize = FONT_SIZE_STAT_INFO_HEADER)).pad(4f) + .colspan(2).row() + var sumOfAllBaseValues = 0f + for (entry in relevantBaseStats) { + val specificStatValue = entry.value + if (!entry.key.startsWith('-')) + sumOfAllBaseValues += specificStatValue + statValuesTable.add(entry.key.toLabel()).left() + statValuesTable.add(specificStatValue.toOneDecimalLabel()).row() + } + statValuesTable.addSeparator() + statValuesTable.add("Total".toLabel()) + statValuesTable.add(sumOfAllBaseValues.toOneDecimalLabel()).row() + + val relevantBonuses = LinkedHashMap() + addStatsToHashmap(cityStats.statPercentBonusTree, relevantBonuses, stat, showDetails) + + val totalBonusStats = cityStats.statPercentBonusTree.totalStats + if (totalBonusStats[stat] != 0f) { + statValuesTable.add("Bonuses".toLabel(fontSize = FONT_SIZE_STAT_INFO_HEADER)).colspan(2) + .padTop(20f).row() + for ((source, bonusAmount) in relevantBonuses) { + statValuesTable.add(source.toLabel()).left() + statValuesTable.add(bonusAmount.toPercentLabel()).row() // negative bonus + } + statValuesTable.addSeparator() + statValuesTable.add("Total".toLabel()) + statValuesTable.add(totalBonusStats[stat].toPercentLabel()).row() // negative bonus + + + statValuesTable.add("Final".toLabel(fontSize = FONT_SIZE_STAT_INFO_HEADER)).colspan(2) + .padTop(20f).row() + var finalTotal = 0f + for (entry in cityStats.finalStatList) { + val specificStatValue = entry.value[stat] + finalTotal += specificStatValue + if (specificStatValue == 0f) continue + statValuesTable.add(entry.key.toLabel()) + statValuesTable.add(specificStatValue.toOneDecimalLabel()).row() + } + statValuesTable.addSeparator() + statValuesTable.add("Total".toLabel()) + statValuesTable.add(finalTotal.toOneDecimalLabel()).row() + } + + statValuesTable.pack() + + if (stat != Stat.Happiness) { + val toggleButton = getToggleButton(showDetails) + statValuesTable.addActor(toggleButton) + toggleButton.setPosition(0f, statValuesTable.height, Align.topLeft) + } + + statValuesTable.padBottom(4f) + } + + private fun getToggleButton(showDetails: Boolean): IconCircleGroup { + val label = (if (showDetails) "-" else "+").toLabel() + label.setAlignment(Align.center) + return label + .surroundWithCircle(25f, color = BaseScreen.skinStrings.skinConfig.baseColor) + .surroundWithCircle(27f, false) + } + + private fun Table.addGreatPersonPointInfo(cityInfo: CityInfo) { + val greatPersonPoints = cityInfo.getGreatPersonPointsForNextTurn() + val allGreatPersonNames = greatPersonPoints.asSequence().flatMap { it.value.keys }.distinct() + for (greatPersonName in allGreatPersonNames) { + val expanderName = "[$greatPersonName] points" + val greatPersonTable = Table() + addCategory(expanderName, greatPersonTable) + for ((source, gppCounter) in greatPersonPoints) { + val gppPointsFromSource = gppCounter[greatPersonName]!! + if (gppPointsFromSource == 0) continue + greatPersonTable.add(source.toLabel()).padRight(10f) + greatPersonTable.add(gppPointsFromSource.toLabel()).row() + } + } + } + + companion object { + private const val FONT_SIZE_STAT_INFO_HEADER = 22 + + private fun Float.toPercentLabel() = + "${if (this>0f) "+" else ""}${DecimalFormat("0.#").format(this)}%".toLabel() + private fun Float.toOneDecimalLabel() = + DecimalFormat("0.#").format(this).toLabel() + } + }