chore(purity): Victory, ReligionManager

This commit is contained in:
yairm210 2025-08-06 23:30:08 +03:00
parent cca650ab8a
commit e059d0dc4c
4 changed files with 48 additions and 14 deletions

View File

@ -63,9 +63,13 @@ allprojects {
"kotlin.collections.getValue", // moved "kotlin.collections.getValue", // moved
"kotlin.collections.randomOrNull", "kotlin.collections.randomOrNull",
"kotlin.collections.Collection.isEmpty", "kotlin.collections.Collection.isEmpty",
"kotlin.collections.subtract" "kotlin.collections.subtract",
"kotlin.collections.union",
"kotlin.collections.intersect",
) )
wellKnownPureClasses = setOf<String>( wellKnownPureClasses = setOf<String>(
"java.lang.Integer"
) )
wellKnownInternalStateClasses = setOf<String>( wellKnownInternalStateClasses = setOf<String>(
// Moved all // Moved all

View File

@ -88,14 +88,15 @@ class ReligionManager : IsPartOfGameInfoSerialization {
storedFaith += faithFromNewTurn storedFaith += faithFromNewTurn
} }
fun isMajorityReligionForCiv(religion: Religion): Boolean { @Readonly
return civInfo.cities.count { it.religion.getMajorityReligion() == religion } > civInfo.cities.size / 2 fun isMajorityReligionForCiv(religion: Religion): Boolean =
} civInfo.cities.count { it.religion.getMajorityReligion() == religion } > civInfo.cities.size / 2
/** /**
* This helper function makes it easy to interface the Counter<String> [freeBeliefs] with functions * This helper function makes it easy to interface the Counter<String> [freeBeliefs] with functions
* that use Counter<BeliefType> * that use Counter<BeliefType>
*/ */
@Readonly
fun freeBeliefsAsEnums(): Counter<BeliefType> { fun freeBeliefsAsEnums(): Counter<BeliefType> {
val toReturn = Counter<BeliefType>() val toReturn = Counter<BeliefType>()
for (entry in freeBeliefs.entries) { for (entry in freeBeliefs.entries) {
@ -104,13 +105,15 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return toReturn return toReturn
} }
fun hasFreeBeliefs(): Boolean = freeBeliefs.sumValues() > 0 @Readonly fun hasFreeBeliefs(): Boolean = freeBeliefs.sumValues() > 0
@Readonly
fun usingFreeBeliefs(): Boolean fun usingFreeBeliefs(): Boolean
= (religionState == ReligionState.None && storedFaith < faithForPantheon()) // first pantheon is free = (religionState == ReligionState.None && storedFaith < faithForPantheon()) // first pantheon is free
|| religionState == ReligionState.Pantheon // any subsequent pantheons before founding a religion || religionState == ReligionState.Pantheon // any subsequent pantheons before founding a religion
|| (religionState == ReligionState.Religion || religionState == ReligionState.EnhancedReligion) // any belief adding outside of great prophet use || (religionState == ReligionState.Religion || religionState == ReligionState.EnhancedReligion) // any belief adding outside of great prophet use
@Readonly
fun faithForPantheon(additionalCivs: Int = 0): Int { fun faithForPantheon(additionalCivs: Int = 0): Int {
val gameInfo = civInfo.gameInfo val gameInfo = civInfo.gameInfo
val numCivs = additionalCivs + gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.religion != null } val numCivs = additionalCivs + gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.religion != null }
@ -120,6 +123,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
/** Used for founding the pantheon and for each time the player gets additional pantheon beliefs /** Used for founding the pantheon and for each time the player gets additional pantheon beliefs
* before forming a religion */ * before forming a religion */
@Readonly
fun canFoundOrExpandPantheon(): Boolean { fun canFoundOrExpandPantheon(): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false if (!civInfo.gameInfo.isReligionEnabled()) return false
if (religionState > ReligionState.Pantheon) return false if (religionState > ReligionState.Pantheon) return false
@ -161,6 +165,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
// https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/ // https://www.reddit.com/r/civ/comments/2m82wu/can_anyone_detail_the_finer_points_of_great/
// Game files (globaldefines.xml) // Game files (globaldefines.xml)
@Readonly
fun faithForNextGreatProphet(): Int { fun faithForNextGreatProphet(): Int {
val greatProphetsEarned = greatProphetsEarned() val greatProphetsEarned = greatProphetsEarned()
@ -174,6 +179,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return faithCost.toInt() return faithCost.toInt()
} }
@Readonly
fun canGenerateProphet(ignoreFaithAmount: Boolean = false): Boolean { fun canGenerateProphet(ignoreFaithAmount: Boolean = false): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no prophets if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no prophets
if (religion == null || religionState == ReligionState.None) return false // First get a pantheon, then we'll talk about a real religion if (religion == null || religionState == ReligionState.None) return false // First get a pantheon, then we'll talk about a real religion
@ -211,6 +217,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
} }
} }
@Readonly
private fun maxNumberOfReligions(): Int { private fun maxNumberOfReligions(): Int {
val gameInfo = civInfo.gameInfo val gameInfo = civInfo.gameInfo
val ruleset = gameInfo.ruleset val ruleset = gameInfo.ruleset
@ -221,11 +228,13 @@ class ReligionManager : IsPartOfGameInfoSerialization {
} }
/** Calculates the number of religions that are already founded */ /** Calculates the number of religions that are already founded */
@Readonly
private fun foundedReligionsCount() = civInfo.gameInfo.civilizations.count { private fun foundedReligionsCount() = civInfo.gameInfo.civilizations.count {
it.religionManager.religion != null && it.religionManager.religionState >= ReligionState.Religion it.religionManager.religion != null && it.religionManager.religionState >= ReligionState.Religion
} }
/** Calculates the amount of religions that can still be founded */ /** Calculates the amount of religions that can still be founded */
@Readonly
fun remainingFoundableReligions(): Int { fun remainingFoundableReligions(): Int {
// count the number of foundable religions left given defined ruleset religions and number of civs in game // count the number of foundable religions left given defined ruleset religions and number of civs in game
val maxNumberOfAdditionalReligions = maxNumberOfReligions() - foundedReligionsCount() val maxNumberOfAdditionalReligions = maxNumberOfReligions() - foundedReligionsCount()
@ -239,6 +248,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
} }
/** Get info breaking down the reasons behind the result of [remainingFoundableReligions] */ /** Get info breaking down the reasons behind the result of [remainingFoundableReligions] */
@Readonly
fun remainingFoundableReligionsBreakdown() = sequence { fun remainingFoundableReligionsBreakdown() = sequence {
val gameInfo = civInfo.gameInfo val gameInfo = civInfo.gameInfo
val ruleset = gameInfo.ruleset val ruleset = gameInfo.ruleset
@ -266,6 +276,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
yield("Available follower beliefs" to numberOfBeliefsAvailable(BeliefType.Follower)) yield("Available follower beliefs" to numberOfBeliefsAvailable(BeliefType.Follower))
} }
@Readonly
fun numberOfBeliefsAvailable(type: BeliefType): Int { fun numberOfBeliefsAvailable(type: BeliefType): Int {
val gameInfo = civInfo.gameInfo val gameInfo = civInfo.gameInfo
val numberOfBeliefs = if (type == BeliefType.Any) gameInfo.ruleset.beliefs.values.count() val numberOfBeliefs = if (type == BeliefType.Any) gameInfo.ruleset.beliefs.values.count()
@ -274,11 +285,12 @@ class ReligionManager : IsPartOfGameInfoSerialization {
// We need to do the distinct above, as pantheons and religions founded out of those pantheons might share beliefs // We need to do the distinct above, as pantheons and religions founded out of those pantheons might share beliefs
} }
fun getReligionWithBelief(belief: Belief): Religion? { @Readonly
return civInfo.gameInfo.religions.values.firstOrNull { it.hasBelief(belief.name) } fun getReligionWithBelief(belief: Belief): Religion? =
} civInfo.gameInfo.religions.values.firstOrNull { it.hasBelief(belief.name) }
@Readonly
fun mayFoundReligionAtAll(): Boolean { fun mayFoundReligionAtAll(): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion
if (religionState >= ReligionState.Religion) return false // Already created a major religion if (religionState >= ReligionState.Religion) return false // Already created a major religion
@ -291,6 +303,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return true return true
} }
@Readonly
fun mayFoundReligionHere(tile: Tile): Boolean { fun mayFoundReligionHere(tile: Tile): Boolean {
if (!mayFoundReligionAtAll()) return false if (!mayFoundReligionAtAll()) return false
if (!tile.isCityCenter()) return false if (!tile.isCityCenter()) return false
@ -306,6 +319,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
civInfo.religionManager.foundingCityId = prophet.getTile().getCity()!!.id civInfo.religionManager.foundingCityId = prophet.getTile().getCity()!!.id
} }
@Readonly
fun mayEnhanceReligionAtAll(): Boolean { fun mayEnhanceReligionAtAll(): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false if (!civInfo.gameInfo.isReligionEnabled()) return false
if (religion == null) return false // First found a pantheon if (religion == null) return false // First found a pantheon
@ -321,6 +335,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return true return true
} }
@Readonly
fun mayEnhanceReligionHere(tile: Tile): Boolean { fun mayEnhanceReligionHere(tile: Tile): Boolean {
if (!mayEnhanceReligionAtAll()) return false if (!mayEnhanceReligionAtAll()) return false
if (!tile.isCityCenter()) return false if (!tile.isCityCenter()) return false
@ -351,6 +366,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
* and [mayEnhanceReligionAtAll] only check if there is 1 founder/enhancer and 1 follower belief * and [mayEnhanceReligionAtAll] only check if there is 1 founder/enhancer and 1 follower belief
* available but the civ may be given more beliefs through uniques or a missing pantheon belief) * available but the civ may be given more beliefs through uniques or a missing pantheon belief)
*/ */
@Readonly
private fun getBeliefsToChooseAtProphetUse(enhancingReligion: Boolean): Counter<BeliefType> { private fun getBeliefsToChooseAtProphetUse(enhancingReligion: Boolean): Counter<BeliefType> {
val action = if (enhancingReligion) "enhancing" else "founding" val action = if (enhancingReligion) "enhancing" else "founding"
val beliefsToChoose: Counter<BeliefType> = Counter() val beliefsToChoose: Counter<BeliefType> = Counter()
@ -399,8 +415,8 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return beliefsToChoose return beliefsToChoose
} }
fun getBeliefsToChooseAtFounding(): Counter<BeliefType> = getBeliefsToChooseAtProphetUse(false) @Readonly fun getBeliefsToChooseAtFounding(): Counter<BeliefType> = getBeliefsToChooseAtProphetUse(false)
fun getBeliefsToChooseAtEnhancing(): Counter<BeliefType> = getBeliefsToChooseAtProphetUse(true) @Readonly fun getBeliefsToChooseAtEnhancing(): Counter<BeliefType> = getBeliefsToChooseAtProphetUse(true)
fun chooseBeliefs(beliefs: List<Belief>, useFreeBeliefs: Boolean = false) { fun chooseBeliefs(beliefs: List<Belief>, useFreeBeliefs: Boolean = false) {
// Remove the free beliefs in case we had them // Remove the free beliefs in case we had them
@ -481,6 +497,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
} }
} }
@Readonly
fun maySpreadReligionAtAll(missionary: MapUnit): Boolean { fun maySpreadReligionAtAll(missionary: MapUnit): Boolean {
if (!civInfo.isMajorCiv()) return false // Only major civs if (!civInfo.isMajorCiv()) return false // Only major civs
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no spreading if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no spreading
@ -491,6 +508,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return true return true
} }
@Readonly
fun maySpreadReligionNow(missionary: MapUnit): Boolean { fun maySpreadReligionNow(missionary: MapUnit): Boolean {
if (!maySpreadReligionAtAll(missionary)) return false if (!maySpreadReligionAtAll(missionary)) return false
if (missionary.getTile().getOwner() == null) return false if (missionary.getTile().getOwner() == null) return false
@ -500,12 +518,14 @@ class ReligionManager : IsPartOfGameInfoSerialization {
return true return true
} }
@Readonly
fun numberOfCitiesFollowingThisReligion(): Int { fun numberOfCitiesFollowingThisReligion(): Int {
if (religion == null) return 0 if (religion == null) return 0
return civInfo.gameInfo.getCities() return civInfo.gameInfo.getCities()
.count { it.religion.getMajorityReligion() == religion } .count { it.religion.getMajorityReligion() == religion }
} }
@Readonly
fun numberOfFollowersFollowingThisReligion(cityFilter: String): Int { fun numberOfFollowersFollowingThisReligion(cityFilter: String): Int {
if (religion == null) return 0 if (religion == null) return 0
return civInfo.gameInfo.getCities() return civInfo.gameInfo.getCities()
@ -513,11 +533,13 @@ class ReligionManager : IsPartOfGameInfoSerialization {
.sumOf { it.religion.getFollowersOf(religion!!.name) } .sumOf { it.religion.getFollowersOf(religion!!.name) }
} }
@Readonly
fun getHolyCity(): City? { fun getHolyCity(): City? {
if (religion == null) return null if (religion == null) return null
return civInfo.gameInfo.getCities().firstOrNull { it.isHolyCityOf(religion!!.name) } return civInfo.gameInfo.getCities().firstOrNull { it.isHolyCityOf(religion!!.name) }
} }
@Readonly
fun getMajorityReligion(): Religion? { fun getMajorityReligion(): Religion? {
// let's count for each religion (among those actually presents in civ's cities) // let's count for each religion (among those actually presents in civ's cities)
val religionCounter = Counter<Religion>() val religionCounter = Counter<Religion>()

View File

@ -64,7 +64,7 @@ open class Counter<K>(
return clone return clone
} }
fun sumValues() = values.sum() @Readonly fun sumValues() = values.sum()
@Readonly override fun clone() = Counter(this) @Readonly override fun clone() = Counter(this)

View File

@ -11,6 +11,8 @@ import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.getPlaceholderText import com.unciv.models.translations.getPlaceholderText
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly
enum class MilestoneType(val text: String) { enum class MilestoneType(val text: String) {
@ -63,7 +65,7 @@ class Victory : INamed {
val victoryString = "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!" val victoryString = "Your civilization stands above all others! The exploits of your people shall be remembered until the end of civilization itself!"
val defeatString = "You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory!" val defeatString = "You have been defeated. Your civilization has been overwhelmed by its many foes. But your people do not despair, for they know that one day you shall return - and lead them forward to victory!"
fun enablesMaxTurns(): Boolean = milestoneObjects.any { it.type == MilestoneType.ScoreAfterTimeOut } @Readonly fun enablesMaxTurns(): Boolean = milestoneObjects.any { it.type == MilestoneType.ScoreAfterTimeOut }
} }
class Milestone(val uniqueDescription: String, private val parentVictory: Victory) { class Milestone(val uniqueDescription: String, private val parentVictory: Victory) {
@ -71,15 +73,18 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor
val type: MilestoneType? = MilestoneType.entries.firstOrNull { uniqueDescription.getPlaceholderText() == it.text.getPlaceholderText() } val type: MilestoneType? = MilestoneType.entries.firstOrNull { uniqueDescription.getPlaceholderText() == it.text.getPlaceholderText() }
val params = uniqueDescription.getPlaceholderParameters() val params = uniqueDescription.getPlaceholderParameters()
@Readonly
private fun getIncompleteSpaceshipParts(civInfo: Civilization): Counter<String> { private fun getIncompleteSpaceshipParts(civInfo: Civilization): Counter<String> {
val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() @LocalState val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone()
incompleteSpaceshipParts.remove(civInfo.victoryManager.currentsSpaceshipParts) incompleteSpaceshipParts.remove(civInfo.victoryManager.currentsSpaceshipParts)
return incompleteSpaceshipParts return incompleteSpaceshipParts
} }
@Readonly
private fun originalMajorCapitalsOwned(civInfo: Civilization): Int = civInfo.cities private fun originalMajorCapitalsOwned(civInfo: Civilization): Int = civInfo.cities
.count { it.isOriginalCapital && it.foundingCiv != "" && civInfo.gameInfo.getCivilization(it.foundingCiv).isMajorCiv() } .count { it.isOriginalCapital && it.foundingCiv != "" && civInfo.gameInfo.getCivilization(it.foundingCiv).isMajorCiv() }
@Readonly
private fun civsWithPotentialCapitalsToOwn(gameInfo: GameInfo): Set<Civilization> { private fun civsWithPotentialCapitalsToOwn(gameInfo: GameInfo): Set<Civilization> {
// Capitals that still exist, even if the civ is dead // Capitals that still exist, even if the civ is dead
val civsWithCapitals = gameInfo.getCities().filter { it.isOriginalCapital } val civsWithCapitals = gameInfo.getCities().filter { it.isOriginalCapital }
@ -90,6 +95,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor
return civsWithCapitals.union(livingCivs) return civsWithCapitals.union(livingCivs)
} }
@Readonly
fun hasBeenCompletedBy(civInfo: Civilization): Boolean { fun hasBeenCompletedBy(civInfo: Civilization): Boolean {
return when (type!!) { return when (type!!) {
MilestoneType.BuiltBuilding -> MilestoneType.BuiltBuilding ->
@ -123,6 +129,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor
} }
} }
// Todo remove from here, this is models, not UI
private fun getMilestoneButton(text: String, achieved: Boolean): TextButton { private fun getMilestoneButton(text: String, achieved: Boolean): TextButton {
val textButton = text.toTextButton(hideIcons = true) val textButton = text.toTextButton(hideIcons = true)
if (achieved) textButton.color = Color.GREEN if (achieved) textButton.color = Color.GREEN
@ -130,6 +137,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor
return textButton return textButton
} }
@Readonly
fun getVictoryScreenButtonHeaderText(completed: Boolean, civInfo: Civilization): String { fun getVictoryScreenButtonHeaderText(completed: Boolean, civInfo: Civilization): String {
return when (type!!) { return when (type!!) {
MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote, MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote,
@ -164,7 +172,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor
} }
MilestoneType.AddedSSPartsInCapital -> { MilestoneType.AddedSSPartsInCapital -> {
val completeSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts val completeSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts
val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() @LocalState val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone()
val amountToDo = incompleteSpaceshipParts.sumValues() val amountToDo = incompleteSpaceshipParts.sumValues()
incompleteSpaceshipParts.remove(completeSpaceshipParts) incompleteSpaceshipParts.remove(completeSpaceshipParts)