chore: Tile purity

This commit is contained in:
yairm210 2025-07-18 11:15:02 +03:00
parent 673c33bb01
commit c1f0b97e2d
7 changed files with 89 additions and 71 deletions

View File

@ -240,6 +240,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
/** Get barbarian civ /** Get barbarian civ
* @throws NoSuchElementException in no-barbarians games! */ * @throws NoSuchElementException in no-barbarians games! */
fun getBarbarianCivilization() = getCivilization(Constants.barbarians) fun getBarbarianCivilization() = getCivilization(Constants.barbarians)
@Readonly @Suppress("purity") // This should be autorecognized!!
fun getDifficulty() = difficultyObject fun getDifficulty() = difficultyObject
/** Access a cached `GlobalUniques` that combines the [ruleset]'s [globalUniques][Ruleset.globalUniques] /** Access a cached `GlobalUniques` that combines the [ruleset]'s [globalUniques][Ruleset.globalUniques]
* with the Uniques of the chosen [speed] and [difficulty][getDifficulty] */ * with the Uniques of the chosen [speed] and [difficulty][getDifficulty] */

View File

@ -324,10 +324,8 @@ class Civilization : IsPartOfGameInfoSerialization {
if (!knows(civInfo)) diplomacyFunctions.makeCivilizationsMeet(civInfo) if (!knows(civInfo)) diplomacyFunctions.makeCivilizationsMeet(civInfo)
return getDiplomacyManager(civInfo.civName)!! return getDiplomacyManager(civInfo.civName)!!
} }
@Readonly @Readonly fun getDiplomacyManager(civInfo: Civilization): DiplomacyManager? = getDiplomacyManager(civInfo.civName)
fun getDiplomacyManager(civInfo: Civilization): DiplomacyManager? = getDiplomacyManager(civInfo.civName) @Readonly fun getDiplomacyManager(civName: String): DiplomacyManager? = diplomacy[civName]
@Readonly
fun getDiplomacyManager(civName: String): DiplomacyManager? = diplomacy[civName]
fun getProximity(civInfo: Civilization) = getProximity(civInfo.civName) fun getProximity(civInfo: Civilization) = getProximity(civInfo.civName)
@Suppress("MemberVisibilityCanBePrivate") // same visibility for overloads @Suppress("MemberVisibilityCanBePrivate") // same visibility for overloads
@ -347,17 +345,15 @@ class Civilization : IsPartOfGameInfoSerialization {
fun getKnownCivsWithSpectators() = diplomacy.values.asSequence().map { it.otherCiv() } fun getKnownCivsWithSpectators() = diplomacy.values.asSequence().map { it.otherCiv() }
.filter { !it.isDefeated() } .filter { !it.isDefeated() }
@Readonly
fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName) @Readonly fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName)
@Readonly @Readonly fun knows(otherCiv: Civilization) = knows(otherCiv.civName)
fun knows(otherCiv: Civilization) = knows(otherCiv.civName)
@Readonly @Readonly
fun getCapital(firstCityIfNoCapital: Boolean = true) = cities.firstOrNull { it.isCapital() } ?: fun getCapital(firstCityIfNoCapital: Boolean = true) = cities.firstOrNull { it.isCapital() } ?:
if (firstCityIfNoCapital) cities.firstOrNull() else null if (firstCityIfNoCapital) cities.firstOrNull() else null
@Readonly
fun isHuman() = playerType == PlayerType.Human @Readonly fun isHuman() = playerType == PlayerType.Human
@Readonly @Readonly fun isAI() = playerType == PlayerType.AI
fun isAI() = playerType == PlayerType.AI
@Readonly @Readonly
fun isAIOrAutoPlaying(): Boolean { fun isAIOrAutoPlaying(): Boolean {
if (playerType == PlayerType.AI) return true if (playerType == PlayerType.AI) return true
@ -365,14 +361,11 @@ class Civilization : IsPartOfGameInfoSerialization {
val worldScreen = UncivGame.Current.worldScreen ?: return false val worldScreen = UncivGame.Current.worldScreen ?: return false
return worldScreen.viewingCiv == this && worldScreen.autoPlay.isAutoPlaying() return worldScreen.viewingCiv == this && worldScreen.autoPlay.isAutoPlaying()
} }
@Readonly
fun isOneCityChallenger() = playerType == PlayerType.Human && gameInfo.gameParameters.oneCityChallenge @Readonly fun isOneCityChallenger() = playerType == PlayerType.Human && gameInfo.gameParameters.oneCityChallenge
@Readonly @Readonly fun isCurrentPlayer() = gameInfo.currentPlayerCiv == this
fun isCurrentPlayer() = gameInfo.currentPlayerCiv == this @Readonly fun isMajorCiv() = nation.isMajorCiv
@Readonly @Readonly fun isMinorCiv() = nation.isCityState || nation.isBarbarian
fun isMajorCiv() = nation.isMajorCiv
@Readonly
fun isMinorCiv() = nation.isCityState || nation.isBarbarian
@delegate:Transient @delegate:Transient
val isCityState by lazy { nation.isCityState } val isCityState by lazy { nation.isCityState }
@ -380,10 +373,9 @@ class Civilization : IsPartOfGameInfoSerialization {
@delegate:Transient @delegate:Transient
val isBarbarian by lazy { nation.isBarbarian } val isBarbarian by lazy { nation.isBarbarian }
@Readonly
fun isSpectator() = nation.isSpectator @Readonly fun isSpectator() = nation.isSpectator
@Readonly @Readonly fun isAlive(): Boolean = !isDefeated()
fun isAlive(): Boolean = !isDefeated()
@delegate:Transient @delegate:Transient
val cityStateType: CityStateType by lazy { gameInfo.ruleset.cityStateTypes[nation.cityStateType!!]!! } val cityStateType: CityStateType by lazy { gameInfo.ruleset.cityStateTypes[nation.cityStateType!!]!! }
@ -407,13 +399,6 @@ class Civilization : IsPartOfGameInfoSerialization {
} }
@Suppress("MemberVisibilityCanBePrivate")
fun getPreferredVictoryTypeObjects(): List<Victory> {
val preferredVictoryTypes = getPreferredVictoryTypes()
return if (preferredVictoryTypes.contains(Constants.neutralVictoryType)) emptyList()
else preferredVictoryTypes.map { gameInfo.ruleset.victories[it]!! }
}
@Readonly @Readonly
fun getPersonality(): Personality { fun getPersonality(): Personality {
return if (isAIOrAutoPlaying()) gameInfo.ruleset.personalities[nation.personality] ?: Personality.neutralPersonality return if (isAIOrAutoPlaying()) gameInfo.ruleset.personalities[nation.personality] ?: Personality.neutralPersonality
@ -1081,6 +1066,7 @@ class Civilization : IsPartOfGameInfoSerialization {
fun asPreview() = CivilizationInfoPreview(this) fun asPreview() = CivilizationInfoPreview(this)
@Readonly
fun getLastSeenImprovement(position: Vector2): String? { fun getLastSeenImprovement(position: Vector2): String? {
if (isAI() || isSpectator()) return null if (isAI() || isSpectator()) return null
return lastSeenImprovement[position] return lastSeenImprovement[position]

View File

@ -147,6 +147,7 @@ class DiplomacyFunctions(val civInfo: Civilization) {
* Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through * Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through
* a specific tile. * a specific tile.
*/ */
@Readonly
fun canPassThroughTiles(otherCiv: Civilization): Boolean { fun canPassThroughTiles(otherCiv: Civilization): Boolean {
if (otherCiv == civInfo) return true if (otherCiv == civInfo) return true
if (otherCiv.isBarbarian) return true if (otherCiv.isBarbarian) return true

View File

@ -18,10 +18,11 @@ import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.utils.addToMapOfSets import com.unciv.utils.addToMapOfSets
import com.unciv.utils.contains import com.unciv.utils.contains
import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly import yairm210.purity.annotations.Readonly
import java.lang.Integer.max
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max
/** An Unciv map with all properties as produced by the [map editor][com.unciv.ui.screens.mapeditorscreen.MapEditorScreen] /** An Unciv map with all properties as produced by the [map editor][com.unciv.ui.screens.mapeditorscreen.MapEditorScreen]
* or [MapGenerator][com.unciv.logic.map.mapgenerator.MapGenerator]; or as part of a running [game][GameInfo]. * or [MapGenerator][com.unciv.logic.map.mapgenerator.MapGenerator]; or as part of a running [game][GameInfo].
@ -400,8 +401,10 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization {
data class ViewableTile(val tile: Tile, val maxHeightSeenToTile: Int, val isVisible: Boolean, val isAttackable: Boolean) data class ViewableTile(val tile: Tile, val maxHeightSeenToTile: Int, val isVisible: Boolean, val isAttackable: Boolean)
/** @return List of tiles visible from location [position] for a unit with sight range [sightDistance] */ /** @return List of tiles visible from location [position] for a unit with sight range [sightDistance] */
@Readonly
fun getViewableTiles(position: Vector2, sightDistance: Int, forAttack: Boolean = false): List<Tile> { fun getViewableTiles(position: Vector2, sightDistance: Int, forAttack: Boolean = false): List<Tile> {
val aUnitHeight = get(position).unitHeight val aUnitHeight = get(position).unitHeight
@LocalState
val viewableTiles = mutableListOf(ViewableTile( val viewableTiles = mutableListOf(ViewableTile(
get(position), get(position),
aUnitHeight, aUnitHeight,
@ -412,6 +415,7 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization {
for (i in 1..sightDistance+1) { // in each layer, for (i in 1..sightDistance+1) { // in each layer,
// This is so we don't use tiles in the same distance to "see over", // This is so we don't use tiles in the same distance to "see over",
// that is to say, the "viewableTiles.contains(it) check will return false for neighbors from the same distance // that is to say, the "viewableTiles.contains(it) check will return false for neighbors from the same distance
@LocalState
val tilesToAddInDistanceI = ArrayList<ViewableTile>() val tilesToAddInDistanceI = ArrayList<ViewableTile>()
for (cTile in getTilesAtDistance(position, i)) { // for each tile in that layer, for (cTile in getTilesAtDistance(position, i)) { // for each tile in that layer,

View File

@ -26,6 +26,7 @@ import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.UnitMovementMemoryType import com.unciv.ui.components.UnitMovementMemoryType
import yairm210.purity.annotations.Readonly
import java.text.DecimalFormat import java.text.DecimalFormat
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.ulp import kotlin.math.ulp
@ -230,6 +231,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
fun getMovementString(): String = fun getMovementString(): String =
(DecimalFormat("0.#").format(currentMovement.toDouble()) + "/" + getMaxMovement()).tr() (DecimalFormat("0.#").format(currentMovement.toDouble()) + "/" + getMaxMovement()).tr()
@Readonly @Suppress("purity") // should be autorecognized
fun getTile(): Tile = currentTile fun getTile(): Tile = currentTile
fun getClosestCity(): City? = civ.cities.minByOrNull { fun getClosestCity(): City? = civ.cities.minByOrNull {
@ -284,6 +286,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
fun getUniques(): Sequence<Unique> = tempUniquesMap.getAllUniques() fun getUniques(): Sequence<Unique> = tempUniquesMap.getAllUniques()
@Readonly
fun getMatchingUniques( fun getMatchingUniques(
uniqueType: UniqueType, uniqueType: UniqueType,
gameContext: GameContext = cache.state, gameContext: GameContext = cache.state,
@ -296,6 +299,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
yieldAll(civ.getMatchingUniques(uniqueType, gameContext)) yieldAll(civ.getMatchingUniques(uniqueType, gameContext))
} }
@Readonly
fun hasUnique( fun hasUnique(
uniqueType: UniqueType, uniqueType: UniqueType,
gameContext: GameContext = cache.state, gameContext: GameContext = cache.state,
@ -405,12 +409,14 @@ class MapUnit : IsPartOfGameInfoSerialization {
return getRange() * 2 return getRange() * 2
} }
@Readonly
fun isEmbarked(): Boolean { fun isEmbarked(): Boolean {
if (!baseUnit.isLandUnit) return false if (!baseUnit.isLandUnit) return false
if (cache.canMoveOnWater) return false if (cache.canMoveOnWater) return false
return currentTile.isWater return currentTile.isWater
} }
@Readonly
fun isInvisible(to: Civilization): Boolean { fun isInvisible(to: Civilization): Boolean {
if (hasUnique(UniqueType.Invisible) && !to.isSpectator()) if (hasUnique(UniqueType.Invisible) && !to.isSpectator())
return true return true

View File

@ -34,6 +34,7 @@ import com.unciv.utils.DebugUtils
import com.unciv.utils.Log import com.unciv.utils.Log
import com.unciv.utils.withItem import com.unciv.utils.withItem
import com.unciv.utils.withoutItem import com.unciv.utils.withoutItem
import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly import yairm210.purity.annotations.Readonly
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet import kotlin.collections.HashSet
@ -241,9 +242,10 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
//region pure functions //region pure functions
fun isHill() = baseTerrain == Constants.hill || terrainFeatures.contains(Constants.hill) @Readonly fun isHill() = baseTerrain == Constants.hill || terrainFeatures.contains(Constants.hill)
/** Returns military, civilian and air units in tile */ /** Returns military, civilian and air units in tile */
@Readonly
fun getUnits() = sequence { fun getUnits() = sequence {
if (militaryUnit != null) yield(militaryUnit!!) if (militaryUnit != null) yield(militaryUnit!!)
if (civilianUnit != null) yield(civilianUnit!!) if (civilianUnit != null) yield(civilianUnit!!)
@ -251,6 +253,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
} }
/** This is for performance reasons of canPassThrough() - faster than getUnits().firstOrNull() */ /** This is for performance reasons of canPassThrough() - faster than getUnits().firstOrNull() */
@Readonly
fun getFirstUnit(): MapUnit? { fun getFirstUnit(): MapUnit? {
if (militaryUnit != null) return militaryUnit!! if (militaryUnit != null) return militaryUnit!!
if (civilianUnit != null) return civilianUnit!! if (civilianUnit != null) return civilianUnit!!
@ -258,41 +261,39 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return null return null
} }
@Readonly @Readonly @Suppress("purity") // should be autorecognized as readonly
fun getCity(): City? = owningCity fun getCity(): City? = owningCity
internal fun getNaturalWonder(): Terrain = @Readonly internal fun getNaturalWonder(): Terrain =
if (naturalWonder == null) throw Exception("No natural wonder exists for this tile!") if (naturalWonder == null) throw Exception("No natural wonder exists for this tile!")
else ruleset.terrains[naturalWonder!!]!! else ruleset.terrains[naturalWonder!!]!!
@Readonly
fun isVisible(player: Civilization): Boolean { fun isVisible(player: Civilization): Boolean {
if (DebugUtils.VISIBLE_MAP) if (DebugUtils.VISIBLE_MAP)
return true return true
return player.viewableTiles.contains(this) return player.viewableTiles.contains(this)
} }
@Readonly
fun isExplored(player: Civilization): Boolean { fun isExplored(player: Civilization): Boolean {
if (DebugUtils.VISIBLE_MAP || player.civName == Constants.spectator) if (DebugUtils.VISIBLE_MAP || player.civName == Constants.spectator)
return true return true
return exploredBy.contains(player.civName) return exploredBy.contains(player.civName)
} }
@Readonly @Suppress("purity") // should be autorecognized as readonly
fun isCityCenter(): Boolean = isCityCenterInternal fun isCityCenter(): Boolean = isCityCenterInternal
fun isNaturalWonder(): Boolean = naturalWonder != null @Readonly fun isNaturalWonder(): Boolean = naturalWonder != null
fun isImpassible() = lastTerrain.impassable @Readonly fun isImpassible() = lastTerrain.impassable
fun hasImprovementInProgress() = improvementQueue.isNotEmpty() @Readonly fun hasImprovementInProgress() = improvementQueue.isNotEmpty()
@Readonly @Readonly fun getTileImprovement(): TileImprovement? = if (improvement == null) null else ruleset.tileImprovements[improvement!!]
fun getTileImprovement(): TileImprovement? = if (improvement == null) null else ruleset.tileImprovements[improvement!!] @Readonly fun isPillaged(): Boolean = improvementIsPillaged || roadIsPillaged
@Readonly @Readonly fun getUnpillagedTileImprovement(): TileImprovement? = if (getUnpillagedImprovement() == null) null else ruleset.tileImprovements[improvement!!]
fun isPillaged(): Boolean = improvementIsPillaged || roadIsPillaged @Readonly fun getTileImprovementInProgress(): TileImprovement? = improvementQueue.firstOrNull()?.let { ruleset.tileImprovements[it.improvement] }
@Readonly @Readonly fun containsGreatImprovement() = getTileImprovement()?.isGreatImprovement() == true
fun getUnpillagedTileImprovement(): TileImprovement? = if (getUnpillagedImprovement() == null) null else ruleset.tileImprovements[improvement!!]
@Readonly
fun getTileImprovementInProgress(): TileImprovement? = improvementQueue.firstOrNull()?.let { ruleset.tileImprovements[it.improvement] }
@Readonly
fun containsGreatImprovement() = getTileImprovement()?.isGreatImprovement() == true
@Readonly @Readonly
fun getImprovementToPillage(): TileImprovement? { fun getImprovementToPillage(): TileImprovement? {
@ -319,10 +320,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return ruleset.tileImprovements[roadStatus.name]!! return ruleset.tileImprovements[roadStatus.name]!!
return null return null
} }
@Readonly @Readonly fun canPillageTile(): Boolean = canPillageTileImprovement() || canPillageRoad()
fun canPillageTile(): Boolean {
return canPillageTileImprovement() || canPillageRoad()
}
@Readonly @Readonly
fun canPillageTileImprovement(): Boolean { fun canPillageTileImprovement(): Boolean {
return improvement != null && !improvementIsPillaged return improvement != null && !improvementIsPillaged
@ -335,12 +333,10 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
&& !ruleset.tileImprovements[roadStatus.name]!!.hasUnique(UniqueType.Unpillagable) && !ruleset.tileImprovements[roadStatus.name]!!.hasUnique(UniqueType.Unpillagable)
&& !ruleset.tileImprovements[roadStatus.name]!!.hasUnique(UniqueType.Irremovable) && !ruleset.tileImprovements[roadStatus.name]!!.hasUnique(UniqueType.Irremovable)
} }
@Readonly @Readonly fun getUnpillagedImprovement(): String? = if (improvementIsPillaged) null else improvement
fun getUnpillagedImprovement(): String? = if (improvementIsPillaged) null else improvement
/** @return [RoadStatus] on this [Tile], pillaged road counts as [RoadStatus.None] */ /** @return [RoadStatus] on this [Tile], pillaged road counts as [RoadStatus.None] */
@Readonly @Readonly fun getUnpillagedRoad(): RoadStatus = if (roadIsPillaged) RoadStatus.None else roadStatus
fun getUnpillagedRoad(): RoadStatus = if (roadIsPillaged) RoadStatus.None else roadStatus
@Readonly @Readonly
fun getUnpillagedRoadImprovement(): TileImprovement? { fun getUnpillagedRoadImprovement(): TileImprovement? {
@ -354,12 +350,13 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
* @param viewingCiv `null` means civ-agnostic and thus always showing the actual improvement * @param viewingCiv `null` means civ-agnostic and thus always showing the actual improvement
* @return The improvement name, or `null` if no improvement should be shown * @return The improvement name, or `null` if no improvement should be shown
*/ */
@Readonly
fun getShownImprovement(viewingCiv: Civilization?): String? = fun getShownImprovement(viewingCiv: Civilization?): String? =
if (viewingCiv == null || viewingCiv.playerType == PlayerType.AI || viewingCiv.isSpectator()) improvement if (viewingCiv == null || viewingCiv.playerType == PlayerType.AI || viewingCiv.isSpectator()) improvement
else viewingCiv.getLastSeenImprovement(position) else viewingCiv.getLastSeenImprovement(position)
/** Returns true if this tile has fallout or an equivalent terrain feature */ /** Returns true if this tile has fallout or an equivalent terrain feature */
fun hasFalloutEquivalent(): Boolean = terrainFeatures.any { ruleset.terrains[it]!!.hasUnique(UniqueType.NullifyYields)} @Readonly fun hasFalloutEquivalent(): Boolean = terrainFeatures.any { ruleset.terrains[it]!!.hasUnique(UniqueType.NullifyYields)}
fun getRow() = HexMath.getRow(position) fun getRow() = HexMath.getRow(position)
@ -367,9 +364,9 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
fun getBaseTerrain(): Terrain = baseTerrainObject fun getBaseTerrain(): Terrain = baseTerrainObject
@Readonly @Readonly fun getOwner(): Civilization? = getCity()?.civ
fun getOwner(): Civilization? = getCity()?.civ
@Readonly
fun getRoadOwner(): Civilization? { fun getRoadOwner(): Civilization? {
return if (roadOwner != "") return if (roadOwner != "")
tileMap.gameInfo.getCivilization(roadOwner) tileMap.gameInfo.getCivilization(roadOwner)
@ -394,19 +391,22 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return civInfo.isAtWarWith(tileOwner) return civInfo.isAtWarWith(tileOwner)
} }
fun isRoughTerrain() = allTerrains.any { it.isRough() } @Readonly fun isRoughTerrain() = allTerrains.any { it.isRough() }
@Transient @Transient
internal var stateThisTile: GameContext = GameContext.EmptyState internal var stateThisTile: GameContext = GameContext.EmptyState
/** Checks whether any of the TERRAINS of this tile has a certain unique */ /** Checks whether any of the TERRAINS of this tile has a certain unique */
@Readonly
fun terrainHasUnique(uniqueType: UniqueType, state: GameContext = stateThisTile) = fun terrainHasUnique(uniqueType: UniqueType, state: GameContext = stateThisTile) =
cachedTerrainData.uniques.hasMatchingUnique(uniqueType, state) cachedTerrainData.uniques.hasMatchingUnique(uniqueType, state)
/** Get all uniques of this type that any TERRAIN on this tile has */ /** Get all uniques of this type that any TERRAIN on this tile has */
@Readonly
fun getTerrainMatchingUniques(uniqueType: UniqueType, gameContext: GameContext = stateThisTile ): Sequence<Unique> { fun getTerrainMatchingUniques(uniqueType: UniqueType, gameContext: GameContext = stateThisTile ): Sequence<Unique> {
return cachedTerrainData.uniques.getMatchingUniques(uniqueType, gameContext) return cachedTerrainData.uniques.getMatchingUniques(uniqueType, gameContext)
} }
/** Get all uniques of this type that any part of this tile has: terrains, improvement, resource */ /** Get all uniques of this type that any part of this tile has: terrains, improvement, resource */
@Readonly
fun getMatchingUniques(uniqueType: UniqueType, gameContext: GameContext = stateThisTile): Sequence<Unique> { fun getMatchingUniques(uniqueType: UniqueType, gameContext: GameContext = stateThisTile): Sequence<Unique> {
var uniques = getTerrainMatchingUniques(uniqueType, gameContext) var uniques = getTerrainMatchingUniques(uniqueType, gameContext)
if (getUnpillagedImprovement() != null) { if (getUnpillagedImprovement() != null) {
@ -427,6 +427,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return civInfo.cities.firstOrNull { it != owningCity && it.isWorked(this) } return civInfo.cities.firstOrNull { it != owningCity && it.isWorked(this) }
} }
@Readonly
fun isBlockaded(): Boolean { fun isBlockaded(): Boolean {
val owner = getOwner() ?: return false val owner = getOwner() ?: return false
val unit = militaryUnit val unit = militaryUnit
@ -463,11 +464,13 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|| terrainHasUnique(UniqueType.TileProvidesYieldWithoutPopulation) || terrainHasUnique(UniqueType.TileProvidesYieldWithoutPopulation)
} }
@Readonly
fun isLocked(): Boolean { fun isLocked(): Boolean {
val workingCity = getWorkingCity() val workingCity = getWorkingCity()
return workingCity != null && workingCity.lockedTiles.contains(position) return workingCity != null && workingCity.lockedTiles.contains(position)
} }
@Readonly
fun providesResources(civInfo: Civilization): Boolean { fun providesResources(civInfo: Civilization): Boolean {
if (!hasViewableResource(civInfo)) return false if (!hasViewableResource(civInfo)) return false
if (isCityCenter()) { if (isCityCenter()) {
@ -520,6 +523,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
} }
/** Implements [UniqueParameterType.TerrainFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TerrainFilter] */ /** Implements [UniqueParameterType.TerrainFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TerrainFilter] */
@Readonly
fun matchesTerrainFilter(filter: String, observingCiv: Civilization?, multiFilter: Boolean = true): Boolean { fun matchesTerrainFilter(filter: String, observingCiv: Civilization?, multiFilter: Boolean = true): Boolean {
return if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleTerrainFilter(it, observingCiv) }) return if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleTerrainFilter(it, observingCiv) })
else matchesSingleTerrainFilter(filter, observingCiv) else matchesSingleTerrainFilter(filter, observingCiv)
@ -586,11 +590,13 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
fun hasViewableResource(civInfo: Civilization): Boolean = fun hasViewableResource(civInfo: Civilization): Boolean =
resource != null && civInfo.tech.isRevealed(tileResource) resource != null && civInfo.tech.isRevealed(tileResource)
fun getViewableTilesList(distance: Int): List<Tile> = tileMap.getViewableTiles(position, distance)
fun getTilesInDistance(distance: Int): Sequence<Tile> = tileMap.getTilesInDistance(position, distance)
fun getTilesInDistanceRange(range: IntRange): Sequence<Tile> = tileMap.getTilesInDistanceRange(position, range)
fun getTilesAtDistance(distance: Int): Sequence<Tile> = tileMap.getTilesAtDistance(position, distance)
@Readonly fun getViewableTilesList(distance: Int): List<Tile> = tileMap.getViewableTiles(position, distance)
@Readonly fun getTilesInDistance(distance: Int): Sequence<Tile> = tileMap.getTilesInDistance(position, distance)
@Readonly fun getTilesInDistanceRange(range: IntRange): Sequence<Tile> = tileMap.getTilesInDistanceRange(position, range)
@Readonly fun getTilesAtDistance(distance: Int): Sequence<Tile> = tileMap.getTilesAtDistance(position, distance)
@Readonly
fun getDefensiveBonus(includeImprovementBonus: Boolean = true, unit: MapUnit? = null): Float { fun getDefensiveBonus(includeImprovementBonus: Boolean = true, unit: MapUnit? = null): Float {
var bonus = baseTerrainObject.defenceBonus var bonus = baseTerrainObject.defenceBonus
if (terrainFeatureObjects.isNotEmpty()) { if (terrainFeatureObjects.isNotEmpty()) {
@ -612,6 +618,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
* @param otherTile Destination tile * @param otherTile Destination tile
* @return Shortest distance from this [Tile] to [otherTile] in count of tiles including impassable tiles but not including origin tile * @return Shortest distance from this [Tile] to [otherTile] in count of tiles including impassable tiles but not including origin tile
*/ */
@Readonly
fun aerialDistanceTo(otherTile: Tile): Int { fun aerialDistanceTo(otherTile: Tile): Int {
val xDelta = position.x - otherTile.position.x val xDelta = position.x - otherTile.position.x
val yDelta = position.y - otherTile.position.y val yDelta = position.y - otherTile.position.y
@ -628,6 +635,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return min(distance, wrappedDistance).toInt() return min(distance, wrappedDistance).toInt()
} }
@Readonly
fun canBeSettled(): Boolean { fun canBeSettled(): Boolean {
val modConstants = tileMap.gameInfo.ruleset.modOptions.constants val modConstants = tileMap.gameInfo.ruleset.modOptions.constants
return when { return when {
@ -641,6 +649,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
} }
/** The two tiles have a river between them */ /** The two tiles have a river between them */
@Readonly
fun isConnectedByRiver(otherTile: Tile): Boolean { fun isConnectedByRiver(otherTile: Tile): Boolean {
if (otherTile == this) throw Exception("Should not be called to compare to self!") if (otherTile == this) throw Exception("Should not be called to compare to self!")
@ -672,6 +681,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
* @returns whether units of [civInfo] can pass through this tile, considering only civ-wide filters. * @returns whether units of [civInfo] can pass through this tile, considering only civ-wide filters.
* Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through a tile. * Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through a tile.
*/ */
@Readonly
fun canCivPassThrough(civInfo: Civilization): Boolean { fun canCivPassThrough(civInfo: Civilization): Boolean {
val tileOwner = getOwner() val tileOwner = getOwner()
// comparing the CivInfo objects is cheaper than comparing strings // comparing the CivInfo objects is cheaper than comparing strings
@ -681,6 +691,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return civInfo.diplomacyFunctions.canPassThroughTiles(tileOwner) return civInfo.diplomacyFunctions.canPassThroughTiles(tileOwner)
} }
@Readonly
fun hasEnemyInvisibleUnit(viewingCiv: Civilization): Boolean { fun hasEnemyInvisibleUnit(viewingCiv: Civilization): Boolean {
val unitsInTile = getUnits() val unitsInTile = getUnits()
return when { return when {
@ -691,28 +702,33 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
} }
} }
@Readonly
fun hasConnection(civInfo: Civilization) = fun hasConnection(civInfo: Civilization) =
getUnpillagedRoad() != RoadStatus.None || forestOrJungleAreRoads(civInfo) getUnpillagedRoad() != RoadStatus.None || forestOrJungleAreRoads(civInfo)
@Readonly
fun hasRoadConnection(civInfo: Civilization, mustBeUnpillaged: Boolean) = fun hasRoadConnection(civInfo: Civilization, mustBeUnpillaged: Boolean) =
if (mustBeUnpillaged) if (mustBeUnpillaged)
(getUnpillagedRoad() == RoadStatus.Road) || forestOrJungleAreRoads(civInfo) (getUnpillagedRoad() == RoadStatus.Road) || forestOrJungleAreRoads(civInfo)
else else
roadStatus == RoadStatus.Road || forestOrJungleAreRoads(civInfo) roadStatus == RoadStatus.Road || forestOrJungleAreRoads(civInfo)
@Readonly
fun hasRailroadConnection(mustBeUnpillaged: Boolean) = fun hasRailroadConnection(mustBeUnpillaged: Boolean) =
if (mustBeUnpillaged) if (mustBeUnpillaged)
getUnpillagedRoad() == RoadStatus.Railroad getUnpillagedRoad() == RoadStatus.Railroad
else else
roadStatus == RoadStatus.Railroad roadStatus == RoadStatus.Railroad
@Readonly
private fun forestOrJungleAreRoads(civInfo: Civilization) = private fun forestOrJungleAreRoads(civInfo: Civilization) =
civInfo.nation.forestsAndJunglesAreRoads civInfo.nation.forestsAndJunglesAreRoads
&& (terrainFeatures.contains(Constants.jungle) || terrainFeatures.contains(Constants.forest)) && (terrainFeatures.contains(Constants.jungle) || terrainFeatures.contains(Constants.forest))
&& isFriendlyTerritory(civInfo) && isFriendlyTerritory(civInfo)
@Readonly
fun getRulesetIncompatibility(ruleset: Ruleset): HashSet<String> { fun getRulesetIncompatibility(ruleset: Ruleset): HashSet<String> {
@LocalState
val out = HashSet<String>() val out = HashSet<String>()
if (!ruleset.terrains.containsKey(baseTerrain)) if (!ruleset.terrains.containsKey(baseTerrain))
out.add("Base terrain [$baseTerrain] does not exist in ruleset!") out.add("Base terrain [$baseTerrain] does not exist in ruleset!")
@ -727,15 +743,17 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
return out return out
} }
@Readonly @Suppress("purity") // should be auto-recognized as readonly
fun getContinent() = continent fun getContinent() = continent
/** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */ /** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */
fun isMarkedForCreatesOneImprovement() = @Readonly fun isMarkedForCreatesOneImprovement() =
turnsToImprovement < 0 && improvementInProgress != null turnsToImprovement < 0 && improvementInProgress != null
/** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique creating a specific [improvement] */ /** Checks if this tile is marked as target tile for a building with a [UniqueType.CreatesOneImprovement] unique creating a specific [improvement] */
fun isMarkedForCreatesOneImprovement(improvement: String) = @Readonly fun isMarkedForCreatesOneImprovement(improvement: String) =
turnsToImprovement < 0 && improvementInProgress == improvement turnsToImprovement < 0 && improvementInProgress == improvement
@Readonly
private fun approximateMajorDepositDistribution(): Double { private fun approximateMajorDepositDistribution(): Double {
// We can't replicate the MapRegions resource distributor, so let's try to get // We can't replicate the MapRegions resource distributor, so let's try to get
// a close probability of major deposits per tile // a close probability of major deposits per tile

View File

@ -13,6 +13,7 @@ import com.unciv.models.stats.GameResource
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines
import com.unciv.ui.screens.civilopediascreen.FormattedLine import com.unciv.ui.screens.civilopediascreen.FormattedLine
import yairm210.purity.annotations.Readonly
class TileResource : RulesetStatsObject(), GameResource { class TileResource : RulesetStatsObject(), GameResource {
@ -52,6 +53,7 @@ class TileResource : RulesetStatsObject(), GameResource {
* @see improvedBy * @see improvedBy
* @see UniqueType.ImprovesResources * @see UniqueType.ImprovesResources
*/ */
@Readonly @Suppress("purity") // requires some plumbing
fun getImprovements(): Set<String> { fun getImprovements(): Set<String> {
if (improvementsInitialized) return allImprovements if (improvementsInitialized) return allImprovements
val ruleset = this.ruleset val ruleset = this.ruleset