From b006673e1cea9ce014f326561bcec6d2702a6416 Mon Sep 17 00:00:00 2001 From: yairm210 Date: Sun, 17 Aug 2025 14:37:47 +0300 Subject: [PATCH] chore(purity) --- build.gradle.kts | 2 +- .../automation/civilization/BarbarianManager.kt | 17 ++++++++++++----- core/src/com/unciv/logic/city/CityStats.kt | 6 +++--- .../models/ruleset/tile/TileImprovement.kt | 2 +- .../src/com/unciv/utils/CollectionExtensions.kt | 8 ++++++++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c99b1a2fe6..a629909d39 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -38,7 +38,7 @@ plugins { // This is *with* gradle 8.2 downloaded according the project specs, no idea what that's about kotlin("multiplatform") version "1.9.24" kotlin("plugin.serialization") version "1.9.24" - id("io.github.yairm210.purity-plugin") version "1.1.1" apply(false) + id("io.github.yairm210.purity-plugin") version "1.2.2" apply(false) } allprojects { diff --git a/core/src/com/unciv/logic/automation/civilization/BarbarianManager.kt b/core/src/com/unciv/logic/automation/civilization/BarbarianManager.kt index 58ce9a8aa8..399b80c2b1 100644 --- a/core/src/com/unciv/logic/automation/civilization/BarbarianManager.kt +++ b/core/src/com/unciv/logic/automation/civilization/BarbarianManager.kt @@ -11,6 +11,7 @@ import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.utils.randomWeighted +import yairm210.purity.annotations.Readonly import kotlin.math.max import kotlin.math.min import kotlin.math.pow @@ -241,20 +242,26 @@ class Encampment() : IsPartOfGameInfoSerialization { /** Attempts to spawn a barbarian on [position], returns true if successful and false if unsuccessful. */ private fun spawnUnit(naval: Boolean): Boolean { + updateBarbarianTech() val unitToSpawn = chooseBarbarianUnit(naval) ?: return false // return false if we didn't find a unit val spawnedUnit = gameInfo.tileMap.placeUnitNearTile(position, unitToSpawn, gameInfo.getBarbarianCivilization()) return (spawnedUnit != null) } - - private fun chooseBarbarianUnit(naval: Boolean): BaseUnit? { - // if we don't make this into a separate list then the retain() will happen on the Tech keys, - // which effectively removes those techs from the game and causes all sorts of problems + + private fun updateBarbarianTech(){ + val barbarianCiv = gameInfo.getBarbarianCivilization() val allResearchedTechs = gameInfo.ruleset.technologies.keys.toMutableList() for (civ in gameInfo.civilizations.filter { !it.isBarbarian && !it.isDefeated() }) { allResearchedTechs.retainAll(civ.tech.techsResearched) } - val barbarianCiv = gameInfo.getBarbarianCivilization() barbarianCiv.tech.techsResearched = allResearchedTechs.toHashSet() + } + + @Readonly + private fun chooseBarbarianUnit(naval: Boolean): BaseUnit? { + // if we don't make this into a separate list then the retain() will happen on the Tech keys, + // which effectively removes those techs from the game and causes all sorts of problems + val barbarianCiv = gameInfo.getBarbarianCivilization() val unitList = gameInfo.ruleset.units.values .filter { it.isMilitary && !(it.hasUnique(UniqueType.CannotAttack) || diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index 6c44ab227b..4062d41a90 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -97,7 +97,7 @@ class CityStats(val city: City) { //endregion //region Pure Functions - + @Readonly private fun getStatsFromTradeRoute(): Stats { val stats = Stats() val capitalForTradeRoutePurposes = city.civ.getCapital()!! @@ -201,14 +201,14 @@ class CityStats(val city: City) { } - @Readonly @Suppress("purity") // stats[] *= fails + @Readonly private fun getStatsFromUniquesBySource(): StatTreeNode { val sourceToStats = StatTreeNode() val cityStateStatsMultipliers = city.civ.getMatchingUniques(UniqueType.BonusStatsFromCityStates).toList() fun addUniqueStats(unique: Unique) { - val stats = unique.stats.clone() + @LocalState val stats = unique.stats.clone() if (unique.sourceObjectType==UniqueTarget.CityState) for (multiplierUnique in cityStateStatsMultipliers) stats[Stat.valueOf(multiplierUnique.params[1])] *= multiplierUnique.params[0].toPercent() diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index 47c36c39c5..15315abaf8 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -131,7 +131,7 @@ class TileImprovement : RulesetStatsObject() { } }.filter { it !in cannotFilters }.toMutableSet() - @LocalState val terrainsCanBeBuiltOnTypes = sequence { + val terrainsCanBeBuiltOnTypes = sequence { yieldAll(expandedTerrainsCanBeBuiltOn.asSequence() .mapNotNull { ruleset.terrains[it]?.type }) yieldAll( diff --git a/core/src/com/unciv/utils/CollectionExtensions.kt b/core/src/com/unciv/utils/CollectionExtensions.kt index 648b38aaed..585c574671 100644 --- a/core/src/com/unciv/utils/CollectionExtensions.kt +++ b/core/src/com/unciv/utils/CollectionExtensions.kt @@ -2,12 +2,14 @@ package com.unciv.utils import com.badlogic.gdx.utils.Array import yairm210.purity.annotations.Pure +import yairm210.purity.annotations.Readonly import kotlin.random.Random /** Get one random element of a given List. * * The probability for each element is proportional to the value of its corresponding element in the [weights] List. */ +@Readonly fun List.randomWeighted(weights: List, random: Random = Random): T { if (this.isEmpty()) throw NoSuchElementException("Empty list.") if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.") @@ -28,6 +30,7 @@ fun List.randomWeighted(weights: List, random: Random = Random): T * * The probability for each element is proportional to the result of [getWeight] (evaluated only once). */ +@Readonly fun List.randomWeighted(random: Random = Random, getWeight: (T) -> Float): T = randomWeighted(map(getWeight), random) @@ -35,6 +38,7 @@ fun List.randomWeighted(random: Random = Random, getWeight: (T) -> Float) * * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed */ +@Readonly fun ArrayList.withItem(item: T): ArrayList { val newArrayList = ArrayList(this) newArrayList.add(item) @@ -45,6 +49,7 @@ fun ArrayList.withItem(item: T): ArrayList { * * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed */ +@Readonly fun HashSet.withItem(item: T): HashSet { val newHashSet = HashSet(this) newHashSet.add(item) @@ -55,6 +60,7 @@ fun HashSet.withItem(item: T): HashSet { * * Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed */ +@Readonly fun ArrayList.withoutItem(item: T): ArrayList { val newArrayList = ArrayList(this) newArrayList.remove(item) @@ -65,6 +71,7 @@ fun ArrayList.withoutItem(item: T): ArrayList { * * Solves concurrent modification problems - everyone who had a reference to the previous hashSet can keep using it because it hasn't changed */ +@Readonly fun HashSet.withoutItem(item: T): HashSet { val newHashSet = HashSet(this) newHashSet.remove(item) @@ -105,6 +112,7 @@ fun HashMap>.addToMapOfSets(key: KT, element: ET) = getOrPut(key) { hashSetOf() }.add(element) /** Simplifies testing whether in a sparse map of sets the [element] exists for [key]. */ +@Readonly fun HashMap>.contains(key: KT, element: ET) = get(key)?.contains(element) == true