chore(purity): Many autodetected functions and friends

This commit is contained in:
yairm210 2025-08-04 23:54:58 +03:00
parent 04b7f350bd
commit c8893723bf
11 changed files with 38 additions and 26 deletions

View File

@ -198,6 +198,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
@Readonly fun isWeLoveTheKingDayActive() = hasFlag(CityFlags.WeLoveTheKing)
@Readonly fun isInResistance() = hasFlag(CityFlags.Resistance)
@Readonly
fun isBlockaded(): Boolean {
// Coastal cities are blocked if every adjacent water tile is blocked
if (!isCoastal()) return false
@ -258,19 +259,6 @@ class City : IsPartOfGameInfoSerialization, INamed {
}
}
fun getReserve(stat: GameResource): Int {
if (stat is TileResource) {
if (!stat.isStockpiled) return 0
if (stat.isCityWide) return resourceStockpiles[stat.name]
return civ.resourceStockpiles[stat.name]
}
return when (stat) {
Stat.Production -> cityConstructions.getWorkDone(cityConstructions.getCurrentConstruction().name)
Stat.Food, SubStat.StoredFood -> population.foodStored
else -> civ.getReserve(stat)
}
}
@Readonly
fun hasStatToBuy(stat: Stat, price: Int): Boolean {
return when {
@ -460,7 +448,8 @@ class City : IsPartOfGameInfoSerialization, INamed {
population.autoAssignPopulation() // also updates city stats
civ.cache.updateCivResources() // this building could be a resource-requiring one
}
@Readonly
fun canPlaceNewUnit(construction: BaseUnit): Boolean {
val tile = getCenterTile()
return when {

View File

@ -582,6 +582,7 @@ class Civilization : IsPartOfGameInfoSerialization {
}
}
@Readonly
fun shouldOpenTechPicker(): Boolean {
if (!tech.canResearchTech()) return false
if (tech.freeTechs != 0) return true

View File

@ -36,7 +36,7 @@ class PolicyManager : IsPartOfGameInfoSerialization {
var shouldOpenPolicyPicker = false
/** Used by NextTurnAction.PickPolicy.isChoice */
fun shouldShowPolicyPicker() = (shouldOpenPolicyPicker || freePolicies > 0) && canAdoptPolicy()
@Readonly fun shouldShowPolicyPicker() = (shouldOpenPolicyPicker || freePolicies > 0) && canAdoptPolicy()
/** A [Map] pairing each [PolicyBranch] to its priority ([Int]). */
val priorityMap: Map<PolicyBranch, Int>

View File

@ -214,6 +214,7 @@ class TechManager : IsPartOfGameInfoSerialization {
return prerequisites.sortedBy { it.column!!.columnNumber }
}
@Readonly
fun getScienceFromGreatScientist(): Int {
// https://civilization.fandom.com/wiki/Great_Scientist_(Civ5)
return (scienceOfLast8Turns.sum() * civInfo.gameInfo.speed.scienceCostModifier).toInt()
@ -223,6 +224,7 @@ class TechManager : IsPartOfGameInfoSerialization {
scienceOfLast8Turns[civInfo.gameInfo.turns % 8] = science
}
@Readonly
private fun limitOverflowScience(overflowScience: Int): Int {
// http://www.civclub.net/bbs/forum.php?mod=viewthread&tid=123976
// Apparently yes, we care about the absolute tech cost, not the actual calculated-for-this-player tech cost,
@ -231,6 +233,7 @@ class TechManager : IsPartOfGameInfoSerialization {
getRuleset().technologies[currentTechnologyName()]!!.cost))
}
@Readonly
private fun scienceFromResearchAgreements(): Int {
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
var researchAgreementModifier = 0.5f
@ -355,6 +358,7 @@ class TechManager : IsPartOfGameInfoSerialization {
}
/** A variant of kotlin's [associateBy] that omits null values */
@Readonly
private inline fun <T, K, V> Iterable<T>.associateByNotNull(keySelector: (T) -> K, valueTransform: (T) -> V?): Map<K, V> {
val destination = LinkedHashMap<K, V>()
for (element in this) {
@ -540,6 +544,7 @@ class TechManager : IsPartOfGameInfoSerialization {
.all { isResearched(it.name) || !canBeResearched(it.name)}
}
@Readonly
fun getBestRoadAvailable(): RoadStatus {
val railroadImprovement = getRuleset().railroadImprovement // May not exist in mods
if (railroadImprovement != null && (railroadImprovement.techRequired == null || isResearched(railroadImprovement.techRequired!!))
@ -554,7 +559,5 @@ class TechManager : IsPartOfGameInfoSerialization {
return RoadStatus.None
}
fun canResearchTech(): Boolean {
return getRuleset().technologies.values.any { canBeResearched(it.name) }
}
@Readonly fun canResearchTech(): Boolean = getRuleset().technologies.values.any { canBeResearched(it.name) }
}

View File

@ -7,6 +7,7 @@ import com.unciv.logic.map.BFS
import com.unciv.logic.map.tile.RoadStatus
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.unique.UniqueType
import yairm210.purity.annotations.Readonly
import kotlin.collections.set
class CapitalConnectionsFinder(private val civInfo: Civilization) {
@ -87,6 +88,7 @@ class CapitalConnectionsFinder(private val civInfo: Civilization) {
)
}
@Readonly
private fun City.containsHarbor() =
this.containsBuildingUnique(UniqueType.ConnectTradeRoutes)
@ -122,7 +124,8 @@ class CapitalConnectionsFinder(private val civInfo: Civilization) {
citiesReachedToMediums[reachedCity] = mutableSetOf()
}
}
@Readonly
private fun City.wasPreviouslyReached(transportType: String, overridingTransportType: String?): Boolean {
val mediums = citiesReachedToMediums[this]!!
return mediums.contains(transportType) || mediums.contains(overridingTransportType)
@ -132,7 +135,7 @@ class CapitalConnectionsFinder(private val civInfo: Civilization) {
citiesReachedToMediums[this]!!.add(transportType)
}
@Readonly
private fun canEnterBordersOf(otherCiv: Civilization): Boolean {
if (otherCiv == civInfo) return true // own borders are always open
if (otherCiv.isBarbarian || civInfo.isBarbarian) return false // barbarians blocks the routes

View File

@ -19,6 +19,7 @@ import com.unciv.utils.debug
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import yairm210.purity.annotations.Readonly
import java.time.Duration
import java.time.Instant
import java.util.concurrent.atomic.AtomicReference
@ -78,6 +79,7 @@ class Multiplayer {
}.launchIn(CoroutineScope(Dispatcher.DAEMON))
}
@Readonly
private fun getCurrentGame(): MultiplayerGamePreview? {
val gameInfo = UncivGame.Current.gameInfo
return if (gameInfo != null && gameInfo.gameParameters.isOnlineMultiplayer) {
@ -271,6 +273,7 @@ class Multiplayer {
/**
* Checks if [gameInfo] and [preview] are up-to-date with each other.
*/
@Readonly
fun hasLatestGameState(gameInfo: GameInfo, preview: GameInfoPreview): Boolean {
// TODO look into how to maybe extract interfaces to not make this take two different methods
return gameInfo.currentPlayer == preview.currentPlayer
@ -281,6 +284,7 @@ class Multiplayer {
/**
* Checks if [preview1] has a more recent game state than [preview2]
*/
@Readonly
private fun hasNewerGameState(preview1: GameInfoPreview, preview2: GameInfoPreview): Boolean {
return preview1.turns > preview2.turns
}

View File

@ -6,6 +6,7 @@ import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview
import com.unciv.logic.event.EventBus
import com.unciv.utils.debug
import yairm210.purity.annotations.Readonly
import java.time.Instant
import java.util.*
@ -60,10 +61,12 @@ class MultiplayerFiles {
savedGames[fileHandle] = game
}
@Readonly
fun getGameByName(name: String): MultiplayerGamePreview? {
return savedGames.values.firstOrNull { it.name == name }
}
@Readonly
fun getGameByGameId(gameId: String): MultiplayerGamePreview? {
return savedGames.values.firstOrNull { it.preview?.gameId == gameId }
}

View File

@ -35,6 +35,7 @@ import com.unciv.ui.screens.cityscreen.CityReligionInfoTable
import com.unciv.ui.screens.cityscreen.CityScreen
import com.unciv.ui.screens.diplomacyscreen.DiplomacyScreen
import com.unciv.utils.DebugUtils
import yairm210.purity.annotations.Readonly
import kotlin.math.max
import kotlin.math.min
@ -516,7 +517,7 @@ class CityButton(val city: City, private val tileGroup: TileGroup) : Table(BaseS
listOfHiddenUnitMarkers.add(indicator)
}
private fun belongsToViewingCiv() = city.civ == viewingPlayer
@Readonly private fun belongsToViewingCiv() = city.civ == viewingPlayer
private fun setButtonActions() {

View File

@ -72,6 +72,7 @@ import com.unciv.utils.launchOnThreadPool
import com.unciv.utils.withGLContext
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import yairm210.purity.annotations.Readonly
import java.util.Timer
import kotlin.concurrent.timer
@ -555,6 +556,8 @@ class WorldScreen(
val scrollX = mapHolder.scrollX
val scrollY = mapHolder.scrollY
}
@Readonly
fun getRestoreState(): RestoreState {
return RestoreState(mapHolder, selectedCiv.civName, viewingCiv.civName, fogOfWar)
}
@ -668,7 +671,8 @@ class WorldScreen(
}
shouldUpdate = true
}
@Readonly
internal fun isNextTurnUpdateRunning(): Boolean {
val job = nextTurnUpdateJob
return job != null && job.isActive

View File

@ -20,6 +20,7 @@ import com.unciv.ui.screens.pickerscreens.TechPickerScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.utils.Concurrency
import com.unciv.utils.launchOnGLThread
import yairm210.purity.annotations.Readonly
enum class NextTurnAction(protected val text: String, val color: Color) {
Default("", ImageGetter.CHARCOAL) {
@ -126,7 +127,7 @@ enum class NextTurnAction(protected val text: String, val color: Color) {
},
MoveAutomatedUnits("Move automated units", Color.LIGHT_GRAY) {
override fun isChoice(worldScreen: WorldScreen) =
worldScreen.isMoveAutomatedUnits()
worldScreen.canMoveAutomatedUnits()
override fun action(worldScreen: WorldScreen) =
moveAutomatedUnits(worldScreen)
},
@ -148,6 +149,7 @@ enum class NextTurnAction(protected val text: String, val color: Color) {
companion object {
// Readability helpers to allow concise enum instances
@Readonly
private fun getCityWithNoProductionSet(worldScreen: WorldScreen) =
worldScreen.viewingCiv.cities
.firstOrNull {
@ -167,7 +169,8 @@ enum class NextTurnAction(protected val text: String, val color: Color) {
)
)
private fun WorldScreen.isMoveAutomatedUnits(): Boolean {
@Readonly
private fun WorldScreen.canMoveAutomatedUnits(): Boolean {
if (game.settings.automatedUnitsMoveOnTurnStart || viewingCiv.hasMovedAutomatedUnits)
return false
return viewingCiv.units.getCivUnits()

View File

@ -18,6 +18,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.worldscreen.WorldScreen
import com.unciv.ui.screens.worldscreen.status.NextTurnAction.Default
import com.unciv.utils.Concurrency
import yairm210.purity.annotations.Readonly
class NextTurnButton(
private val worldScreen: WorldScreen
@ -78,7 +79,7 @@ class NextTurnButton(
private fun getNextTurnAction(worldScreen: WorldScreen) =
// Guaranteed to return a non-null NextTurnAction because the last isChoice always returns true
NextTurnAction.entries.first { it.isChoice(worldScreen) }
fun isNextUnitAction(): Boolean = nextTurnAction == NextTurnAction.NextUnit
@Readonly fun isNextUnitAction(): Boolean = nextTurnAction == NextTurnAction.NextUnit
}