chore(purity): Many autodetected functions

This commit is contained in:
yairm210 2025-08-04 18:30:18 +03:00
parent e8e02a367d
commit 04b7f350bd
11 changed files with 45 additions and 52 deletions

View File

@ -38,7 +38,7 @@ plugins {
// This is *with* gradle 8.2 downloaded according the project specs, no idea what that's about // This is *with* gradle 8.2 downloaded according the project specs, no idea what that's about
kotlin("multiplatform") version "1.9.24" kotlin("multiplatform") version "1.9.24"
kotlin("plugin.serialization") version "1.9.24" kotlin("plugin.serialization") version "1.9.24"
id("io.github.yairm210.purity-plugin") version "0.0.42" apply(false) id("io.github.yairm210.purity-plugin") version "0.0.43" apply(false)
} }
allprojects { allprojects {
@ -62,19 +62,14 @@ allprojects {
"java.lang.Class.getField", "java.lang.Class.getField",
// Looks like the Collection.contains is not considered overridden :thunk: // Looks like the Collection.contains is not considered overridden :thunk:
"kotlin.collections.Iterable.iterator", // moved
"kotlin.collections.Collection.containsAll", // moved
"kotlin.collections.filterKeys", // moved
"kotlin.collections.reversed", // moved
"kotlin.collections.minus", // moved
"kotlin.Array.get", "kotlin.Array.get",
"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",
) )
wellKnownPureClasses = setOf( wellKnownPureClasses = setOf(
"java.util.Locale", // moved
) )
warnOnPossibleAnnotations = true
} }
apply(plugin = "eclipse") apply(plugin = "eclipse")

View File

