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
kotlin("multiplatform") 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 {
@ -62,19 +62,14 @@ allprojects {
"java.lang.Class.getField",
// 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.collections.mutableSetOf",
"kotlin.collections.withIndex", // applicable to sequence as well
"kotlin.collections.intersect",
)
wellKnownPureClasses = setOf(
"java.util.Locale", // moved
)
warnOnPossibleAnnotations = true
}
apply(plugin = "eclipse")

View File

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

View File

@ -47,6 +47,8 @@ import com.unciv.utils.launchOnGLThread
import com.unciv.utils.withGLContext
import com.unciv.utils.withThreadPoolContext
import kotlinx.coroutines.CancellationException
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.io.PrintWriter
import java.util.EnumSet
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]
* - 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]
* - 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 */
@Readonly
fun getWorldScreenIfActive(): WorldScreen? {
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.
lateinit var Current: UncivGame
/** @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 */
fun getGameInfoOrNull() = if (isCurrentInitialized()) Current.gameInfo else null
fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.gameInfo != null && Current.gameInfo!!.gameId == gameId
fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null
@Readonly fun getGameInfoOrNull() = if (isCurrentInitialized()) Current.gameInfo else null
@Readonly fun isCurrentGame(gameId: String): Boolean = isCurrentInitialized() && Current.gameInfo != null && Current.gameInfo!!.gameId == gameId
@Readonly fun isDeepLinkedGameLoading() = isCurrentInitialized() && Current.deepLinkedMultiplayerGame != null
}
data class Version(
@ -507,7 +512,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
) : IsPartOfGameInfoSerialization {
@Suppress("unused") // used by json serialization
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.JsonValue
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].
@ -37,6 +39,7 @@ open class LastSeenImprovement(
}
}
@Pure
private fun String.toVector2(): Vector2 {
val (x, y) = removeSurrounding("(", ")").split(',')
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.PerpetualConstruction
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.
@ -98,6 +99,7 @@ object BackwardCompatibility {
city.cityConstructions.builtBuildings.remove(building)
}
@Readonly
fun isInvalidConstruction(construction: String) =
!ruleset.buildings.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.utils.DebugUtils
import com.unciv.utils.debug
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.security.MessageDigest
import java.util.UUID
@ -83,8 +84,7 @@ data class CompatibilityVersion(
@Suppress("unused") // used by json serialization
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) {
@ -371,6 +371,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// Skip the player if we are playing hotseat
// If all hotseat players are defeated then skip all but the first one
@Readonly
fun shouldAutoProcessHotseatPlayer(): Boolean =
!isOnline &&
player.isDefeated() && (civilizations.any { it.isHuman() && it.isAlive() }
@ -378,6 +379,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
// Skip all spectators and defeated players
// If all players are defeated then let the first player control next turn
@Readonly
fun shouldAutoProcessOnlinePlayer(): Boolean =
isOnline && (player.isSpectator() || player.isDefeated() &&
(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() {
if (diplomaticVictoryVotesProcessed) return
@ -778,7 +780,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
//endregion
fun asPreview() = GameInfoPreview(this)
@Readonly fun asPreview() = GameInfoPreview(this)
}
@ -809,9 +811,9 @@ class GameInfoPreview() {
currentTurnStartTime = gameInfo.currentTurnStartTime
}
fun getCivilization(civName: String) = civilizations.first { it.civName == civName }
fun getCurrentPlayerCiv() = getCivilization(currentPlayer)
fun getPlayerCiv(playerId: String) = civilizations.firstOrNull { it.playerId == playerId }
@Readonly fun getCivilization(civName: String) = civilizations.first { it.civName == civName }
@Readonly fun getCurrentPlayerCiv() = getCivilization(currentPlayer)
@Readonly fun getPlayerCiv(playerId: String) = civilizations.firstOrNull { it.playerId == playerId }
}
/** 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.getPlaceholderParameters
import com.unciv.utils.debug
import yairm210.purity.annotations.Readonly
object GameStarter {
// temporary instrumentation while tuning/debugging
@ -577,6 +578,7 @@ object GameStarter {
}.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> {
return landTilesInBigEnoughGroup.asSequence()
.filter {

View File

@ -1,6 +1,8 @@
package com.unciv.logic
import com.unciv.ui.screens.mainmenuscreen.EasterEggFloatingArt
import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly
import java.time.DayOfWeek
import java.time.Instant
import java.time.LocalDate
@ -123,7 +125,7 @@ object HolidayDates {
abstract fun getByYear(year: Int): DateRange
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
import yairm210.purity.annotations.Readonly
import java.util.Locale
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 */
@Readonly
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)
resourceModifers[resource.name] = city.civ.getResourceModifier(resource)

View File

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