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() } val amountToRemove = -newFinalStatList.values.sumOf { it[Stat.Food].toDouble() }
newFinalStatList.add( newFinalStatList.add(
getSourceNameForUnique(growthNullifyingUnique), getSourceNameForUnique(growthNullifyingUnique),
Stats().apply { this[Stat.Food] = amountToRemove.toFloat() } Stats(food = amountToRemove.toFloat())
) )
} }

View File

@ -102,21 +102,27 @@ class CityPopulationManager : IsPartOfGameInfoSerialization {
if (population > 1) addPopulation(-1) if (population > 1) addPopulation(-1)
foodStored = 0 foodStored = 0
} }
if (foodStored >= getFoodToNextPopulation()) { // growth! val foodNeededToGrow = getFoodToNextPopulation()
foodStored -= getFoodToNextPopulation() if (foodStored < foodNeededToGrow) return
var percentOfFoodCarriedOver =
// 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) city.getMatchingUniques(UniqueType.CarryOverFood)
.filter { city.matchesFilter(it.params[1]) } .filter { city.matchesFilter(it.params[1]) }
.sumOf { it.params[0].toInt() } .sumOf { it.params[0].toInt() }
// Try to avoid runaway food gain in mods, just in case .coerceAtMost(95) // Try to avoid runaway food gain in mods, just in case
if (percentOfFoodCarriedOver > 95) percentOfFoodCarriedOver = 95 foodStored += (foodNeededToGrow * percentOfFoodCarriedOver / 100f).toInt()
foodStored += (getFoodToNextPopulation() * percentOfFoodCarriedOver / 100f).toInt()
addPopulation(1) addPopulation(1)
city.updateCitizens = true city.updateCitizens = true
city.civ.addNotification("[${city.name}] has grown!", city.location, city.civ.addNotification("[${city.name}] has grown!", city.location,
NotificationCategory.Cities, NotificationIcon.Growth) NotificationCategory.Cities, NotificationIcon.Growth)
} }
}
fun addPopulation(count: Int) { fun addPopulation(count: Int) {
val changedAmount = val changedAmount =

View File

@ -130,6 +130,7 @@ class RejectionReason(val type: RejectionReasonType,
RejectionReasonType.RequiresBuildingInAllCities, RejectionReasonType.RequiresBuildingInAllCities,
RejectionReasonType.RequiresBuildingInThisCity, RejectionReasonType.RequiresBuildingInThisCity,
RejectionReasonType.RequiresBuildingInSomeCity, RejectionReasonType.RequiresBuildingInSomeCity,
RejectionReasonType.CannotBeBuiltUnhappiness,
RejectionReasonType.PopulationRequirement, RejectionReasonType.PopulationRequirement,
RejectionReasonType.ConsumesResources, RejectionReasonType.ConsumesResources,
RejectionReasonType.CanOnlyBePurchased, RejectionReasonType.CanOnlyBePurchased,
@ -160,6 +161,7 @@ enum class RejectionReasonType(val shouldShow: Boolean, val errorMessage: String
UniqueToOtherNation(false, "Unique to another nation"), UniqueToOtherNation(false, "Unique to another nation"),
ReplacedByOurUnique(false, "Our unique replaces this"), ReplacedByOurUnique(false, "Our unique replaces this"),
CannotBeBuilt(false, "Cannot be built by this nation"), CannotBeBuilt(false, "Cannot be built by this nation"),
CannotBeBuiltUnhappiness(true, "Unhappiness"),
Obsoleted(false, "Obsolete"), Obsoleted(false, "Obsolete"),
RequiresTech(false, "Required tech not researched"), 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 UniqueType.ConditionalWhenBelowAmountResource -> state.civInfo != null
&& state.civInfo.getCivResourcesByName()[condition.params[1]]!! < condition.params[0].toInt() && state.civInfo.getCivResourcesByName()[condition.params[1]]!! < condition.params[0].toInt()
UniqueType.ConditionalHappy -> UniqueType.ConditionalHappy ->
state.civInfo != null && state.civInfo.stats.statsForNextTurn.happiness >= 0 state.civInfo != null && state.civInfo.stats.happiness >= 0
UniqueType.ConditionalBetweenHappiness -> UniqueType.ConditionalBetweenHappiness ->
state.civInfo != null state.civInfo != null
&& condition.params[0].toInt() <= state.civInfo.stats.happiness && condition.params[0].toInt() <= state.civInfo.stats.happiness
@ -379,4 +379,3 @@ fun ArrayList<TemporaryUnique>.getMatchingUniques(uniqueType: UniqueType, stateF
.map { it.uniqueObject } .map { it.uniqueObject }
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) } .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.logic.map.mapunit.MapUnit
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetObject 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.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
@ -141,7 +142,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
yieldAll(getRejectionReasons(civInfo, cityConstructions.city)) 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>() val result = mutableListOf<RejectionReason>()
if (requiredTech != null && !civ.tech.isResearched(requiredTech!!)) if (requiredTech != null && !civ.tech.isResearched(requiredTech!!))
result.add(RejectionReasonType.RequiresTech.toInstance("$requiredTech not researched")) result.add(RejectionReasonType.RequiresTech.toInstance("$requiredTech not researched"))
@ -189,16 +190,15 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
} }
} }
for (unique in civ.getMatchingUniques(UniqueType.CannotBuildUnits)) val stateForConditionals = StateForConditionals(civ, city)
if (this@BaseUnit.matchesFilter(unique.params[0])) { for (unique in civ.getMatchingUniques(UniqueType.CannotBuildUnits, stateForConditionals))
if (unique.conditionals.any { it.type == UniqueType.ConditionalBelowHappiness }) { if (this.matchesFilter(unique.params[0])) {
result.add( val hasHappinessCondition = unique.conditionals.any {
RejectionReasonType.CannotBeBuilt.toInstance( it.type == UniqueType.ConditionalBelowHappiness || it.type == UniqueType.ConditionalBetweenHappiness
unique.text, }
true if (hasHappinessCondition)
) result.add(RejectionReasonType.CannotBeBuiltUnhappiness.toInstance(unique.text))
) else result.add(RejectionReasonType.CannotBeBuilt.toInstance())
} else result.add(RejectionReasonType.CannotBeBuilt.toInstance())
} }
return result.asSequence() return result.asSequence()
} }