@ -25,47 +25,29 @@ object GUI {
UncivGame.Current.resetToWorldScreen() UncivGame.Current.resetToWorldScreen()
} }
@Readonly fun getSettings(): GameSettings = UncivGame.Current.settings
@Readonly fun isWorldLoaded(): Boolean = UncivGame.Current.worldScreen != null
@Readonly @Readonly
fun getSettings(): GameSettings {
return UncivGame.Current.settings
}
fun isWorldLoaded(): Boolean {
return UncivGame.Current.worldScreen != null
}
fun isMyTurn(): Boolean { fun isMyTurn(): Boolean {
if (!UncivGame.isCurrentInitialized() || !isWorldLoaded()) return false if (!UncivGame.isCurrentInitialized() || !isWorldLoaded()) return false
return UncivGame.Current.worldScreen!!.isPlayersTurn return UncivGame.Current.worldScreen!!.isPlayersTurn
} }
fun isAllowedChangeState(): Boolean { @Readonly fun isAllowedChangeState(): Boolean = UncivGame.Current.worldScreen!!.canChangeState
return UncivGame.Current.worldScreen!!.canChangeState
}
fun getWorldScreen(): WorldScreen { @Readonly fun getWorldScreen(): WorldScreen = UncivGame.Current.worldScreen!!
return UncivGame.Current.worldScreen!!
}
fun getWorldScreenIfActive(): WorldScreen? { @Readonly fun getWorldScreenIfActive(): WorldScreen? = UncivGame.Current.getWorldScreenIfActive()
return UncivGame.Current.getWorldScreenIfActive()
}
fun getMap(): WorldMapHolder { @Readonly fun getMap(): WorldMapHolder = UncivGame.Current.worldScreen!!.mapHolder
return UncivGame.Current.worldScreen!!.mapHolder
}
fun getUnitTable(): UnitTable { @Readonly fun getUnitTable(): UnitTable = UncivGame.Current.worldScreen!!.bottomUnitTable
return UncivGame.Current.worldScreen!!.bottomUnitTable
}
fun getViewingPlayer(): Civilization { @Readonly fun getViewingPlayer(): Civilization = UncivGame.Current.worldScreen!!.viewingCiv
return UncivGame.Current.worldScreen!!.viewingCiv
}
fun getSelectedPlayer(): Civilization { @Readonly fun getSelectedPlayer(): Civilization = UncivGame.Current.worldScreen!!.selectedCiv
return UncivGame.Current.worldScreen!!.selectedCiv
}
/** Disable Undo (as in: forget the way back, but allow future undo checkpoints) */ /** Disable Undo (as in: forget the way back, but allow future undo checkpoints) */
fun clearUndoCheckpoints() { fun clearUndoCheckpoints() {

View File

@ -47,6 +47,8 @@ import com.unciv.utils.launchOnGLThread
import com.unciv.utils.withGLContext import com.unciv.utils.withGLContext
import com.unciv.utils.withThreadPoolContext import com.unciv.utils.withThreadPoolContext
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.io.PrintWriter import java.io.PrintWriter
import java.util.EnumSet import java.util.EnumSet
import java.util.UUID import java.util.UUID
@ -346,7 +348,9 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
/** Get all currently existing screens of type [clazz] /** Get all currently existing screens of type [clazz]
* - Not a generic to allow screenStack to be private * - Not a generic to allow screenStack to be private
*/ */
fun getScreensOfType(clazz: KClass<out BaseScreen>): Sequence<BaseScreen> = screenStack.asSequence().filter { it::class == clazz } @Readonly
fun getScreensOfType(clazz: KClass<out BaseScreen>): Sequence<BaseScreen> =
screenStack.asSequence().filter { it::class == clazz }
/** Dispose and remove all currently existing screens of type [clazz] /** Dispose and remove all currently existing screens of type [clazz]
* - Not a generic to allow screenStack to be private * - Not a generic to allow screenStack to be private
@ -471,6 +475,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
} }
/** Returns the [worldScreen] if it is the currently active screen of the game */ /** Returns the [worldScreen] if it is the currently active screen of the game */
@Readonly
fun getWorldScreenIfActive(): WorldScreen? { fun getWorldScreenIfActive(): WorldScreen? {
return if (screen == worldScreen) worldScreen else null return if (screen == worldScreen) worldScreen else null
} }
@ -494,11 +499,11 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
// Set by Gdx Game.create callback, or the special cases ConsoleLauncher and unit tests make do with out Gdx and set this themselves. // Set by Gdx Game.create callback, or the special cases ConsoleLauncher and unit tests make do with out Gdx and set this themselves.
lateinit var Current: UncivGame lateinit var Current: UncivGame
/** @return `true` if [Current] has been set yet and can be accessed */ /** @return `true` if [Current] has been set yet and can be accessed */
fun isCurrentInitialized() = this::Current.isInitialized @Readonly fun isCurrentInitialized() = this::Current.isInitialized
/** Get the game currently in progress safely - null either if [Current] has not yet been set or if its gameInfo field has no game */ /** Get the game currently in progress safely - null either if [Current] has not yet been set or if its gameInfo field has no game */
fun getGameInfoOrNull() = if (isCurrentInitialized()) Current.gameInfo else null @Readonly fun getGameInfoOrNull() = if (isCurrentInitialized()) Current.gameInfo else null
fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.gameInfo != null && Current.gameInfo!!.gameId == gameId @Readonly fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.gameInfo != null && Current.gameInfo!!.gameId == gameId
fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null @Readonly fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null
} }
data class Version( data class Version(
@ -507,7 +512,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
) : IsPartOfGameInfoSerialization { ) : IsPartOfGameInfoSerialization {
@Suppress("unused") // used by json serialization @Suppress("unused") // used by json serialization
constructor() : this("", -1) constructor() : this("", -1)
fun toNiceString() = "$text (Build ${number.tr()})" @Pure fun toNiceString() = "$text (Build ${number.tr()})"
} }
} }

View File

