mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 17:59:11 +07:00
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:
@ -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())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 =
|
||||||
|
@ -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"),
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user