Unhappiness effects (#9188)

* Block growth if Unique says so - even with full stores

* Display unhappiness rejection reason for Settlers

* Display unhappiness effects in Overview Stats

* Display unhappiness effects in Overview Stats - simplify
This commit is contained in:
SomeTroglodyte
2023-04-16 13:21:58 +02:00
committed by GitHub
parent 90cac15c7f
commit dea3256c93
6 changed files with 74 additions and 28 deletions

View File

@ -593,7 +593,7 @@ class CityStats(val city: City) {
val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() }
newFinalStatList.add(
getSourceNameForUnique(growthNullifyingUnique),
Stats().apply { this[Stat.Food] = amountToRemove.toFloat() }
Stats(food = amountToRemove.toFloat())
)
}

View File

@ -102,20 +102,26 @@ class CityPopulationManager : IsPartOfGameInfoSerialization {
if (population > 1) addPopulation(-1)
foodStored = 0
}
if (foodStored >= getFoodToNextPopulation()) { // growth!
foodStored -= getFoodToNextPopulation()
var percentOfFoodCarriedOver =
city.getMatchingUniques(UniqueType.CarryOverFood)
.filter { city.matchesFilter(it.params[1]) }
.sumOf { it.params[0].toInt() }
// Try to avoid runaway food gain in mods, just in case
if (percentOfFoodCarriedOver > 95) percentOfFoodCarriedOver = 95
foodStored += (getFoodToNextPopulation() * percentOfFoodCarriedOver / 100f).toInt()
addPopulation(1)
city.updateCitizens = true
city.civ.addNotification("[${city.name}] has grown!", city.location,
NotificationCategory.Cities, NotificationIcon.Growth)
}
val foodNeededToGrow = getFoodToNextPopulation()
if (foodStored < foodNeededToGrow) return
// What if the stores are already over foodNeededToGrow but NullifiesGrowth is in effect?
// We could simply test food==0 - but this way NullifiesStat(food) will still allow growth:
if (city.getMatchingUniques(UniqueType.NullifiesGrowth).any())
return
// growth!
foodStored -= foodNeededToGrow
val percentOfFoodCarriedOver =
city.getMatchingUniques(UniqueType.CarryOverFood)
.filter { city.matchesFilter(it.params[1]) }
.sumOf { it.params[0].toInt() }
.coerceAtMost(95) // Try to avoid runaway food gain in mods, just in case
foodStored += (foodNeededToGrow * percentOfFoodCarriedOver / 100f).toInt()
addPopulation(1)
city.updateCitizens = true
city.civ.addNotification("[${city.name}] has grown!", city.location,
NotificationCategory.Cities, NotificationIcon.Growth)
}
fun addPopulation(count: Int) {

View File

@ -130,6 +130,7 @@ class RejectionReason(val type: RejectionReasonType,
RejectionReasonType.RequiresBuildingInAllCities,
RejectionReasonType.RequiresBuildingInThisCity,
RejectionReasonType.RequiresBuildingInSomeCity,
RejectionReasonType.CannotBeBuiltUnhappiness,
RejectionReasonType.PopulationRequirement,
RejectionReasonType.ConsumesResources,
RejectionReasonType.CanOnlyBePurchased,
@ -160,6 +161,7 @@ enum class RejectionReasonType(val shouldShow: Boolean, val errorMessage: String
UniqueToOtherNation(false, "Unique to another nation"),
ReplacedByOurUnique(false, "Our unique replaces this"),
CannotBeBuilt(false, "Cannot be built by this nation"),
CannotBeBuiltUnhappiness(true, "Unhappiness"),
Obsoleted(false, "Obsolete"),
RequiresTech(false, "Required tech not researched"),

View File

@ -162,7 +162,7 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
UniqueType.ConditionalWhenBelowAmountResource -> state.civInfo != null
&& state.civInfo.getCivResourcesByName()[condition.params[1]]!! < condition.params[0].toInt()
UniqueType.ConditionalHappy ->
state.civInfo != null && state.civInfo.stats.statsForNextTurn.happiness >= 0
state.civInfo != null && state.civInfo.stats.happiness >= 0
UniqueType.ConditionalBetweenHappiness ->
state.civInfo != null
&& condition.params[0].toInt() <= state.civInfo.stats.happiness
@ -379,4 +379,3 @@ fun ArrayList<TemporaryUnique>.getMatchingUniques(uniqueType: UniqueType, stateF
.map { it.uniqueObject }
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) }
}

View File

@ -9,6 +9,7 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
@ -141,7 +142,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
yieldAll(getRejectionReasons(civInfo, cityConstructions.city))
}
fun getRejectionReasons(civ: Civilization, city:City?=null): Sequence<RejectionReason> {
fun getRejectionReasons(civ: Civilization, city: City? = null): Sequence<RejectionReason> {
val result = mutableListOf<RejectionReason>()
if (requiredTech != null && !civ.tech.isResearched(requiredTech!!))
result.add(RejectionReasonType.RequiresTech.toInstance("$requiredTech not researched"))
@ -189,16 +190,15 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
}
}
for (unique in civ.getMatchingUniques(UniqueType.CannotBuildUnits))
if (this@BaseUnit.matchesFilter(unique.params[0])) {
if (unique.conditionals.any { it.type == UniqueType.ConditionalBelowHappiness }) {
result.add(
RejectionReasonType.CannotBeBuilt.toInstance(
unique.text,
true
)
)
} else result.add(RejectionReasonType.CannotBeBuilt.toInstance())
val stateForConditionals = StateForConditionals(civ, city)
for (unique in civ.getMatchingUniques(UniqueType.CannotBuildUnits, stateForConditionals))
if (this.matchesFilter(unique.params[0])) {
val hasHappinessCondition = unique.conditionals.any {
it.type == UniqueType.ConditionalBelowHappiness || it.type == UniqueType.ConditionalBetweenHappiness
}
if (hasHappinessCondition)
result.add(RejectionReasonType.CannotBeBuiltUnhappiness.toInstance(unique.text))
else result.add(RejectionReasonType.CannotBeBuilt.toInstance())
}
return result.asSequence()
}

