mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 01:08:25 +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() }
|
||||
newFinalStatList.add(
|
||||
getSourceNameForUnique(growthNullifyingUnique),
|
||||
Stats().apply { this[Stat.Food] = amountToRemove.toFloat() }
|
||||
Stats(food = amountToRemove.toFloat())
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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"),
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user