diff --git a/build.gradle.kts b/build.gradle.kts index fe48664f6f..2a14e20dc3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,6 +66,9 @@ allprojects { "kotlin.collections.mutableSetOf", "kotlin.collections.withIndex", // applicable to sequence as well "kotlin.collections.intersect", + "kotlin.collections.maxOfOrNull", + "kotlin.collections.minOfOrNull", + "kotlin.reflect.KMutableProperty0.get", // also 1 and 2 ) wellKnownPureClasses = setOf( ) diff --git a/core/src/com/unciv/logic/UncivExceptions.kt b/core/src/com/unciv/logic/UncivExceptions.kt index 6e855cd9f1..d4c41141b7 100644 --- a/core/src/com/unciv/logic/UncivExceptions.kt +++ b/core/src/com/unciv/logic/UncivExceptions.kt @@ -1,6 +1,7 @@ package com.unciv.logic import com.unciv.models.translations.tr +import yairm210.purity.annotations.Readonly /** * An [Exception] wrapper marking an Exception as suitable to be shown to the user. @@ -27,6 +28,6 @@ class MissingModsException( val missingMods: Iterable ) : UncivShowableException("Missing mods: [${shorten(missingMods)}]") { companion object { - private fun shorten(missingMods: Iterable) = missingMods.joinToString(limit = 5) { it } + @Readonly private fun shorten(missingMods: Iterable) = missingMods.joinToString(limit = 5) { it } } } diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index f3eda72e5e..76f8e2e81f 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -11,7 +11,6 @@ import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.INonPerpetualConstruction import com.unciv.models.ruleset.PerpetualConstruction -import com.unciv.models.ruleset.nation.PersonalityValue import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.LocalUniqueCache @@ -21,6 +20,7 @@ import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.ui.screens.victoryscreen.RankingType +import yairm210.purity.annotations.Readonly import kotlin.math.min @@ -406,6 +406,7 @@ object Automation { return true } + @Readonly fun threatAssessment(assessor: Civilization, assessed: Civilization): ThreatLevel { val powerLevelComparison = assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat() diff --git a/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt b/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt index 0a166f0e61..d9eabfccc4 100644 --- a/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt @@ -19,6 +19,7 @@ import com.unciv.logic.trade.TradeOfferType import com.unciv.models.ruleset.nation.PersonalityValue import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.screens.victoryscreen.RankingType +import yairm210.purity.annotations.Readonly import kotlin.math.abs import kotlin.random.Random @@ -40,6 +41,7 @@ object DiplomacyAutomation { } } + @Readonly internal fun wantsToSignDeclarationOfFrienship(civInfo: Civilization, otherCiv: Civilization): Boolean { val diploManager = civInfo.getDiplomacyManager(otherCiv)!! if (diploManager.hasFlag(DiplomacyFlags.DeclinedDeclarationOfFriendship)) return false @@ -187,6 +189,7 @@ object DiplomacyAutomation { /** * Test if [otherCiv] wants to accept our embassy in their capital */ + @Readonly fun wantsToAcceptEmbassy(civInfo: Civilization, otherCiv: Civilization): Boolean { val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!! if (civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)) return false @@ -211,6 +214,7 @@ object DiplomacyAutomation { return true // Relationship is Afraid or greater } + @Readonly fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean { val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false @@ -278,6 +282,7 @@ object DiplomacyAutomation { } } + @Readonly fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean { val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false @@ -450,6 +455,7 @@ object DiplomacyAutomation { } } + @Readonly private fun areWeOfferingTrade(civInfo: Civilization, otherCiv: Civilization, offerName: String): Boolean { return otherCiv.tradeRequests.filter { request -> request.requestingCiv == civInfo.civName } .any { trade -> trade.trade.ourOffers.any { offer -> offer.name == offerName } diff --git a/core/src/com/unciv/logic/automation/civilization/MotivationToAttackAutomation.kt b/core/src/com/unciv/logic/automation/civilization/MotivationToAttackAutomation.kt index 390c2d9997..e903777b7e 100644 --- a/core/src/com/unciv/logic/automation/civilization/MotivationToAttackAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/MotivationToAttackAutomation.kt @@ -16,11 +16,13 @@ import com.unciv.models.ruleset.nation.PersonalityValue import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.ui.screens.victoryscreen.RankingType +import yairm210.purity.annotations.Readonly object MotivationToAttackAutomation { /** Will return the motivation to attack, but might short circuit if the value is guaranteed to * be lower than `atLeast`. So any values below `atLeast` should not be used for comparison. */ + @Readonly @Suppress("purity") fun hasAtLeastMotivationToAttack(civInfo: Civilization, targetCiv: Civilization, atLeast: Float): Float { val diplomacyManager = civInfo.getDiplomacyManager(targetCiv)!! val personality = civInfo.getPersonality() diff --git a/core/src/com/unciv/logic/civilization/Civilization.kt b/core/src/com/unciv/logic/civilization/Civilization.kt index 9049b3f2f7..8f9954d864 100644 --- a/core/src/com/unciv/logic/civilization/Civilization.kt +++ b/core/src/com/unciv/logic/civilization/Civilization.kt @@ -868,6 +868,7 @@ class Civilization : IsPartOfGameInfoSerialization { } } + @Readonly fun hasStatToBuy(stat: Stat, price: Int): Boolean { return when { gameInfo.gameParameters.godMode -> true @@ -930,22 +931,6 @@ class Civilization : IsPartOfGameInfoSerialization { } } - fun getReserve(stat: GameResource): Int { - if (stat is TileResource && !stat.isCityWide && stat.isStockpiled) - return resourceStockpiles[stat.name] - return when (stat) { - Stat.Culture -> policies.storedCulture - Stat.Science -> { - if (tech.currentTechnology() == null) 0 - else tech.researchOfTech(tech.currentTechnology()!!.name) - } - Stat.Gold -> gold - Stat.Faith -> religionManager.storedFaith - SubStat.GoldenAgePoints -> goldenAges.storedHappiness - else -> 0 - } - } - // region addNotification fun addNotification(text: String, category: NotificationCategory, vararg notificationIcons: String) = addNotification(text, null, category, *notificationIcons) diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 058c7cad6a..fdb9ad90cf 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -255,16 +255,16 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { } } /** @see compareRelationshipLevel */ - fun isRelationshipLevelEQ(level: RelationshipLevel) = + @Readonly fun isRelationshipLevelEQ(level: RelationshipLevel) = compareRelationshipLevel(level, 0) /** @see compareRelationshipLevel */ - fun isRelationshipLevelLT(level: RelationshipLevel) = + @Readonly fun isRelationshipLevelLT(level: RelationshipLevel) = compareRelationshipLevel(level, -1) /** @see compareRelationshipLevel */ - fun isRelationshipLevelGT(level: RelationshipLevel) = + @Readonly fun isRelationshipLevelGT(level: RelationshipLevel) = compareRelationshipLevel(level, 1) /** @see compareRelationshipLevel */ - fun isRelationshipLevelLE(level: RelationshipLevel) = + @Readonly fun isRelationshipLevelLE(level: RelationshipLevel) = if (level == RelationshipLevel.Ally) true else compareRelationshipLevel(level + 1, -1) /** @see compareRelationshipLevel */ @@ -468,9 +468,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { } /** Returns the [civilizations][Civilization] that know about both sides ([civInfo] and [otherCiv]) */ - fun getCommonKnownCivs(): Set = civInfo.getKnownCivs().asIterable().intersect(otherCiv().getKnownCivs().toSet()) + @Readonly fun getCommonKnownCivs(): Set = civInfo.getKnownCivs().asIterable().intersect(otherCiv().getKnownCivs().toSet()) - fun getCommonKnownCivsWithSpectators(): Set = civInfo.getKnownCivsWithSpectators().asIterable().intersect(otherCiv().getKnownCivsWithSpectators().toSet()) + @Readonly fun getCommonKnownCivsWithSpectators(): Set = civInfo.getKnownCivsWithSpectators().asIterable().intersect(otherCiv().getKnownCivsWithSpectators().toSet()) /** Returns true when the [civInfo]'s territory is considered allied for [otherCiv]. * This includes friendly and allied city-states and the open border treaties. */ diff --git a/core/src/com/unciv/logic/civilization/managers/ThreatManager.kt b/core/src/com/unciv/logic/civilization/managers/ThreatManager.kt index 6b65e06b05..fd584a1d32 100644 --- a/core/src/com/unciv/logic/civilization/managers/ThreatManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/ThreatManager.kt @@ -5,6 +5,7 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.tile.Tile import com.unciv.ui.screens.victoryscreen.RankingType +import yairm210.purity.annotations.Readonly /** * Handles optimised operations related to finding threats or allies in an area. @@ -83,6 +84,7 @@ class ThreatManager(val civInfo: Civilization) { * May be quicker than a manual search because of caching. * Also ends up calculating and caching [getDistanceToClosestEnemyUnit]. */ + @Readonly @Suppress("purity") fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList { val tileData = distanceToClosestEnemyTiles[tile] @@ -129,19 +131,15 @@ class ThreatManager(val civInfo: Civilization) { return tilesWithEnemies } - /** - * Returns all enemy military units within maxDistance of the tile. - */ - fun getEnemyMilitaryUnitsInDistance(tile: Tile, maxDist: Int): List = - getEnemyUnitsOnTiles(getTilesWithEnemyUnitsInDistance(tile, maxDist)) - /** * Returns all enemy military units on tiles */ + @Readonly fun getEnemyUnitsOnTiles(tilesWithEnemyUnitsInDistance:List): List = tilesWithEnemyUnitsInDistance.flatMap { enemyTile -> enemyTile.getUnits() .filter { it.isMilitary() && civInfo.isAtWarWith(it.civ) } } - + + @Readonly fun getDangerousTiles(unit: MapUnit, distance: Int = 3): HashSet { val tilesWithEnemyUnits = getTilesWithEnemyUnitsInDistance(unit.getTile(), distance) val nearbyRangedEnemyUnits = getEnemyUnitsOnTiles(tilesWithEnemyUnits) @@ -162,6 +160,7 @@ class ThreatManager(val civInfo: Civilization) { /** * Returns true if the tile has a visible enemy, otherwise returns false. */ + @Readonly fun doesTileHaveMilitaryEnemy(tile: Tile): Boolean { if (!tile.isExplored(civInfo)) return false if (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo)) return true @@ -174,13 +173,17 @@ class ThreatManager(val civInfo: Civilization) { } /** @return a sequence of pairs of cities, the first city is our city and the second city is a nearby city that is not from our civ. */ + @Readonly fun getNeighboringCitiesOfOtherCivs(): Sequence> = civInfo.cities.flatMap { ourCity -> ourCity.neighboringCities.filter { it.civ != civInfo }.map { Pair(ourCity, it) } }.asSequence() - fun getNeighboringCivilizations(): Set = civInfo.cities.flatMap { it.neighboringCities }.filter { it.civ != civInfo && civInfo.knows(it.civ) }.map { it.civ }.toSet() + @Readonly fun getNeighboringCivilizations(): Set = civInfo.cities + .flatMap { it.neighboringCities } + .filter { it.civ != civInfo && civInfo.knows(it.civ) } + .map { it.civ }.toSet() - fun getCombinedForceOfWarringCivs(): Int = civInfo.getCivsAtWarWith().sumOf { it.getStatForRanking(RankingType.Force) } + @Readonly fun getCombinedForceOfWarringCivs(): Int = civInfo.getCivsAtWarWith().sumOf { it.getStatForRanking(RankingType.Force) } fun clear() { distanceToClosestEnemyTiles.clear() diff --git a/core/src/com/unciv/logic/civilization/transients/CivInfoStatsForNextTurn.kt b/core/src/com/unciv/logic/civilization/transients/CivInfoStatsForNextTurn.kt index 9cd2f4d68f..699cc02218 100644 --- a/core/src/com/unciv/logic/civilization/transients/CivInfoStatsForNextTurn.kt +++ b/core/src/com/unciv/logic/civilization/transients/CivInfoStatsForNextTurn.kt @@ -15,6 +15,7 @@ import com.unciv.models.stats.Stat import com.unciv.models.stats.StatMap import com.unciv.models.stats.Stats import com.unciv.ui.components.extensions.toPercent +import yairm210.purity.annotations.Readonly import kotlin.math.max import kotlin.math.min import kotlin.math.pow @@ -125,6 +126,7 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) { return transportationUpkeep } + @Readonly fun getUnitSupply(): Int { /* TotalSupply = BaseSupply + NumCities*modifier + Population*modifier * In civ5, it seems population modifier is always 0.5, so i hardcoded it down below */ @@ -135,15 +137,18 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) { return supply } + @Readonly fun getBaseUnitSupply(): Int { return civInfo.getDifficulty().unitSupplyBase + civInfo.getMatchingUniques(UniqueType.BaseUnitSupply).sumOf { it.params[0].toInt() } } + @Readonly fun getUnitSupplyFromCities(): Int { return civInfo.cities.size * (civInfo.getDifficulty().unitSupplyPerCity + civInfo.getMatchingUniques(UniqueType.UnitSupplyPerCity).sumOf { it.params[0].toInt() }) } + @Readonly fun getUnitSupplyFromPop(): Int { var totalSupply = civInfo.cities.sumOf { it.population.population } * civInfo.gameInfo.ruleset.modOptions.constants.unitSupplyPerPopulation @@ -155,10 +160,10 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) { } return totalSupply.toInt() } - fun getUnitSupplyDeficit(): Int = max(0,civInfo.units.getCivUnitsSize() - getUnitSupply()) + @Readonly fun getUnitSupplyDeficit(): Int = max(0,civInfo.units.getCivUnitsSize() - getUnitSupply()) /** Per each supply missing, a player gets -10% production. Capped at -70%. */ - fun getUnitSupplyProductionPenalty(): Float = -min(getUnitSupplyDeficit() * 10f, 70f) + @Readonly fun getUnitSupplyProductionPenalty(): Float = -min(getUnitSupplyDeficit() * 10f, 70f) fun getStatMapForNextTurn(): StatMap { val statMap = StatMap() diff --git a/core/src/com/unciv/logic/map/BFS.kt b/core/src/com/unciv/logic/map/BFS.kt index af63589e15..923d282861 100644 --- a/core/src/com/unciv/logic/map/BFS.kt +++ b/core/src/com/unciv/logic/map/BFS.kt @@ -1,6 +1,7 @@ package com.unciv.logic.map import com.unciv.logic.map.tile.Tile +import yairm210.purity.annotations.Readonly import kotlin.collections.ArrayDeque /** @@ -67,6 +68,7 @@ class BFS( /** * @return a Sequence from the [destination] back to the [startingPoint], including both, or empty if [destination] has not been reached */ + @Readonly fun getPathTo(destination: Tile): Sequence = sequence { var currentNode = destination while (true) { diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index 0e37dee96b..eccc5d1f60 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -237,6 +237,7 @@ class MapUnit : IsPartOfGameInfoSerialization { @Readonly fun getTile(): Tile = currentTile + @Readonly fun getClosestCity(): City? = civ.cities.minByOrNull { it.getCenterTile().aerialDistanceTo(currentTile) } diff --git a/core/src/com/unciv/models/ruleset/nation/Personality.kt b/core/src/com/unciv/models/ruleset/nation/Personality.kt index 6df299bb67..d72123ec84 100644 --- a/core/src/com/unciv/models/ruleset/nation/Personality.kt +++ b/core/src/com/unciv/models/ruleset/nation/Personality.kt @@ -1,11 +1,12 @@ package com.unciv.models.ruleset.nation import com.unciv.Constants -import com.unciv.logic.civilization.Civilization import com.unciv.models.ruleset.RulesetObject import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats +import yairm210.purity.annotations.Pure +import yairm210.purity.annotations.Readonly import kotlin.reflect.KMutableProperty0 /** @@ -65,6 +66,7 @@ class Personality: RulesetObject() { var preferredVictoryType: String = Constants.neutralVictoryType var isNeutralPersonality: Boolean = false + @Pure private fun nameToVariable(value: PersonalityValue): KMutableProperty0 { return when(value) { PersonalityValue.Production -> ::production @@ -95,6 +97,7 @@ class Personality: RulesetObject() { /** * Scales the value to a more meaningful range, where 10 is 2, and 5 is 1, and 0 is 0 */ + @Readonly fun scaledFocus(value: PersonalityValue): Float { return nameToVariable(value).get() / 5 } @@ -102,6 +105,7 @@ class Personality: RulesetObject() { /** * Inverse scales the value to a more meaningful range, where 0 is 2, and 5 is 1 and 10 is 0 */ + @Readonly fun inverseScaledFocus(value: PersonalityValue): Float { return (10 - nameToVariable(value).get()) / 5 } @@ -110,6 +114,7 @@ class Personality: RulesetObject() { * @param weight a value between 0 and 1 that determines how much the modifier deviates from 1 * @return a modifier between 0 and 2 centered around 1 based off of the personality value and the weight given */ + @Readonly fun modifierFocus(value: PersonalityValue, weight: Float): Float { return 1f + (scaledFocus(value) - 1) * weight } @@ -119,6 +124,7 @@ class Personality: RulesetObject() { * @param weight a value between 0 and 1 that determines how much the modifier deviates from 1 * @return a modifier between 0 and 2 centered around 1 based off of the personality value and the weight given */ + @Readonly fun inverseModifierFocus(value: PersonalityValue, weight: Float): Float { return 1f - (inverseScaledFocus(value) - 2) * weight } @@ -128,7 +134,7 @@ class Personality: RulesetObject() { * @param weight a positive value that determines how much the personality should impact the stats given */ fun scaleStats(stats: Stats, weight: Float): Stats { - Stat.values().forEach { stats[it] *= modifierFocus(PersonalityValue[it], weight) } + Stat.entries.forEach { stats[it] *= modifierFocus(PersonalityValue[it], weight) } return stats } diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionModifiers.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionModifiers.kt index ed12a36860..ec70599eb1 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionModifiers.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionModifiers.kt @@ -9,19 +9,24 @@ import com.unciv.models.translations.removeConditionals import com.unciv.models.translations.tr import com.unciv.ui.components.fonts.FontRulesetIcons import com.unciv.ui.components.fonts.Fonts +import yairm210.purity.annotations.LocalState +import yairm210.purity.annotations.Readonly import kotlin.math.ceil object UnitActionModifiers { + @Readonly fun canUse(unit: MapUnit, actionUnique: Unique): Boolean { val usagesLeft = usagesLeft(unit, actionUnique) return usagesLeft == null || usagesLeft > 0 } + @Readonly fun getUsableUnitActionUniques(unit: MapUnit, actionUniqueType: UniqueType) = unit.getMatchingUniques(actionUniqueType) .filter { unique -> !unique.hasModifier(UniqueType.UnitActionExtraLimitedTimes) } .filter { canUse(unit, it) } + @Readonly private fun getMovementPointsToUse(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): Int { if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll)) return unit.getMaxMovement() @@ -34,6 +39,7 @@ object UnitActionModifiers { return if (defaultAllMovement) unit.getMaxMovement() else 1 } + @Readonly private fun getMovementPointsRequired(actionUnique: Unique): Int { if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll)) return 1 @@ -46,6 +52,7 @@ object UnitActionModifiers { * going into the negatives * @return Boolean */ + @Readonly private fun canSpendStatsCost(unit: MapUnit, actionUnique: Unique): Boolean { for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost)) { for ((stat, value) in conditional.stats) { @@ -63,6 +70,7 @@ object UnitActionModifiers { return true } + @Readonly private fun canSpendStockpileCost(unit: MapUnit, actionUnique: Unique): Boolean { for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStockpileCost)) { val amount = conditional.params[0].toInt() @@ -79,6 +87,7 @@ object UnitActionModifiers { * @param actionUnique: Unique that defines the Action * @return Boolean */ + @Readonly fun canActivateSideEffects(unit: MapUnit, actionUnique: Unique): Boolean { if (!canUse(unit, actionUnique)) return false if (getMovementPointsRequired(actionUnique) > ceil(unit.currentMovement).toInt()) return false @@ -134,12 +143,14 @@ object UnitActionModifiers { } /** Returns 'null' if usages are not limited */ + @Readonly private fun usagesLeft(unit: MapUnit, actionUnique: Unique): Int?{ val usagesTotal = getMaxUsages(unit, actionUnique) ?: return null val usagesSoFar = unit.abilityToTimesUsed[actionUnique.text.removeConditionals()] ?: 0 return usagesTotal - usagesSoFar } + @Readonly private fun getMaxUsages(unit: MapUnit, actionUnique: Unique): Int? { val extraTimes = unit.getMatchingUniques(actionUnique.type!!) .filter { it.text.removeConditionals() == actionUnique.text.removeConditionals() } @@ -154,12 +165,14 @@ object UnitActionModifiers { return null } + @Readonly fun actionTextWithSideEffects(originalText: String, actionUnique: Unique, unit: MapUnit): String { val sideEffectString = getSideEffectString(unit, actionUnique) if (sideEffectString == "") return originalText else return "{$originalText} $sideEffectString" } + @Readonly fun getSideEffectString(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): String { val effects = ArrayList() @@ -167,7 +180,7 @@ object UnitActionModifiers { if (maxUsages!=null) effects += "${usagesLeft(unit, actionUnique)}/$maxUsages" if (actionUnique.hasModifier(UniqueType.UnitActionStatsCost)) { - val statCost = Stats() + @LocalState val statCost = Stats() for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost)) statCost.add(conditional.stats) effects += statCost.toStringOnlyIcons(false)