View File

@ -6,6 +6,8 @@ import com.unciv.Constants
import com.unciv.GUI
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.StatMap
import com.unciv.ui.components.TabbedPager
@ -13,6 +15,8 @@ import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
import kotlin.math.roundToInt
class StatsOverviewTab(
@ -20,6 +24,7 @@ class StatsOverviewTab(
overviewScreen: EmpireOverviewScreen
) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
private val happinessTable = Table()
private val unhappinessTable = UnhappinessTable()
private val goldAndSliderTable = Table()
private val goldTable = Table()
private val scienceTable = Table()
@ -45,6 +50,7 @@ class StatsOverviewTab(
faithTable.defaults().pad(5f)
greatPeopleTable.defaults().pad(5f)
scoreTable.defaults().pad(5f)
unhappinessTable.update()
goldAndSliderTable.add(goldTable).row()
if (gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience))
@ -54,6 +60,7 @@ class StatsOverviewTab(
val allStatTables = sequence {
yield(happinessTable)
if (unhappinessTable.show) yield(unhappinessTable)
yield(goldAndSliderTable)
yield(scienceTable)
yield(cultureTable)
@ -121,6 +128,38 @@ class StatsOverviewTab(
addTotal(happinessBreakdown.values.sum())
}
inner class UnhappinessTable : Table() {
val show: Boolean
private val uniques: Set<Unique>
init {
defaults().pad(5f)
uniques = sequenceOf(
UniqueType.ConditionalBetweenHappiness,
UniqueType.ConditionalBelowHappiness
).flatMap { conditional ->
viewingPlayer.getTriggeredUniques(conditional)
.sortedBy { it.type } // otherwise order might change as a HashMap is involved
}.toSet()
show = uniques.isNotEmpty()
}
fun update() {
add(ImageGetter.getStatIcon("Malcontent"))
.size(Constants.headingFontSize.toFloat())
.right().padRight(1f)
add("Unhappiness".toLabel(fontSize = Constants.headingFontSize)).left()
addSeparator()
add(MarkupRenderer.render(
uniques.map { FormattedLine(it) },
labelWidth = (overviewScreen.stage.width * 0.25f).coerceAtLeast(greatPeopleTable.width * 0.8f),
iconDisplay = FormattedLine.IconDisplay.NoLink
)).colspan(2)
}
}
private fun Table.updateStatTable(stat: Stat, statMap: StatMap) {
addHeading(stat.name)
var total = 0f