diff --git a/core/src/com/unciv/logic/MultiFilter.kt b/core/src/com/unciv/logic/MultiFilter.kt index e9b8c84330..c3d2e2771c 100644 --- a/core/src/com/unciv/logic/MultiFilter.kt +++ b/core/src/com/unciv/logic/MultiFilter.kt @@ -20,7 +20,7 @@ object MultiFilter { * @param filterFunction The single filter implementation * @param forUniqueValidityTests Inverts the `non-[filter]` test because Unique validity doesn't check for actual matching */ - @Readonly @Suppress("purity") + @Readonly @Suppress("purity") // Calls function invoke fun multiFilter( input: String, filterFunction: (String) -> Boolean, diff --git a/core/src/com/unciv/logic/civilization/Civilization.kt b/core/src/com/unciv/logic/civilization/Civilization.kt index 5abee8e70c..ead7889369 100644 --- a/core/src/com/unciv/logic/civilization/Civilization.kt +++ b/core/src/com/unciv/logic/civilization/Civilization.kt @@ -583,10 +583,12 @@ class Civilization : IsPartOfGameInfoSerialization { }.toList() // Triggers can e.g. add buildings which contain triggers, causing concurrent modification errors /** Implements [UniqueParameterType.CivFilter][com.unciv.models.ruleset.unique.UniqueParameterType.CivFilter] */ + @Readonly fun matchesFilter(filter: String, state: GameContext? = this.state, multiFilter: Boolean = true): Boolean = if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleFilter(it, state) }) else matchesSingleFilter(filter, state) + @Readonly fun matchesSingleFilter(filter: String, state: GameContext? = this.state): Boolean { return when (filter) { "Human player" -> isHuman() diff --git a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt index 44a4253ecf..c005d9af26 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt @@ -414,7 +414,7 @@ class CityStateFunctions(val civInfo: Civilization) { return getTributeModifiers(demandingCiv, demandingWorker).values.sum() } - @Readonly @Suppress("purity") + @Readonly @Suppress("purity") // Local state update fun getTributeModifiers(demandingCiv: Civilization, demandingWorker: Boolean = false, requireWholeList: Boolean = false): HashMap { val modifiers = LinkedHashMap() // Linked to preserve order when presenting the modifiers table // Can't bully major civs or unsettled CS's diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 895ea5bf36..10c49446f7 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -471,6 +471,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { /** Returns true when the [civInfo]'s territory is considered allied for [otherCiv]. * This includes friendly and allied city-states and the open border treaties. */ + @Readonly fun isConsideredFriendlyTerritory(): Boolean { if (civInfo.isCityState && (isRelationshipLevelGE(RelationshipLevel.Friend) || otherCiv().hasUnique(UniqueType.CityStateTerritoryAlwaysFriendly))) diff --git a/core/src/com/unciv/logic/map/tile/Tile.kt b/core/src/com/unciv/logic/map/tile/Tile.kt index dfc18c5349..d7091fe683 100644 --- a/core/src/com/unciv/logic/map/tile/Tile.kt +++ b/core/src/com/unciv/logic/map/tile/Tile.kt @@ -377,6 +377,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { getOwner() } + @Readonly fun isFriendlyTerritory(civInfo: Civilization): Boolean { val tileOwner = getOwner() return when { @@ -387,6 +388,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { } } + @Readonly fun isEnemyTerritory(civInfo: Civilization): Boolean { val tileOwner = getOwner() ?: return false return civInfo.isAtWarWith(tileOwner) @@ -492,6 +494,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { } // This should be the only adjacency function + @Readonly fun isAdjacentTo(terrainFilter: String, observingCiv: Civilization? = null): Boolean { // Rivers are odd, as they aren't technically part of any specific tile but still count towards adjacency if (terrainFilter == Constants.river) return isAdjacentToRiver() @@ -522,7 +525,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { else matchesSingleTerrainFilter(filter, observingCiv) } - @Readonly @Suppress("purity") + @Readonly private fun matchesSingleTerrainFilter(filter: String, observingCiv: Civilization?): Boolean { // Constant strings get their own 'when' for performance - // see https://yairm210.medium.com/kotlin-when-string-optimization-e15c6eea2734 @@ -579,6 +582,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable { @Readonly @Suppress("purity") // should be auto-recognized! fun isCoastalTile() = _isCoastalTile + @Readonly fun hasViewableResource(civInfo: Civilization): Boolean = resource != null && civInfo.tech.isRevealed(tileResource) diff --git a/core/src/com/unciv/models/ruleset/nation/Nation.kt b/core/src/com/unciv/models/ruleset/nation/Nation.kt index 90784696a2..b47679b67f 100644 --- a/core/src/com/unciv/models/ruleset/nation/Nation.kt +++ b/core/src/com/unciv/models/ruleset/nation/Nation.kt @@ -18,6 +18,7 @@ import com.unciv.ui.objectdescriptions.BuildingDescriptions import com.unciv.ui.objectdescriptions.ImprovementDescriptions import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine +import yairm210.purity.annotations.Readonly import kotlin.math.pow class Nation : RulesetObject() { @@ -265,7 +266,8 @@ class Nation : RulesetObject() { } } } - + + @Readonly fun matchesFilter(filter: String, state: GameContext? = null, multiFilter: Boolean = true): Boolean { // Todo: Add 'multifilter=false' option to Multifilter itself to cut down on duplicate code return if (multiFilter) MultiFilter.multiFilter(filter, { @@ -278,6 +280,7 @@ class Nation : RulesetObject() { state == null && hasTagUnique(filter) } + @Readonly private fun matchesSingleFilter(filter: String): Boolean { // All cases are compile-time constants, for performance return when (filter) { diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 5d642dfcac..375c040349 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -12,6 +12,7 @@ import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.components.extensions.colorFromRGB import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.screens.civilopediascreen.FormattedLine +import yairm210.purity.annotations.Readonly class Terrain : RulesetStatsObject() { @@ -47,6 +48,7 @@ class Terrain : RulesetStatsObject() { var damagePerTurn = 0 // Shouldn't this just be a lazy property so it's automatically cached? + @Readonly fun isRough(): Boolean = hasUnique(UniqueType.RoughTerrain) /** Tests base terrains, features and natural wonders whether they should be treated as Land/Water. @@ -160,6 +162,7 @@ class Terrain : RulesetStatsObject() { /** Terrain filter matching is "pure" - input always returns same output, and it's called a bajillion times */ val cachedMatchesFilterResult = HashMap() + @Readonly fun matchesFilter(filter: String, state: GameContext? = null, multiFilter: Boolean = true): Boolean { return if (multiFilter) MultiFilter.multiFilter(filter, { cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } || @@ -172,6 +175,7 @@ class Terrain : RulesetStatsObject() { } /** Implements [UniqueParameterType.TerrainFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TerrainFilter] */ + @Readonly fun matchesSingleFilter(filter: String): Boolean { return when (filter) { in Constants.all -> true diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index 5a1171254c..6a09861d5c 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -14,6 +14,7 @@ import com.unciv.utils.Log import com.unciv.utils.debug import java.util.Locale import org.jetbrains.annotations.VisibleForTesting +import yairm210.purity.annotations.Pure import yairm210.purity.annotations.Readonly /** @@ -473,7 +474,7 @@ private fun String.translateIndividualWord(language: String, hideIcons: Boolean, * For example, a string like 'The city of [New [York]]' will return ['New [York]'], * allowing us to have nested translations! */ -@Readonly @Suppress("purity") +@Readonly @Suppress("purity") // Local state update fun String.getPlaceholderParameters(): List { if (!this.contains('[')) return emptyList() @@ -512,6 +513,7 @@ fun String.equalsPlaceholderText(str: String): Boolean { return this.getPlaceholderText() == str } +@Pure fun String.hasPlaceholderParameters(): Boolean { if (!this.contains('[')) return false return squareBraceRegex.containsMatchIn(this.removeConditionals()) @@ -529,12 +531,13 @@ fun String.fillPlaceholders(vararg strings: String): String { return filledString } +@Pure fun String.getModifiers(): List { if (!this.contains('<')) return emptyList() return pointyBraceRegex.findAll(this).map { Unique(it.groups[1]!!.value) }.toList() } -@Readonly @Suppress("purity") // todo fix val reference in purity +@Pure @Suppress("purity") // todo fix val reference in purity fun String.removeConditionals(): String { if (!this.contains('<')) return this // no need to regex search return this