@ -4,6 +4,8 @@ import com.badlogic.gdx.math.Vector2
import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.Json
import com.badlogic.gdx.utils.JsonValue import com.badlogic.gdx.utils.JsonValue
import com.unciv.ui.components.extensions.toPrettyString import com.unciv.ui.components.extensions.toPrettyString
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
/** /**
* Dedicated HashMap with [Vector2] keys for [Civilization.lastSeenImprovement]. * Dedicated HashMap with [Vector2] keys for [Civilization.lastSeenImprovement].
@ -37,6 +39,7 @@ open class LastSeenImprovement(
} }
} }
@Pure
private fun String.toVector2(): Vector2 { private fun String.toVector2(): Vector2 {
val (x, y) = removeSurrounding("(", ")").split(',') val (x, y) = removeSurrounding("(", ")").split(',')
return Vector2(x.toFloat(), y.toFloat()) return Vector2(x.toFloat(), y.toFloat())

View File

@ -8,6 +8,7 @@ import com.unciv.logic.civilization.managers.TechManager
import com.unciv.models.ruleset.ModOptions import com.unciv.models.ruleset.ModOptions
import com.unciv.models.ruleset.PerpetualConstruction import com.unciv.models.ruleset.PerpetualConstruction
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import yairm210.purity.annotations.Readonly
/** /**
* Container for all temporarily used code managing transitions from deprecated elements to their replacements. * Container for all temporarily used code managing transitions from deprecated elements to their replacements.
@ -98,6 +99,7 @@ object BackwardCompatibility {
city.cityConstructions.builtBuildings.remove(building) city.cityConstructions.builtBuildings.remove(building)
} }
@Readonly
fun isInvalidConstruction(construction: String) = fun isInvalidConstruction(construction: String) =
!ruleset.buildings.containsKey(construction) !ruleset.buildings.containsKey(construction)
&& !ruleset.units.containsKey(construction) && !ruleset.units.containsKey(construction)

View File

@ -47,6 +47,7 @@ import com.unciv.ui.screens.savescreens.Gzip
import com.unciv.ui.screens.worldscreen.status.NextTurnProgress import com.unciv.ui.screens.worldscreen.status.NextTurnProgress
import com.unciv.utils.DebugUtils import com.unciv.utils.DebugUtils
import com.unciv.utils.debug import com.unciv.utils.debug
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly import yairm210.purity.annotations.Readonly
import java.security.MessageDigest import java.security.MessageDigest
import java.util.UUID import java.util.UUID
@ -83,8 +84,7 @@ data class CompatibilityVersion(
@Suppress("unused") // used by json serialization @Suppress("unused") // used by json serialization
constructor() : this(-1, Version()) constructor() : this(-1, Version())
operator fun compareTo(other: CompatibilityVersion) = number.compareTo(other.number) @Pure operator fun compareTo(other: CompatibilityVersion) = number.compareTo(other.number)
} }
data class VictoryData(val winningCiv: String, val victoryType: String, val victoryTurn: Int) { data class VictoryData(val winningCiv: String, val victoryType: String, val victoryTurn: Int) {
@ -371,6 +371,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// Skip the player if we are playing hotseat // Skip the player if we are playing hotseat
// If all hotseat players are defeated then skip all but the first one // If all hotseat players are defeated then skip all but the first one
@Readonly
fun shouldAutoProcessHotseatPlayer(): Boolean = fun shouldAutoProcessHotseatPlayer(): Boolean =
!isOnline && !isOnline &&
player.isDefeated() && (civilizations.any { it.isHuman() && it.isAlive() } player.isDefeated() && (civilizations.any { it.isHuman() && it.isAlive() }
@ -378,6 +379,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// Skip all spectators and defeated players // Skip all spectators and defeated players
// If all players are defeated then let the first player control next turn // If all players are defeated then let the first player control next turn
@Readonly
fun shouldAutoProcessOnlinePlayer(): Boolean = fun shouldAutoProcessOnlinePlayer(): Boolean =
isOnline && (player.isSpectator() || player.isDefeated() && isOnline && (player.isSpectator() || player.isDefeated() &&
(civilizations.any { it.isHuman() && it.isAlive() } (civilizations.any { it.isHuman() && it.isAlive() }
@ -474,7 +476,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
) )
} }
fun getEnabledVictories() = ruleset.victories.filter { !it.value.hiddenInVictoryScreen && gameParameters.victoryTypes.contains(it.key) } @Readonly fun getEnabledVictories() = ruleset.victories.filter { !it.value.hiddenInVictoryScreen && gameParameters.victoryTypes.contains(it.key) }
fun processDiplomaticVictory() { fun processDiplomaticVictory() {
if (diplomaticVictoryVotesProcessed) return if (diplomaticVictoryVotesProcessed) return
@ -778,7 +780,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
//endregion //endregion
fun asPreview() = GameInfoPreview(this) @Readonly fun asPreview() = GameInfoPreview(this)
} }
@ -809,9 +811,9 @@ class GameInfoPreview() {
currentTurnStartTime = gameInfo.currentTurnStartTime currentTurnStartTime = gameInfo.currentTurnStartTime
} }
fun getCivilization(civName: String) = civilizations.first { it.civName == civName } @Readonly fun getCivilization(civName: String) = civilizations.first { it.civName == civName }
fun getCurrentPlayerCiv() = getCivilization(currentPlayer) @Readonly fun getCurrentPlayerCiv() = getCivilization(currentPlayer)
fun getPlayerCiv(playerId: String) = civilizations.firstOrNull { it.playerId == playerId } @Readonly fun getPlayerCiv(playerId: String) = civilizations.firstOrNull { it.playerId == playerId }
} }
/** Class to use when parsing jsons if you only want the serialization [version]. */ /** Class to use when parsing jsons if you only want the serialization [version]. */

