chore(purity): Many autodetected functions and friends 2

This commit is contained in:
yairm210 2025-08-05 00:09:36 +03:00
parent c8893723bf
commit 152acba973
13 changed files with 66 additions and 38 deletions

View File

@ -66,6 +66,9 @@ allprojects {
"kotlin.collections.mutableSetOf", "kotlin.collections.mutableSetOf",
"kotlin.collections.withIndex", // applicable to sequence as well "kotlin.collections.withIndex", // applicable to sequence as well
"kotlin.collections.intersect", "kotlin.collections.intersect",
"kotlin.collections.maxOfOrNull",
"kotlin.collections.minOfOrNull",
"kotlin.reflect.KMutableProperty0.get", // also 1 and 2
) )
wellKnownPureClasses = setOf( wellKnownPureClasses = setOf(
) )

View File

@ -1,6 +1,7 @@
package com.unciv.logic package com.unciv.logic
import com.unciv.models.translations.tr 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. * An [Exception] wrapper marking an Exception as suitable to be shown to the user.
@ -27,6 +28,6 @@ class MissingModsException(
val missingMods: Iterable<String> val missingMods: Iterable<String>
) : UncivShowableException("Missing mods: [${shorten(missingMods)}]") { ) : UncivShowableException("Missing mods: [${shorten(missingMods)}]") {
companion object { companion object {
private fun shorten(missingMods: Iterable<String>) = missingMods.joinToString(limit = 5) { it } @Readonly private fun shorten(missingMods: Iterable<String>) = missingMods.joinToString(limit = 5) { it }
} }
} }

View File

