From e059d0dc4c995b5ccd1565cc9ab62a439a2daf94 Mon Sep 17 00:00:00 2001 From: yairm210 Date: Wed, 6 Aug 2025 23:30:08 +0300 Subject: [PATCH] chore(purity): Victory, ReligionManager --- build.gradle.kts | 6 ++- .../civilization/managers/ReligionManager.kt | 40 ++++++++++++++----- core/src/com/unciv/models/Counter.kt | 2 +- core/src/com/unciv/models/ruleset/Victory.kt | 14 +++++-- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d15c6050b4..1767fd5650 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,9 +63,13 @@ allprojects { "kotlin.collections.getValue", // moved "kotlin.collections.randomOrNull", "kotlin.collections.Collection.isEmpty", - "kotlin.collections.subtract" + "kotlin.collections.subtract", + "kotlin.collections.union", + "kotlin.collections.intersect", + ) wellKnownPureClasses = setOf( + "java.lang.Integer" ) wellKnownInternalStateClasses = setOf( // Moved all diff --git a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt index 3f94bf84b4..7c223e42c9 100644 --- a/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/ReligionManager.kt @@ -88,14 +88,15 @@ class ReligionManager : IsPartOfGameInfoSerialization { storedFaith += faithFromNewTurn } - fun isMajorityReligionForCiv(religion: Religion): Boolean { - return civInfo.cities.count { it.religion.getMajorityReligion() == religion } > civInfo.cities.size / 2 - } + @Readonly + 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 [freeBeliefs] with functions * that use Counter */ + @Readonly fun freeBeliefsAsEnums(): Counter { val toReturn = Counter() for (entry in freeBeliefs.entries) { @@ -104,13 +105,15 @@ class ReligionManager : IsPartOfGameInfoSerialization { return toReturn } - fun hasFreeBeliefs(): Boolean = freeBeliefs.sumValues() > 0 + @Readonly fun hasFreeBeliefs(): Boolean = freeBeliefs.sumValues() > 0 + @Readonly fun usingFreeBeliefs(): Boolean = (religionState == ReligionState.None && storedFaith < faithForPantheon()) // first pantheon is free || religionState == ReligionState.Pantheon // any subsequent pantheons before founding a religion || (religionState == ReligionState.Religion || religionState == ReligionState.EnhancedReligion) // any belief adding outside of great prophet use + @Readonly fun faithForPantheon(additionalCivs: Int = 0): Int { val gameInfo = civInfo.gameInfo 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 * before forming a religion */ + @Readonly fun canFoundOrExpandPantheon(): Boolean { if (!civInfo.gameInfo.isReligionEnabled()) 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/ // Game files (globaldefines.xml) + @Readonly fun faithForNextGreatProphet(): Int { val greatProphetsEarned = greatProphetsEarned() @@ -174,6 +179,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { return faithCost.toInt() } + @Readonly fun canGenerateProphet(ignoreFaithAmount: Boolean = false): Boolean { 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 @@ -211,6 +217,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { } } + @Readonly private fun maxNumberOfReligions(): Int { val gameInfo = civInfo.gameInfo val ruleset = gameInfo.ruleset @@ -221,11 +228,13 @@ class ReligionManager : IsPartOfGameInfoSerialization { } /** Calculates the number of religions that are already founded */ + @Readonly private fun foundedReligionsCount() = civInfo.gameInfo.civilizations.count { it.religionManager.religion != null && it.religionManager.religionState >= ReligionState.Religion } /** Calculates the amount of religions that can still be founded */ + @Readonly fun remainingFoundableReligions(): Int { // count the number of foundable religions left given defined ruleset religions and number of civs in game val maxNumberOfAdditionalReligions = maxNumberOfReligions() - foundedReligionsCount() @@ -239,6 +248,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { } /** Get info breaking down the reasons behind the result of [remainingFoundableReligions] */ + @Readonly fun remainingFoundableReligionsBreakdown() = sequence { val gameInfo = civInfo.gameInfo val ruleset = gameInfo.ruleset @@ -266,6 +276,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { yield("Available follower beliefs" to numberOfBeliefsAvailable(BeliefType.Follower)) } + @Readonly fun numberOfBeliefsAvailable(type: BeliefType): Int { val gameInfo = civInfo.gameInfo 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 } - fun getReligionWithBelief(belief: Belief): Religion? { - return civInfo.gameInfo.religions.values.firstOrNull { it.hasBelief(belief.name) } - } + @Readonly + fun getReligionWithBelief(belief: Belief): Religion? = + civInfo.gameInfo.religions.values.firstOrNull { it.hasBelief(belief.name) } + @Readonly fun mayFoundReligionAtAll(): Boolean { if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion if (religionState >= ReligionState.Religion) return false // Already created a major religion @@ -291,6 +303,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { return true } + @Readonly fun mayFoundReligionHere(tile: Tile): Boolean { if (!mayFoundReligionAtAll()) return false if (!tile.isCityCenter()) return false @@ -306,6 +319,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { civInfo.religionManager.foundingCityId = prophet.getTile().getCity()!!.id } + @Readonly fun mayEnhanceReligionAtAll(): Boolean { if (!civInfo.gameInfo.isReligionEnabled()) return false if (religion == null) return false // First found a pantheon @@ -321,6 +335,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { return true } + @Readonly fun mayEnhanceReligionHere(tile: Tile): Boolean { if (!mayEnhanceReligionAtAll()) 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 * available but the civ may be given more beliefs through uniques or a missing pantheon belief) */ + @Readonly private fun getBeliefsToChooseAtProphetUse(enhancingReligion: Boolean): Counter { val action = if (enhancingReligion) "enhancing" else "founding" val beliefsToChoose: Counter = Counter() @@ -399,8 +415,8 @@ class ReligionManager : IsPartOfGameInfoSerialization { return beliefsToChoose } - fun getBeliefsToChooseAtFounding(): Counter = getBeliefsToChooseAtProphetUse(false) - fun getBeliefsToChooseAtEnhancing(): Counter = getBeliefsToChooseAtProphetUse(true) + @Readonly fun getBeliefsToChooseAtFounding(): Counter = getBeliefsToChooseAtProphetUse(false) + @Readonly fun getBeliefsToChooseAtEnhancing(): Counter = getBeliefsToChooseAtProphetUse(true) fun chooseBeliefs(beliefs: List, useFreeBeliefs: Boolean = false) { // Remove the free beliefs in case we had them @@ -481,6 +497,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { } } + @Readonly fun maySpreadReligionAtAll(missionary: MapUnit): Boolean { if (!civInfo.isMajorCiv()) return false // Only major civs if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no spreading @@ -491,6 +508,7 @@ class ReligionManager : IsPartOfGameInfoSerialization { return true } + @Readonly fun maySpreadReligionNow(missionary: MapUnit): Boolean { if (!maySpreadReligionAtAll(missionary)) return false if (missionary.getTile().getOwner() == null) return false @@ -500,12 +518,14 @@ class ReligionManager : IsPartOfGameInfoSerialization { return true } + @Readonly fun numberOfCitiesFollowingThisReligion(): Int { if (religion == null) return 0 return civInfo.gameInfo.getCities() .count { it.religion.getMajorityReligion() == religion } } + @Readonly fun numberOfFollowersFollowingThisReligion(cityFilter: String): Int { if (religion == null) return 0 return civInfo.gameInfo.getCities() @@ -513,11 +533,13 @@ class ReligionManager : IsPartOfGameInfoSerialization { .sumOf { it.religion.getFollowersOf(religion!!.name) } } + @Readonly fun getHolyCity(): City? { if (religion == null) return null return civInfo.gameInfo.getCities().firstOrNull { it.isHolyCityOf(religion!!.name) } } + @Readonly fun getMajorityReligion(): Religion? { // let's count for each religion (among those actually presents in civ's cities) val religionCounter = Counter() diff --git a/core/src/com/unciv/models/Counter.kt b/core/src/com/unciv/models/Counter.kt index 6eb3edad73..68d7d97edd 100644 --- a/core/src/com/unciv/models/Counter.kt +++ b/core/src/com/unciv/models/Counter.kt @@ -64,7 +64,7 @@ open class Counter( return clone } - fun sumValues() = values.sum() + @Readonly fun sumValues() = values.sum() @Readonly override fun clone() = Counter(this) diff --git a/core/src/com/unciv/models/ruleset/Victory.kt b/core/src/com/unciv/models/ruleset/Victory.kt index e51b4985a5..a7f3e33768 100644 --- a/core/src/com/unciv/models/ruleset/Victory.kt +++ b/core/src/com/unciv/models/ruleset/Victory.kt @@ -11,6 +11,8 @@ import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderText import com.unciv.models.translations.tr import com.unciv.ui.components.extensions.toTextButton +import yairm210.purity.annotations.LocalState +import yairm210.purity.annotations.Readonly 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 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) { @@ -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 params = uniqueDescription.getPlaceholderParameters() + @Readonly private fun getIncompleteSpaceshipParts(civInfo: Civilization): Counter { - val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() + @LocalState val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() incompleteSpaceshipParts.remove(civInfo.victoryManager.currentsSpaceshipParts) return incompleteSpaceshipParts } + @Readonly private fun originalMajorCapitalsOwned(civInfo: Civilization): Int = civInfo.cities .count { it.isOriginalCapital && it.foundingCiv != "" && civInfo.gameInfo.getCivilization(it.foundingCiv).isMajorCiv() } + @Readonly private fun civsWithPotentialCapitalsToOwn(gameInfo: GameInfo): Set { // Capitals that still exist, even if the civ is dead val civsWithCapitals = gameInfo.getCities().filter { it.isOriginalCapital } @@ -90,6 +95,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor return civsWithCapitals.union(livingCivs) } + @Readonly fun hasBeenCompletedBy(civInfo: Civilization): Boolean { return when (type!!) { 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 { val textButton = text.toTextButton(hideIcons = true) if (achieved) textButton.color = Color.GREEN @@ -130,6 +137,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor return textButton } + @Readonly fun getVictoryScreenButtonHeaderText(completed: Boolean, civInfo: Civilization): String { return when (type!!) { MilestoneType.BuildingBuiltGlobally, MilestoneType.WinDiplomaticVote, @@ -164,7 +172,7 @@ class Milestone(val uniqueDescription: String, private val parentVictory: Victor } MilestoneType.AddedSSPartsInCapital -> { val completeSpaceshipParts = civInfo.victoryManager.currentsSpaceshipParts - val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() + @LocalState val incompleteSpaceshipParts = parentVictory.requiredSpaceshipPartsAsCounter.clone() val amountToDo = incompleteSpaceshipParts.sumValues() incompleteSpaceshipParts.remove(completeSpaceshipParts)