chore(purity) - IHasUniques

This commit is contained in:
yairm210 2025-08-17 14:56:29 +03:00
parent 77d166898f
commit fb107465a4
4 changed files with 19 additions and 5 deletions

View File

@ -49,6 +49,7 @@ allprojects {
apply(plugin = "io.github.yairm210.purity-plugin") apply(plugin = "io.github.yairm210.purity-plugin")
configure<yairm210.purity.PurityConfiguration>{ configure<yairm210.purity.PurityConfiguration>{
wellKnownPureFunctions = setOf( wellKnownPureFunctions = setOf(
"kotlin.with",
) )
wellKnownReadonlyFunctions = setOf( wellKnownReadonlyFunctions = setOf(
"com.badlogic.gdx.math.Vector2.len", "com.badlogic.gdx.math.Vector2.len",

View File

@ -11,6 +11,7 @@ import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.unique.GameContext import com.unciv.models.ruleset.unique.GameContext
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
import yairm210.purity.annotations.Readonly
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.random.Random import kotlin.random.Random
@ -205,7 +206,7 @@ object ReligionAutomation {
// endregion // endregion
// region rate beliefs // region rate beliefs
@Readonly
fun rateBelief(civInfo: Civilization, belief: Belief): Float { fun rateBelief(civInfo: Civilization, belief: Belief): Float {
var score = 0f // Roughly equivalent to the sum of stats gained across all cities var score = 0f // Roughly equivalent to the sum of stats gained across all cities
@ -234,6 +235,7 @@ object ReligionAutomation {
return score return score
} }
@Readonly
private fun beliefBonusForTile(belief: Belief, tile: Tile, city: City): Float { private fun beliefBonusForTile(belief: Belief, tile: Tile, city: City): Float {
var bonusYield = 0f var bonusYield = 0f
for (unique in belief.uniqueObjects) { for (unique in belief.uniqueObjects) {
@ -253,6 +255,7 @@ object ReligionAutomation {
return bonusYield return bonusYield
} }
@Readonly
private fun beliefBonusForCity(civInfo: Civilization, belief: Belief, city: City): Float { private fun beliefBonusForCity(civInfo: Civilization, belief: Belief, city: City): Float {
var score = 0f var score = 0f
val ruleSet = civInfo.gameInfo.ruleset val ruleSet = civInfo.gameInfo.ruleset
@ -305,6 +308,7 @@ object ReligionAutomation {
return score return score
} }
@Readonly
private fun beliefBonusForPlayer(civInfo: Civilization, belief: Belief): Float { private fun beliefBonusForPlayer(civInfo: Civilization, belief: Belief): Float {
var score = 0f var score = 0f
val numberOfFoundedReligions = civInfo.gameInfo.civilizations.count { val numberOfFoundedReligions = civInfo.gameInfo.civilizations.count {
@ -411,7 +415,7 @@ object ReligionAutomation {
// line 4426 through 4870. // line 4426 through 4870.
// This is way too much work for now, so I'll just choose a random pantheon instead. // This is way too much work for now, so I'll just choose a random pantheon instead.
// Should probably be changed later, but it works for now. // Should probably be changed later, but it works for now.
val chosenPantheon = chooseBeliefOfType(civInfo, BeliefType.Pantheon) val chosenPantheon = pickBeliefOfType(civInfo, BeliefType.Pantheon)
?: return // panic! ?: return // panic!
civInfo.religionManager.chooseBeliefs( civInfo.religionManager.chooseBeliefs(
listOf(chosenPantheon), listOf(chosenPantheon),
@ -461,14 +465,15 @@ object ReligionAutomation {
if (belief == BeliefType.None) continue if (belief == BeliefType.None) continue
repeat(beliefsToChoose[belief]) { repeat(beliefsToChoose[belief]) {
chosenBeliefs.add( chosenBeliefs.add(
chooseBeliefOfType(civInfo, belief, chosenBeliefs) ?: return@repeat pickBeliefOfType(civInfo, belief, chosenBeliefs) ?: return@repeat
) )
} }
} }
return chosenBeliefs return chosenBeliefs
} }
private fun chooseBeliefOfType(civInfo: Civilization, beliefType: BeliefType, additionalBeliefsToExclude: HashSet<Belief> = hashSetOf()): Belief? { @Readonly
private fun pickBeliefOfType(civInfo: Civilization, beliefType: BeliefType, additionalBeliefsToExclude: HashSet<Belief> = hashSetOf()): Belief? {
return civInfo.gameInfo.ruleset.beliefs.values return civInfo.gameInfo.ruleset.beliefs.values
.filter { .filter {
(it.type == beliefType || beliefType == BeliefType.Any) (it.type == beliefType || beliefType == BeliefType.Any)

View File

@ -45,6 +45,7 @@ class UnitUpgradeManager(val unit: MapUnit) {
*/ */
// Only one use from getUpgradeAction at the moment, so AI-specific rules omitted // Only one use from getUpgradeAction at the moment, so AI-specific rules omitted
//todo Does the AI never buy upgrades??? //todo Does the AI never buy upgrades???
@Readonly
fun getCostOfUpgrade(unitToUpgradeTo: BaseUnit): Int { fun getCostOfUpgrade(unitToUpgradeTo: BaseUnit): Int {
// Source rounds to int every step, we don't // Source rounds to int every step, we don't
//TODO From the source, this should apply _Production_ modifiers (Temple of Artemis? GameSpeed! StartEra!), at the moment it doesn't //TODO From the source, this should apply _Production_ modifiers (Temple of Artemis? GameSpeed! StartEra!), at the moment it doesn't

View File

@ -82,17 +82,21 @@ interface IHasUniques : INamed {
@Readonly @Readonly
fun requiredTechs(): Sequence<String> = legacyRequiredTechs() + techsRequiredByUniques() fun requiredTechs(): Sequence<String> = legacyRequiredTechs() + techsRequiredByUniques()
@Readonly
fun requiredTechnologies(ruleset: Ruleset): Sequence<Technology?> = fun requiredTechnologies(ruleset: Ruleset): Sequence<Technology?> =
requiredTechs().map { ruleset.technologies[it] } requiredTechs().map { ruleset.technologies[it] }
@Readonly
fun era(ruleset: Ruleset): Era? = fun era(ruleset: Ruleset): Era? =
requiredTechnologies(ruleset).map { it?.era() }.map { ruleset.eras[it] }.maxByOrNull { it?.eraNumber ?: 0 } requiredTechnologies(ruleset).map { it?.era() }.map { ruleset.eras[it] }.maxByOrNull { it?.eraNumber ?: 0 }
// This will return null only if requiredTechnologies() is empty or all required techs have no eraNumber // This will return null only if requiredTechnologies() is empty or all required techs have no eraNumber
@Readonly
fun techColumn(ruleset: Ruleset): TechColumn? = fun techColumn(ruleset: Ruleset): TechColumn? =
requiredTechnologies(ruleset).map { it?.column }.filterNotNull().maxByOrNull { it.columnNumber } requiredTechnologies(ruleset).map { it?.column }.filterNotNull().maxByOrNull { it.columnNumber }
// This will return null only if *all* required techs have null TechColumn. // This will return null only if *all* required techs have null TechColumn.
@Readonly
fun availableInEra(ruleset: Ruleset, requestedEra: String): Boolean { fun availableInEra(ruleset: Ruleset, requestedEra: String): Boolean {
val eraAvailable: Era = era(ruleset) val eraAvailable: Era = era(ruleset)
?: return true // No technologies are required, so available in the starting era. ?: return true // No technologies are required, so available in the starting era.
@ -103,6 +107,7 @@ interface IHasUniques : INamed {
return eraAvailable.eraNumber <= ruleset.eras[requestedEra]!!.eraNumber return eraAvailable.eraNumber <= ruleset.eras[requestedEra]!!.eraNumber
} }
@Readonly
fun getWeightForAiDecision(gameContext: GameContext): Float { fun getWeightForAiDecision(gameContext: GameContext): Float {
var weight = 1f var weight = 1f
for (unique in getMatchingUniques(UniqueType.AiChoiceWeight, gameContext)) for (unique in getMatchingUniques(UniqueType.AiChoiceWeight, gameContext))
@ -160,6 +165,7 @@ interface IHasUniques : INamed {
* In that case only this parameter can be `null`. So if you know it - provide! * In that case only this parameter can be `null`. So if you know it - provide!
* @param ruleset Required if [gameInfo] is null, otherwise optional (but with both null this will simply return `false`). * @param ruleset Required if [gameInfo] is null, otherwise optional (but with both null this will simply return `false`).
*/ */
@Readonly
fun isHiddenFromCivilopedia( fun isHiddenFromCivilopedia(
gameInfo: GameInfo?, gameInfo: GameInfo?,
ruleset: Ruleset? = null ruleset: Ruleset? = null
@ -187,13 +193,14 @@ interface IHasUniques : INamed {
return false return false
} }
/** Overload of [isHiddenFromCivilopedia] for use in actually game-agnostic parts of Civilopedia */ /** Overload of [isHiddenFromCivilopedia] for use in actually game-agnostic parts of Civilopedia */
fun isHiddenFromCivilopedia(ruleset: Ruleset) = isHiddenFromCivilopedia(UncivGame.getGameInfoOrNull(), ruleset) @Readonly fun isHiddenFromCivilopedia(ruleset: Ruleset) = isHiddenFromCivilopedia(UncivGame.getGameInfoOrNull(), ruleset)
/** Common for Religion/Espionage: Hidden check when no game is loaded /** Common for Religion/Espionage: Hidden check when no game is loaded
* @param hasFeature Best guess from the Ruleset whether the feature is available * @param hasFeature Best guess from the Ruleset whether the feature is available
* @param enabler The modifier testing feature is on: `ConditionalReligionEnabled` or `ConditionalEspionageEnabled` * @param enabler The modifier testing feature is on: `ConditionalReligionEnabled` or `ConditionalEspionageEnabled`
* @param disabler The modifier testing feature is off: `ConditionalReligionDisabled` or `ConditionalEspionageDisabled` * @param disabler The modifier testing feature is off: `ConditionalReligionDisabled` or `ConditionalEspionageDisabled`
*/ */
@Readonly
private fun shouldBeHiddenIfNoGameLoaded(hasFeature: Boolean, enabler: UniqueType, disabler: UniqueType): Boolean { private fun shouldBeHiddenIfNoGameLoaded(hasFeature: Boolean, enabler: UniqueType, disabler: UniqueType): Boolean {
for (unique in getMatchingUniques(UniqueType.OnlyAvailable, GameContext.IgnoreConditionals)) { for (unique in getMatchingUniques(UniqueType.OnlyAvailable, GameContext.IgnoreConditionals)) {
if (unique.hasModifier(enabler)) return !hasFeature if (unique.hasModifier(enabler)) return !hasFeature