View File

@ -24,6 +24,7 @@ import com.unciv.models.stats.Stats
import com.unciv.models.translations.equalsPlaceholderText import com.unciv.models.translations.equalsPlaceholderText
import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.utils.debug import com.unciv.utils.debug
import yairm210.purity.annotations.Readonly
object GameStarter { object GameStarter {
// temporary instrumentation while tuning/debugging // temporary instrumentation while tuning/debugging
@ -577,6 +578,7 @@ object GameStarter {
}.sortedByDescending { it.isHuman() } // More important for humans to get their start biases! }.sortedByDescending { it.isHuman() } // More important for humans to get their start biases!
} }
@Readonly
private fun getFreeTiles(tileMap: TileMap, landTilesInBigEnoughGroup: Map<Tile, Float>, minimumDistanceBetweenStartingLocations: Int): MutableList<Tile> { private fun getFreeTiles(tileMap: TileMap, landTilesInBigEnoughGroup: Map<Tile, Float>, minimumDistanceBetweenStartingLocations: Int): MutableList<Tile> {
return landTilesInBigEnoughGroup.asSequence() return landTilesInBigEnoughGroup.asSequence()
.filter { .filter {

View File

@ -1,6 +1,8 @@
package com.unciv.logic package com.unciv.logic
import com.unciv.ui.screens.mainmenuscreen.EasterEggFloatingArt import com.unciv.ui.screens.mainmenuscreen.EasterEggFloatingArt
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.time.DayOfWeek import java.time.DayOfWeek
import java.time.Instant import java.time.Instant
import java.time.LocalDate import java.time.LocalDate
@ -123,7 +125,7 @@ object HolidayDates {
abstract fun getByYear(year: Int): DateRange abstract fun getByYear(year: Int): DateRange
companion object { companion object {
fun safeValueOf(name: String) = entries.firstOrNull { it.name == name } @Pure fun safeValueOf(name: String) = entries.firstOrNull { it.name == name }
} }
} }

View File

@ -1,5 +1,6 @@
package com.unciv.logic package com.unciv.logic
import yairm210.purity.annotations.Readonly
import java.util.Locale import java.util.Locale
import kotlin.math.abs import kotlin.math.abs

View File

@ -21,7 +21,7 @@ object CityResources {
/** Only for *city-wide* resources - civ-wide resources should use civ-level resources */ /** Only for *city-wide* resources - civ-wide resources should use civ-level resources */
@Readonly @Readonly
fun getCityResourcesAvailableToCity(city: City): ResourceSupplyList { fun getCityResourcesAvailableToCity(city: City): ResourceSupplyList {
@LocalState val resourceModifers = HashMap<String, Float>() val resourceModifers = HashMap<String, Float>()
for (resource in city.civ.gameInfo.ruleset.tileResources.values) for (resource in city.civ.gameInfo.ruleset.tileResources.values)
resourceModifers[resource.name] = city.civ.getResourceModifier(resource) resourceModifers[resource.name] = city.civ.getResourceModifier(resource)

View File

@ -473,7 +473,6 @@ class Civilization : IsPartOfGameInfoSerialization {
*/ */
@Readonly @Readonly
fun getCivResourcesByName(): HashMap<String, Int> { fun getCivResourcesByName(): HashMap<String, Int> {
@LocalState
val hashMap = HashMap<String, Int>(gameInfo.ruleset.tileResources.size) val hashMap = HashMap<String, Int>(gameInfo.ruleset.tileResources.size)
for (resource in gameInfo.ruleset.tileResources.keys) hashMap[resource] = 0 for (resource in gameInfo.ruleset.tileResources.keys) hashMap[resource] = 0
for (entry in getCivResourceSupply()) for (entry in getCivResourceSupply())