@ -11,7 +11,6 @@ import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.INonPerpetualConstruction import com.unciv.models.ruleset.INonPerpetualConstruction
import com.unciv.models.ruleset.PerpetualConstruction 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.ResourceType
import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache 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.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.ui.screens.victoryscreen.RankingType import com.unciv.ui.screens.victoryscreen.RankingType
import yairm210.purity.annotations.Readonly
import kotlin.math.min import kotlin.math.min
@ -406,6 +406,7 @@ object Automation {
return true return true
} }
@Readonly
fun threatAssessment(assessor: Civilization, assessed: Civilization): ThreatLevel { fun threatAssessment(assessor: Civilization, assessed: Civilization): ThreatLevel {
val powerLevelComparison = val powerLevelComparison =
assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat() assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat()

View File

@ -19,6 +19,7 @@ import com.unciv.logic.trade.TradeOfferType
import com.unciv.models.ruleset.nation.PersonalityValue import com.unciv.models.ruleset.nation.PersonalityValue
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.screens.victoryscreen.RankingType import com.unciv.ui.screens.victoryscreen.RankingType
import yairm210.purity.annotations.Readonly
import kotlin.math.abs import kotlin.math.abs
import kotlin.random.Random import kotlin.random.Random
@ -40,6 +41,7 @@ object DiplomacyAutomation {
} }
} }
@Readonly
internal fun wantsToSignDeclarationOfFrienship(civInfo: Civilization, otherCiv: Civilization): Boolean { internal fun wantsToSignDeclarationOfFrienship(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!! val diploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedDeclarationOfFriendship)) return false if (diploManager.hasFlag(DiplomacyFlags.DeclinedDeclarationOfFriendship)) return false
@ -187,6 +189,7 @@ object DiplomacyAutomation {
/** /**
* Test if [otherCiv] wants to accept our embassy in their capital * Test if [otherCiv] wants to accept our embassy in their capital
*/ */
@Readonly
fun wantsToAcceptEmbassy(civInfo: Civilization, otherCiv: Civilization): Boolean { fun wantsToAcceptEmbassy(civInfo: Civilization, otherCiv: Civilization): Boolean {
val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!! val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!!
if (civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)) return false if (civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)) return false
@ -211,6 +214,7 @@ object DiplomacyAutomation {
return true // Relationship is Afraid or greater return true // Relationship is Afraid or greater
} }
@Readonly
fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean { fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean {
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false
@ -278,6 +282,7 @@ object DiplomacyAutomation {
} }
} }
@Readonly
fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean { fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean {
val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false
@ -450,6 +455,7 @@ object DiplomacyAutomation {
} }
} }
@Readonly
private fun areWeOfferingTrade(civInfo: Civilization, otherCiv: Civilization, offerName: String): Boolean { private fun areWeOfferingTrade(civInfo: Civilization, otherCiv: Civilization, offerName: String): Boolean {
return otherCiv.tradeRequests.filter { request -> request.requestingCiv == civInfo.civName } return otherCiv.tradeRequests.filter { request -> request.requestingCiv == civInfo.civName }
.any { trade -> trade.trade.ourOffers.any { offer -> offer.name == offerName } .any { trade -> trade.trade.ourOffers.any { offer -> offer.name == offerName }

View File

@ -16,11 +16,13 @@ import com.unciv.models.ruleset.nation.PersonalityValue
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.ui.screens.victoryscreen.RankingType import com.unciv.ui.screens.victoryscreen.RankingType
import yairm210.purity.annotations.Readonly
object MotivationToAttackAutomation { object MotivationToAttackAutomation {
/** Will return the motivation to attack, but might short circuit if the value is guaranteed to /** 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. */ * 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 { fun hasAtLeastMotivationToAttack(civInfo: Civilization, targetCiv: Civilization, atLeast: Float): Float {
val diplomacyManager = civInfo.getDiplomacyManager(targetCiv)!! val diplomacyManager = civInfo.getDiplomacyManager(targetCiv)!!
val personality = civInfo.getPersonality() val personality = civInfo.getPersonality()

View File

@ -868,6 +868,7 @@ class Civilization : IsPartOfGameInfoSerialization {
} }
} }
@Readonly
fun hasStatToBuy(stat: Stat, price: Int): Boolean { fun hasStatToBuy(stat: Stat, price: Int): Boolean {
return when { return when {
gameInfo.gameParameters.godMode -> true 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 // region addNotification
fun addNotification(text: String, category: NotificationCategory, vararg notificationIcons: String) = fun addNotification(text: String, category: NotificationCategory, vararg notificationIcons: String) =
addNotification(text, null, category, *notificationIcons) addNotification(text, null, category, *notificationIcons)

View File

@ -255,16 +255,16 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
} }
} }
/** @see compareRelationshipLevel */ /** @see compareRelationshipLevel */
fun isRelationshipLevelEQ(level: RelationshipLevel) = @Readonly fun isRelationshipLevelEQ(level: RelationshipLevel) =
compareRelationshipLevel(level, 0) compareRelationshipLevel(level, 0)
/** @see compareRelationshipLevel */ /** @see compareRelationshipLevel */
fun isRelationshipLevelLT(level: RelationshipLevel) = @Readonly fun isRelationshipLevelLT(level: RelationshipLevel) =
compareRelationshipLevel(level, -1) compareRelationshipLevel(level, -1)
/** @see compareRelationshipLevel */ /** @see compareRelationshipLevel */
fun isRelationshipLevelGT(level: RelationshipLevel) = @Readonly fun isRelationshipLevelGT(level: RelationshipLevel) =
compareRelationshipLevel(level, 1) compareRelationshipLevel(level, 1)
/** @see compareRelationshipLevel */ /** @see compareRelationshipLevel */
fun isRelationshipLevelLE(level: RelationshipLevel) = @Readonly fun isRelationshipLevelLE(level: RelationshipLevel) =
if (level == RelationshipLevel.Ally) true if (level == RelationshipLevel.Ally) true
else compareRelationshipLevel(level + 1, -1) else compareRelationshipLevel(level + 1, -1)
/** @see compareRelationshipLevel */ /** @see compareRelationshipLevel */
@ -468,9 +468,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
} }
/** Returns the [civilizations][Civilization] that know about both sides ([civInfo] and [otherCiv]) */ /** Returns the [civilizations][Civilization] that know about both sides ([civInfo] and [otherCiv]) */
fun getCommonKnownCivs(): Set<Civilization> = civInfo.getKnownCivs().asIterable().intersect(otherCiv().getKnownCivs().toSet()) @Readonly fun getCommonKnownCivs(): Set<Civilization> = civInfo.getKnownCivs().asIterable().intersect(otherCiv().getKnownCivs().toSet())
fun getCommonKnownCivsWithSpectators(): Set<Civilization> = civInfo.getKnownCivsWithSpectators().asIterable().intersect(otherCiv().getKnownCivsWithSpectators().toSet()) @Readonly fun getCommonKnownCivsWithSpectators(): Set<Civilization> = civInfo.getKnownCivsWithSpectators().asIterable().intersect(otherCiv().getKnownCivsWithSpectators().toSet())
/** Returns true when the [civInfo]'s territory is considered allied for [otherCiv]. /** Returns true when the [civInfo]'s territory is considered allied for [otherCiv].
* This includes friendly and allied city-states and the open border treaties. * This includes friendly and allied city-states and the open border treaties.
*/ */

View File

@ -5,6 +5,7 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.ui.screens.victoryscreen.RankingType import com.unciv.ui.screens.victoryscreen.RankingType
import yairm210.purity.annotations.Readonly
/** /**
* Handles optimised operations related to finding threats or allies in an area. * 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. * May be quicker than a manual search because of caching.
* Also ends up calculating and caching [getDistanceToClosestEnemyUnit]. * Also ends up calculating and caching [getDistanceToClosestEnemyUnit].
*/ */
@Readonly @Suppress("purity")
fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList<Tile> { fun getTilesWithEnemyUnitsInDistance(tile: Tile, maxDist: Int): MutableList<Tile> {
val tileData = distanceToClosestEnemyTiles[tile] val tileData = distanceToClosestEnemyTiles[tile]
@ -129,19 +131,15 @@ class ThreatManager(val civInfo: Civilization) {
return tilesWithEnemies return tilesWithEnemies
} }
/**
* Returns all enemy military units within maxDistance of the tile.
*/
fun getEnemyMilitaryUnitsInDistance(tile: Tile, maxDist: Int): List<MapUnit> =
getEnemyUnitsOnTiles(getTilesWithEnemyUnitsInDistance(tile, maxDist))
/** /**
* Returns all enemy military units on tiles * Returns all enemy military units on tiles
*/ */
@Readonly
fun getEnemyUnitsOnTiles(tilesWithEnemyUnitsInDistance:List<Tile>): List<MapUnit> = fun getEnemyUnitsOnTiles(tilesWithEnemyUnitsInDistance:List<Tile>): List<MapUnit> =
tilesWithEnemyUnitsInDistance.flatMap { enemyTile -> enemyTile.getUnits() tilesWithEnemyUnitsInDistance.flatMap { enemyTile -> enemyTile.getUnits()
.filter { it.isMilitary() && civInfo.isAtWarWith(it.civ) } } .filter { it.isMilitary() && civInfo.isAtWarWith(it.civ) } }
@Readonly
fun getDangerousTiles(unit: MapUnit, distance: Int = 3): HashSet<Tile> { fun getDangerousTiles(unit: MapUnit, distance: Int = 3): HashSet<Tile> {
val tilesWithEnemyUnits = getTilesWithEnemyUnitsInDistance(unit.getTile(), distance) val tilesWithEnemyUnits = getTilesWithEnemyUnitsInDistance(unit.getTile(), distance)
val nearbyRangedEnemyUnits = getEnemyUnitsOnTiles(tilesWithEnemyUnits) 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. * Returns true if the tile has a visible enemy, otherwise returns false.
*/ */
@Readonly
fun doesTileHaveMilitaryEnemy(tile: Tile): Boolean { fun doesTileHaveMilitaryEnemy(tile: Tile): Boolean {
if (!tile.isExplored(civInfo)) return false if (!tile.isExplored(civInfo)) return false
if (tile.isCityCenter() && tile.getCity()!!.civ.isAtWarWith(civInfo)) return true 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. */ /** @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<Pair<City,City>> = civInfo.cities.flatMap { fun getNeighboringCitiesOfOtherCivs(): Sequence<Pair<City,City>> = civInfo.cities.flatMap {
ourCity -> ourCity.neighboringCities.filter { it.civ != civInfo }.map { Pair(ourCity, it) } ourCity -> ourCity.neighboringCities.filter { it.civ != civInfo }.map { Pair(ourCity, it) }
}.asSequence() }.asSequence()
fun getNeighboringCivilizations(): Set<Civilization> = civInfo.cities.flatMap { it.neighboringCities }.filter { it.civ != civInfo && civInfo.knows(it.civ) }.map { it.civ }.toSet() @Readonly fun getNeighboringCivilizations(): Set<Civilization> = 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() { fun clear() {
distanceToClosestEnemyTiles.clear() distanceToClosestEnemyTiles.clear()

View File

@ -15,6 +15,7 @@ import com.unciv.models.stats.Stat
import com.unciv.models.stats.StatMap import com.unciv.models.stats.StatMap
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.ui.components.extensions.toPercent import com.unciv.ui.components.extensions.toPercent
import yairm210.purity.annotations.Readonly
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
@ -125,6 +126,7 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) {
return transportationUpkeep return transportationUpkeep
} }
@Readonly
fun getUnitSupply(): Int { fun getUnitSupply(): Int {
/* TotalSupply = BaseSupply + NumCities*modifier + Population*modifier /* TotalSupply = BaseSupply + NumCities*modifier + Population*modifier
* In civ5, it seems population modifier is always 0.5, so i hardcoded it down below */ * 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 return supply
} }
@Readonly
fun getBaseUnitSupply(): Int { fun getBaseUnitSupply(): Int {
return civInfo.getDifficulty().unitSupplyBase + return civInfo.getDifficulty().unitSupplyBase +
civInfo.getMatchingUniques(UniqueType.BaseUnitSupply).sumOf { it.params[0].toInt() } civInfo.getMatchingUniques(UniqueType.BaseUnitSupply).sumOf { it.params[0].toInt() }
} }
@Readonly
fun getUnitSupplyFromCities(): Int { fun getUnitSupplyFromCities(): Int {
return civInfo.cities.size * return civInfo.cities.size *
(civInfo.getDifficulty().unitSupplyPerCity (civInfo.getDifficulty().unitSupplyPerCity
+ civInfo.getMatchingUniques(UniqueType.UnitSupplyPerCity).sumOf { it.params[0].toInt() }) + civInfo.getMatchingUniques(UniqueType.UnitSupplyPerCity).sumOf { it.params[0].toInt() })
} }
@Readonly
fun getUnitSupplyFromPop(): Int { fun getUnitSupplyFromPop(): Int {
var totalSupply = civInfo.cities.sumOf { it.population.population } * civInfo.gameInfo.ruleset.modOptions.constants.unitSupplyPerPopulation 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() 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%. */ /** 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 { fun getStatMapForNextTurn(): StatMap {
val statMap = StatMap() val statMap = StatMap()

View File

@ -1,6 +1,7 @@
package com.unciv.logic.map package com.unciv.logic.map
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import yairm210.purity.annotations.Readonly
import kotlin.collections.ArrayDeque 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 * @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<Tile> = sequence { fun getPathTo(destination: Tile): Sequence<Tile> = sequence {
var currentNode = destination var currentNode = destination
while (true) { while (true) {

View File

@ -237,6 +237,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
@Readonly fun getTile(): Tile = currentTile @Readonly fun getTile(): Tile = currentTile
@Readonly
fun getClosestCity(): City? = civ.cities.minByOrNull { fun getClosestCity(): City? = civ.cities.minByOrNull {
it.getCenterTile().aerialDistanceTo(currentTile) it.getCenterTile().aerialDistanceTo(currentTile)
} }

View File

@ -1,11 +1,12 @@
package com.unciv.models.ruleset.nation package com.unciv.models.ruleset.nation
import com.unciv.Constants import com.unciv.Constants
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.RulesetObject import com.unciv.models.ruleset.RulesetObject
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import kotlin.reflect.KMutableProperty0 import kotlin.reflect.KMutableProperty0
/** /**
@ -65,6 +66,7 @@ class Personality: RulesetObject() {
var preferredVictoryType: String = Constants.neutralVictoryType var preferredVictoryType: String = Constants.neutralVictoryType
var isNeutralPersonality: Boolean = false var isNeutralPersonality: Boolean = false
@Pure
private fun nameToVariable(value: PersonalityValue): KMutableProperty0<Float> { private fun nameToVariable(value: PersonalityValue): KMutableProperty0<Float> {
return when(value) { return when(value) {
PersonalityValue.Production -> ::production 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 * 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 { fun scaledFocus(value: PersonalityValue): Float {
return nameToVariable(value).get() / 5 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 * 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 { fun inverseScaledFocus(value: PersonalityValue): Float {
return (10 - nameToVariable(value).get()) / 5 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 * @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 * @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 { fun modifierFocus(value: PersonalityValue, weight: Float): Float {
return 1f + (scaledFocus(value) - 1) * weight 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 * @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 * @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 { fun inverseModifierFocus(value: PersonalityValue, weight: Float): Float {
return 1f - (inverseScaledFocus(value) - 2) * weight 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 * @param weight a positive value that determines how much the personality should impact the stats given
*/ */
fun scaleStats(stats: Stats, weight: Float): Stats { 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 return stats
} }

View File

@ -9,19 +9,24 @@ import com.unciv.models.translations.removeConditionals
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.fonts.FontRulesetIcons import com.unciv.ui.components.fonts.FontRulesetIcons
import com.unciv.ui.components.fonts.Fonts import com.unciv.ui.components.fonts.Fonts
import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly
import kotlin.math.ceil import kotlin.math.ceil
object UnitActionModifiers { object UnitActionModifiers {
@Readonly
fun canUse(unit: MapUnit, actionUnique: Unique): Boolean { fun canUse(unit: MapUnit, actionUnique: Unique): Boolean {
val usagesLeft = usagesLeft(unit, actionUnique) val usagesLeft = usagesLeft(unit, actionUnique)
return usagesLeft == null || usagesLeft > 0 return usagesLeft == null || usagesLeft > 0
} }
@Readonly
fun getUsableUnitActionUniques(unit: MapUnit, actionUniqueType: UniqueType) = fun getUsableUnitActionUniques(unit: MapUnit, actionUniqueType: UniqueType) =
unit.getMatchingUniques(actionUniqueType) unit.getMatchingUniques(actionUniqueType)
.filter { unique -> !unique.hasModifier(UniqueType.UnitActionExtraLimitedTimes) } .filter { unique -> !unique.hasModifier(UniqueType.UnitActionExtraLimitedTimes) }
.filter { canUse(unit, it) } .filter { canUse(unit, it) }
@Readonly
private fun getMovementPointsToUse(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): Int { private fun getMovementPointsToUse(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): Int {
if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll)) if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll))
return unit.getMaxMovement() return unit.getMaxMovement()
@ -34,6 +39,7 @@ object UnitActionModifiers {
return if (defaultAllMovement) unit.getMaxMovement() else 1 return if (defaultAllMovement) unit.getMaxMovement() else 1
} }
@Readonly
private fun getMovementPointsRequired(actionUnique: Unique): Int { private fun getMovementPointsRequired(actionUnique: Unique): Int {
if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll)) if (actionUnique.hasModifier(UniqueType.UnitActionMovementCostAll))
return 1 return 1
@ -46,6 +52,7 @@ object UnitActionModifiers {
* going into the negatives * going into the negatives
* @return Boolean * @return Boolean
*/ */
@Readonly
private fun canSpendStatsCost(unit: MapUnit, actionUnique: Unique): Boolean { private fun canSpendStatsCost(unit: MapUnit, actionUnique: Unique): Boolean {
for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost)) { for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost)) {
for ((stat, value) in conditional.stats) { for ((stat, value) in conditional.stats) {
@ -63,6 +70,7 @@ object UnitActionModifiers {
return true return true
} }
@Readonly
private fun canSpendStockpileCost(unit: MapUnit, actionUnique: Unique): Boolean { private fun canSpendStockpileCost(unit: MapUnit, actionUnique: Unique): Boolean {
for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStockpileCost)) { for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStockpileCost)) {
val amount = conditional.params[0].toInt() val amount = conditional.params[0].toInt()
@ -79,6 +87,7 @@ object UnitActionModifiers {
* @param actionUnique: Unique that defines the Action * @param actionUnique: Unique that defines the Action
* @return Boolean * @return Boolean
*/ */
@Readonly
fun canActivateSideEffects(unit: MapUnit, actionUnique: Unique): Boolean { fun canActivateSideEffects(unit: MapUnit, actionUnique: Unique): Boolean {
if (!canUse(unit, actionUnique)) return false if (!canUse(unit, actionUnique)) return false
if (getMovementPointsRequired(actionUnique) > ceil(unit.currentMovement).toInt()) return false if (getMovementPointsRequired(actionUnique) > ceil(unit.currentMovement).toInt()) return false
@ -134,12 +143,14 @@ object UnitActionModifiers {
} }
/** Returns 'null' if usages are not limited */ /** Returns 'null' if usages are not limited */
@Readonly
private fun usagesLeft(unit: MapUnit, actionUnique: Unique): Int?{ private fun usagesLeft(unit: MapUnit, actionUnique: Unique): Int?{
val usagesTotal = getMaxUsages(unit, actionUnique) ?: return null val usagesTotal = getMaxUsages(unit, actionUnique) ?: return null
val usagesSoFar = unit.abilityToTimesUsed[actionUnique.text.removeConditionals()] ?: 0 val usagesSoFar = unit.abilityToTimesUsed[actionUnique.text.removeConditionals()] ?: 0
return usagesTotal - usagesSoFar return usagesTotal - usagesSoFar
} }
@Readonly
private fun getMaxUsages(unit: MapUnit, actionUnique: Unique): Int? { private fun getMaxUsages(unit: MapUnit, actionUnique: Unique): Int? {
val extraTimes = unit.getMatchingUniques(actionUnique.type!!) val extraTimes = unit.getMatchingUniques(actionUnique.type!!)
.filter { it.text.removeConditionals() == actionUnique.text.removeConditionals() } .filter { it.text.removeConditionals() == actionUnique.text.removeConditionals() }
@ -154,12 +165,14 @@ object UnitActionModifiers {
return null return null
} }
@Readonly
fun actionTextWithSideEffects(originalText: String, actionUnique: Unique, unit: MapUnit): String { fun actionTextWithSideEffects(originalText: String, actionUnique: Unique, unit: MapUnit): String {
val sideEffectString = getSideEffectString(unit, actionUnique) val sideEffectString = getSideEffectString(unit, actionUnique)
if (sideEffectString == "") return originalText if (sideEffectString == "") return originalText
else return "{$originalText} $sideEffectString" else return "{$originalText} $sideEffectString"
} }
@Readonly
fun getSideEffectString(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): String { fun getSideEffectString(unit: MapUnit, actionUnique: Unique, defaultAllMovement: Boolean = false): String {
val effects = ArrayList<String>() val effects = ArrayList<String>()
@ -167,7 +180,7 @@ object UnitActionModifiers {
if (maxUsages!=null) effects += "${usagesLeft(unit, actionUnique)}/$maxUsages" if (maxUsages!=null) effects += "${usagesLeft(unit, actionUnique)}/$maxUsages"
if (actionUnique.hasModifier(UniqueType.UnitActionStatsCost)) { if (actionUnique.hasModifier(UniqueType.UnitActionStatsCost)) {
val statCost = Stats() @LocalState val statCost = Stats()
for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost)) for (conditional in actionUnique.getModifiers(UniqueType.UnitActionStatsCost))
statCost.add(conditional.stats) statCost.add(conditional.stats)
effects += statCost.toStringOnlyIcons(false) effects += statCost.toStringOnlyIcons(false)