Stat bonus drilldown (#6053)

* Step 1 - converted stat bonus list to tree.
No visual difference yet, since the stat bonus list is still generated in the same way.

* Step 2 - updateStatPercentBonusList converted to tree form

* Step 3 - buildings converted to tree form - now user visible!

* Step 4 - Bonuses from uniques are now drilldownable

* Removed unneeded todo

* Welp, turns out I forgot to apply conditionals
This commit is contained in:
Yair Morgenstern 2022-01-26 22:42:05 +02:00 committed by GitHub
parent 824efcb1a9
commit 84ef8944d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 58 deletions

View File

@ -253,9 +253,7 @@ object SpecificUnitAutomation {
val relatedStat = improvement.maxByOrNull { it.value }?.key ?: Stat.Culture
val citiesByStatBoost = unit.civInfo.cities.sortedByDescending {
val stats = Stats()
for (bonus in it.cityStats.statPercentBonusList.values) stats.add(bonus)
stats[relatedStat]
it.cityStats.statPercentBonusTree.totalStats[relatedStat]
}

View File

@ -107,16 +107,6 @@ class CityConstructions {
return maintenanceCost
}
/**
* @return Bonus (%) [Stats] provided by all built buildings in city
*/
fun getStatPercentBonuses(): Stats {
val stats = Stats()
for (building in getBuiltBuildings())
stats.add(building.getStatPercentageBonuses(cityInfo))
return stats
}
fun getCityProductionTextForCityButton(): String {
val currentConstructionSnapshot = currentConstructionFromQueue // See below
var result = currentConstructionSnapshot.tr()

View File

@ -45,12 +45,13 @@ class StatTreeNode {
}
}
val totalStats: Stats by lazy {
val toReturn = Stats()
if (innerStats != null) toReturn.add(innerStats!!)
for (child in children.values) toReturn.add(child.totalStats)
toReturn
}
val totalStats: Stats
get() {
val toReturn = Stats()
if (innerStats != null) toReturn.add(innerStats!!)
for (child in children.values) toReturn.add(child.totalStats)
return toReturn
}
}
/** Holds and calculates [Stats] for a city.
@ -63,10 +64,10 @@ class CityStats(val cityInfo: CityInfo) {
var baseStatTree = StatTreeNode()
var baseStatList = LinkedHashMap<String, Stats>()
var statPercentBonusList = LinkedHashMap<String, Stats>()
var statPercentBonusTree = StatTreeNode()
// Computed from baseStatList and statPercentBonusList - this is so the players can see a breakdown
var finalStatList = LinkedHashMap<String, Stats>()
@ -124,6 +125,12 @@ class CityStats(val cityInfo: CityInfo) {
return stats
}
private fun addStatPercentBonusesFromBuildings(statPercentBonusTree: StatTreeNode) {
for (building in cityInfo.cityConstructions.getBuiltBuildings())
statPercentBonusTree.addStats(building.getStatPercentageBonuses(cityInfo), "Buildings", building.name)
}
private fun getStatsFromCityStates(): Stats {
val stats = Stats()
@ -265,10 +272,11 @@ class CityStats(val cityInfo: CityInfo) {
return stats
}
private fun getStatsPercentBonusesFromUniquesBySource(currentConstruction: IConstruction):StatMap {
val sourceToStats = StatMap()
fun addUniqueStats(unique: Unique, stat:Stat, amount:Float) {
sourceToStats.add(getSourceNameForUnique(unique), Stats().add(stat, amount))
private fun getStatsPercentBonusesFromUniquesBySource(currentConstruction: IConstruction): StatTreeNode {
val sourceToStats = StatTreeNode()
fun addUniqueStats(unique:Unique, stat:Stat, amount:Float) {
sourceToStats.addStats(Stats().add(stat, amount), getSourceNameForUnique(unique), unique.sourceObjectName ?: "")
}
for (unique in cityInfo.getMatchingUniques(UniqueType.StatPercentBonus)) {
@ -472,25 +480,24 @@ class CityStats(val cityInfo: CityInfo) {
}
private fun updateStatPercentBonusList(currentConstruction: IConstruction, localBuildingUniques: Sequence<Unique>) {
val newStatPercentBonusList = StatMap()
private fun updateStatPercentBonusList(currentConstruction: IConstruction) {
val newStatsBonusTree = StatTreeNode()
newStatPercentBonusList["Golden Age"] = getStatPercentBonusesFromGoldenAge(cityInfo.civInfo.goldenAges.isGoldenAge())
.plus(cityInfo.cityConstructions.getStatPercentBonuses()) // This function is to be deprecated but it'll take a while.
newStatPercentBonusList["Railroads"] = getStatPercentBonusesFromRailroad() // Name chosen same as tech, for translation, but theoretically independent
newStatPercentBonusList["Puppet City"] = getStatPercentBonusesFromPuppetCity()
newStatPercentBonusList["Unit Supply"] = getStatPercentBonusesFromUnitSupply()
newStatsBonusTree.addStats(getStatPercentBonusesFromGoldenAge(cityInfo.civInfo.goldenAges.isGoldenAge()),"Golden Age")
addStatPercentBonusesFromBuildings(newStatsBonusTree)
newStatsBonusTree.addStats(getStatPercentBonusesFromRailroad(), "Railroad")
newStatsBonusTree.addStats(getStatPercentBonusesFromPuppetCity(), "Puppet City")
newStatsBonusTree.addStats(getStatPercentBonusesFromUnitSupply(), "Unit Supply")
for ((source, stats) in getStatsPercentBonusesFromUniquesBySource(currentConstruction))
newStatPercentBonusList.add(source, stats)
newStatsBonusTree.add(getStatsPercentBonusesFromUniquesBySource(currentConstruction))
if (UncivGame.Current.superchargedForDebug) {
val stats = Stats()
for (stat in Stat.values()) stats[stat] = 10000f
newStatPercentBonusList["Supercharged"] = stats
newStatsBonusTree.addStats(stats, "Supercharged")
}
statPercentBonusList = newStatPercentBonusList
statPercentBonusTree = newStatsBonusTree
}
/** Does not update tile stats - instead, updating tile stats updates this */
@ -498,17 +505,12 @@ class CityStats(val cityInfo: CityInfo) {
updateTileStats:Boolean = true) {
if (updateTileStats) updateTileStats()
// We calculate this here for concurrency reasons
// If something needs this, we pass this through as a parameter
val localBuildingUniques = cityInfo.cityConstructions.builtBuildingUniqueMap.getAllUniques()
// We need to compute Tile yields before happiness
val statsFromBuildings = cityInfo.cityConstructions.getStats() // this is performance heavy, so calculate once
updateBaseStatList(statsFromBuildings)
updateCityHappiness(statsFromBuildings)
updateStatPercentBonusList(currentConstruction, localBuildingUniques)
updateStatPercentBonusList(currentConstruction)
updateFinalStatList(currentConstruction) // again, we don't edit the existing currentCityStats directly, in order to avoid concurrency exceptions
@ -525,8 +527,7 @@ class CityStats(val cityInfo: CityInfo) {
for ((key, value) in baseStatTree.children)
newFinalStatList[key] = value.totalStats.clone()
val statPercentBonusesSum = Stats()
for (bonus in statPercentBonusList.values) statPercentBonusesSum.add(bonus)
val statPercentBonusesSum = statPercentBonusTree.totalStats
for (entry in newFinalStatList.values)
entry.production *= statPercentBonusesSum.production.toPercent()

View File

@ -168,7 +168,8 @@ class TechManager {
var allCitiesScience = 0f
civInfo.cities.forEach { it ->
val totalBaseScience = it.cityStats.baseStatTree.totalStats.science
val totalBonusPercents = it.cityStats.statPercentBonusList.filter { it.key != "Policies" }.values.map { it.science }.sum()
val totalBonusPercents = it.cityStats.statPercentBonusTree.children.asSequence()
.filter { it.key != "Policies" }.map { it.value.totalStats.science }.sum()
allCitiesScience += totalBaseScience * totalBonusPercents.toPercent()
}
scienceOfLast8Turns[civInfo.gameInfo.turns % 8] = allCitiesScience.toInt()

View File

@ -19,7 +19,7 @@ interface IHasUniques {
* But making this a function is relevant for future "unify Unciv object" plans ;)
* */
fun getUniqueTarget(): UniqueTarget
fun getMatchingUniques(uniqueTemplate: String, stateForConditionals: StateForConditionals? = null): Sequence<Unique> {
val matchingUniques = uniqueMap[uniqueTemplate] ?: return sequenceOf()
return matchingUniques.asSequence().filter { it.conditionalsApply(stateForConditionals) }

View File

@ -149,7 +149,9 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(BaseScreen.skin)
private fun addStatsToHashmap(statTreeNode: StatTreeNode, hashMap: HashMap<String, Float>, stat:Stat,
showDetails:Boolean, indentation:Int=0) {
for ((name, child) in statTreeNode.children) {
hashMap["- ".repeat(indentation) + name] = child.totalStats[stat]
val statAmount = child.totalStats[stat]
if (statAmount == 0f) continue
hashMap["- ".repeat(indentation) + name] = statAmount
if (showDetails) addStatsToHashmap(child, hashMap, stat, showDetails, indentation + 1)
}
}
@ -207,23 +209,22 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(BaseScreen.skin)
statValuesTable.add("Total".toLabel())
statValuesTable.add(sumOfAllBaseValues.toOneDecimalLabel()).row()
val relevantBonuses = cityStats.statPercentBonusList.filter { it.value[stat] != 0f }
if (relevantBonuses.isNotEmpty()) {
val relevantBonuses = LinkedHashMap<String, Float>()
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()
var sumOfBonuses = 0f
for (entry in relevantBonuses) {
val specificStatValue = entry.value[stat]
sumOfBonuses += specificStatValue
statValuesTable.add(entry.key.toLabel())
statValuesTable.add(specificStatValue.toPercentLabel()).row() // negative bonus
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(sumOfBonuses.toPercentLabel()).row() // negative bonus
}
statValuesTable.add(totalBonusStats[stat].toPercentLabel()).row() // negative bonus
if (stat != Stat.Happiness) {
statValuesTable.add("Final".toLabel(fontSize = FONT_SIZE_STAT_INFO_HEADER)).colspan(2)
.padTop(20f).row()
var finalTotal = 0f