View File

@ -6,6 +6,8 @@ import com.unciv.Constants
import com.unciv.GUI import com.unciv.GUI
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.ModOptionsConstants 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.Stat
import com.unciv.models.stats.StatMap import com.unciv.models.stats.StatMap
import com.unciv.ui.components.TabbedPager 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.addSeparator
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.ImageGetter import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.civilopediascreen.MarkupRenderer
import kotlin.math.roundToInt import kotlin.math.roundToInt
class StatsOverviewTab( class StatsOverviewTab(
@ -20,6 +24,7 @@ class StatsOverviewTab(
overviewScreen: EmpireOverviewScreen overviewScreen: EmpireOverviewScreen
) : EmpireOverviewTab(viewingPlayer, overviewScreen) { ) : EmpireOverviewTab(viewingPlayer, overviewScreen) {
private val happinessTable = Table() private val happinessTable = Table()
private val unhappinessTable = UnhappinessTable()
private val goldAndSliderTable = Table() private val goldAndSliderTable = Table()
private val goldTable = Table() private val goldTable = Table()
private val scienceTable = Table() private val scienceTable = Table()
@ -45,6 +50,7 @@ class StatsOverviewTab(
faithTable.defaults().pad(5f) faithTable.defaults().pad(5f)
greatPeopleTable.defaults().pad(5f) greatPeopleTable.defaults().pad(5f)
scoreTable.defaults().pad(5f) scoreTable.defaults().pad(5f)
unhappinessTable.update()
goldAndSliderTable.add(goldTable).row() goldAndSliderTable.add(goldTable).row()
if (gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience)) if (gameInfo.ruleset.modOptions.uniques.contains(ModOptionsConstants.convertGoldToScience))
@ -54,6 +60,7 @@ class StatsOverviewTab(
val allStatTables = sequence { val allStatTables = sequence {
yield(happinessTable) yield(happinessTable)
if (unhappinessTable.show) yield(unhappinessTable)
yield(goldAndSliderTable) yield(goldAndSliderTable)
yield(scienceTable) yield(scienceTable)
yield(cultureTable) yield(cultureTable)
@ -121,6 +128,38 @@ class StatsOverviewTab(
addTotal(happinessBreakdown.values.sum()) 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) { private fun Table.updateStatTable(stat: Stat, statMap: StatMap) {
addHeading(stat.name) addHeading(stat.name)
var total = 0f var total = 0f