Linting and re-sort Tile class so the regions are no longer lying (#11649)

* Rename TileInfoImprovementFunctions

* Linting and re-sort Tile class so the regions are no longer lying
This commit is contained in:
SomeTroglodyte
2024-05-29 22:59:16 +02:00
committed by GitHub
parent 9757cae098
commit c6e3ff5d1b
6 changed files with 161 additions and 159 deletions

View File

@ -11,7 +11,6 @@ import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.map.tile.TileStatFunctions
import com.unciv.logic.map.tile.toStats
import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.tile.ResourceType
@ -278,7 +277,7 @@ class WorkerAutomation(
var repairBonusPriority = tile.getImprovementToRepair()!!.getTurnsToBuild(unit.civ,unit) - UnitActionsFromUniques.getRepairTurns(unit)
if (tile.improvementInProgress == Constants.repair) repairBonusPriority += UnitActionsFromUniques.getRepairTurns(unit) - tile.turnsToImprovement
val repairPriority = repairBonusPriority + Automation.rankStatsValue(TileStatFunctions(tile).getStatDiffForImprovement(tile.getTileImprovement()!!, unit.civ, tile.owningCity), unit.civ)
val repairPriority = repairBonusPriority + Automation.rankStatsValue(tile.stats.getStatDiffForImprovement(tile.getTileImprovement()!!, unit.civ, tile.owningCity), unit.civ)
if (repairPriority > rank.improvementPriority!!) {
rank.improvementPriority = repairPriority
rank.bestImprovement = null

View File

@ -56,7 +56,7 @@ class CityFounder {
if (civInfo.gameInfo.ruleset.tileImprovements.containsKey(Constants.cityCenter))
tile.changeImprovement(Constants.cityCenter, civInfo)
tile.improvementInProgress = null
tile.stopWorkingOnImprovement()
val ruleset = civInfo.gameInfo.ruleset
city.workedTiles = hashSetOf() //reassign 1st working tile
@ -111,8 +111,8 @@ class CityFounder {
* This method attempts to return the first unused city name of the [foundingCiv], taking used
* city names into consideration (including foreign cities). If that fails, it then checks
* whether the civilization has [UniqueType.BorrowsCityNames] and, if true, returns a borrowed
* name. Else, it repeatedly attaches one of the given [prefixes] to the list of names up to ten
* times until an unused name is successfully generated. If all else fails, null is returned.
* name. Else, it repeatedly attaches one of the given [prefixes][NamingConstants.prefixes] to the list of names
* up to ten times until an unused name is successfully generated. If all else fails, null is returned.
*
* @param foundingCiv The civilization that founded this city.
* @param aliveCivs Every civilization currently alive.

View File

@ -46,7 +46,7 @@ class Tile : IsPartOfGameInfoSerialization {
private set
/** Should be immutable - never be altered in-place, instead replaced */
var exploredBy = HashSet<String>(0)
private var exploredBy = HashSet<String>(0)
var naturalWonder: String? = null
var resource: String? = null
@ -55,13 +55,14 @@ class Tile : IsPartOfGameInfoSerialization {
field = value
}
var resourceAmount: Int = 0
var improvement: String? = null
var improvementInProgress: String? = null
var improvementIsPillaged = false
var roadStatus = RoadStatus.None
var roadIsPillaged = false
var roadOwner: String = "" // either who last built the road or last owner of tile
private var roadOwner: String = "" // either who last built the road or last owner of tile
var turnsToImprovement: Int = 0
var hasBottomRightRiver = false
@ -81,11 +82,18 @@ class Tile : IsPartOfGameInfoSerialization {
lateinit var ruleset: Ruleset // a tile can be a tile with a ruleset, even without a map.
@Transient
val improvementFunctions = TileInfoImprovementFunctions(this)
val improvementFunctions = TileImprovementFunctions(this)
@Transient
val stats = TileStatFunctions(this)
// This is for performance - since we access the neighbors of a tile ALL THE TIME,
// and the neighbors of a tile never change, it's much more efficient to save the list once and for all!
@delegate:Transient
val neighbors: Sequence<Tile> by lazy { getTilesAtDistance(1).toList().asSequence() }
// We have to .toList() so that the values are stored together once for caching,
// and the toSequence so that aggregations (like neighbors.flatMap{it.units} don't take up their own space
@Transient
private var isCityCenterInternal = false
@ -106,6 +114,9 @@ class Tile : IsPartOfGameInfoSerialization {
@Transient
var isOcean = false
@delegate:Transient
private val _isCoastalTile: Boolean by lazy { neighbors.any { it.baseTerrain == Constants.coast } }
@Transient
var unitHeight = 0
@ -153,6 +164,11 @@ class Tile : IsPartOfGameInfoSerialization {
}
return tileResourceCache!!
}
@Transient
private var isAdjacentToRiver = false
@Transient
private var isAdjacentToRiverKnown = false
//endregion
fun clone(): Tile {
@ -197,10 +213,6 @@ class Tile : IsPartOfGameInfoSerialization {
fun isHill() = baseTerrain == Constants.hill || terrainFeatures.contains(Constants.hill)
fun containsGreatImprovement(): Boolean {
return getTileImprovement()?.isGreatImprovement() == true
}
/** Returns military, civilian and air units in tile */
fun getUnits() = sequence {
if (militaryUnit != null) yield(militaryUnit!!)
@ -234,28 +246,16 @@ class Tile : IsPartOfGameInfoSerialization {
return exploredBy.contains(player.civName)
}
fun setExplored(player: Civilization, isExplored: Boolean, explorerPosition: Vector2? = null) {
if (isExplored) {
// Disable the undo button if a new tile has been explored
if (!exploredBy.contains(player.civName)) {
GUI.clearUndoCheckpoints()
exploredBy = exploredBy.withItem(player.civName)
}
if (player.playerType == PlayerType.Human)
player.exploredRegion.checkTilePosition(position, explorerPosition)
} else {
exploredBy = exploredBy.withoutItem(player.civName)
}
}
fun isCityCenter(): Boolean = isCityCenterInternal
fun isNaturalWonder(): Boolean = naturalWonder != null
fun isImpassible() = lastTerrain.impassable
fun getTileImprovement(): TileImprovement? = if (improvement == null) null else ruleset.tileImprovements[improvement!!]
fun isPillaged(): Boolean = improvementIsPillaged || roadIsPillaged
fun getUnpillagedTileImprovement(): TileImprovement? = if (getUnpillagedImprovement() == null) null else ruleset.tileImprovements[improvement!!]
fun getTileImprovementInProgress(): TileImprovement? = if (improvementInProgress == null) null else ruleset.tileImprovements[improvementInProgress!!]
fun containsGreatImprovement() = getTileImprovement()?.isGreatImprovement() == true
fun getImprovementToPillage(): TileImprovement? {
if (canPillageTileImprovement())
@ -299,34 +299,6 @@ class Tile : IsPartOfGameInfoSerialization {
else ruleset.tileImprovements[getUnpillagedRoad().name]
}
/** Does not remove roads */
fun removeImprovement() =
improvementFunctions.changeImprovement(null)
fun changeImprovement(improvementStr: String, civToHandleCompletion: Civilization? = null, unit: MapUnit? = null) =
improvementFunctions.changeImprovement(improvementStr, civToHandleCompletion, unit)
// function handling when adding a road to the tile
fun addRoad(roadType: RoadStatus, creatingCivInfo: Civilization?) {
roadStatus = roadType
roadIsPillaged = false
if (getOwner() != null) {
roadOwner = getOwner()!!.civName
} else if (creatingCivInfo != null) {
roadOwner = creatingCivInfo.civName // neutral tile, use building unit
creatingCivInfo.neutralRoads.add(this.position)
}
}
// function handling when removing a road from the tile
fun removeRoad() {
roadIsPillaged = false
if (roadStatus == RoadStatus.None) return
roadStatus = RoadStatus.None
if (owningCity == null)
getRoadOwner()?.neutralRoads?.remove(this.position)
}
fun getShownImprovement(viewingCiv: Civilization?): String? {
return if (viewingCiv == null || viewingCiv.playerType == PlayerType.AI || viewingCiv.isSpectator())
improvement
@ -338,13 +310,6 @@ class Tile : IsPartOfGameInfoSerialization {
fun hasFalloutEquivalent(): Boolean = terrainFeatures.any { ruleset.terrains[it]!!.hasUnique(UniqueType.NullifyYields)}
// This is for performance - since we access the neighbors of a tile ALL THE TIME,
// and the neighbors of a tile never change, it's much more efficient to save the list once and for all!
@delegate:Transient
val neighbors: Sequence<Tile> by lazy { getTilesAtDistance(1).toList().asSequence() }
// We have to .toList() so that the values are stored together once for caching,
// and the toSequence so that aggregations (like neighbors.flatMap{it.units} don't take up their own space
fun getRow() = HexMath.getRow(position)
fun getColumn() = HexMath.getColumn(position)
@ -537,8 +502,6 @@ class Tile : IsPartOfGameInfoSerialization {
fun hasImprovementInProgress() = improvementInProgress != null && turnsToImprovement > 0
@delegate:Transient
private val _isCoastalTile: Boolean by lazy { neighbors.any { it.baseTerrain == Constants.coast } }
fun isCoastalTile() = _isCoastalTile
fun hasViewableResource(civInfo: Civilization): Boolean =
@ -593,28 +556,6 @@ class Tile : IsPartOfGameInfoSerialization {
}
}
/** Shows important properties of this tile for debugging _only_, it helps to see what you're doing */
override fun toString(): String {
val lineList = arrayListOf("Tile @$position")
if (!this::baseTerrain.isInitialized) return lineList[0] + ", uninitialized"
if (isCityCenter()) lineList += getCity()!!.name
lineList += baseTerrain
for (terrainFeature in terrainFeatures) lineList += terrainFeature
if (resource != null) {
lineList += if (tileResource.resourceType == ResourceType.Strategic)
"{$resourceAmount} {$resource}"
else
resource!!
}
if (naturalWonder != null) lineList += naturalWonder!!
if (roadStatus !== RoadStatus.None && !isCityCenter()) lineList += roadStatus.name
if (improvement != null) lineList += improvement!!
if (civilianUnit != null) lineList += civilianUnit!!.name + " - " + civilianUnit!!.civ.civName
if (militaryUnit != null) lineList += militaryUnit!!.name + " - " + militaryUnit!!.civ.civName
if (this::baseTerrainObject.isInitialized && isImpassible()) lineList += Constants.impassable
return lineList.joinToString()
}
/** The two tiles have a river between them */
fun isConnectedByRiver(otherTile: Tile): Boolean {
if (otherTile == this) throw Exception("Should not be called to compare to self!")
@ -630,10 +571,6 @@ class Tile : IsPartOfGameInfoSerialization {
}
}
@Transient
private var isAdjacentToRiver = false
@Transient
private var isAdjacentToRiverKnown = false
fun isAdjacentToRiver(): Boolean {
if (!isAdjacentToRiverKnown) {
isAdjacentToRiver =
@ -646,43 +583,6 @@ class Tile : IsPartOfGameInfoSerialization {
return isAdjacentToRiver
}
/** Allows resetting the cached value [isAdjacentToRiver] will return
* @param isKnownTrue Set this to indicate you need to update the cache due to **adding** a river edge
* (removing would need to look at other edges, and that is what isAdjacentToRiver will do)
*/
private fun resetAdjacentToRiverTransient(isKnownTrue: Boolean = false) {
isAdjacentToRiver = isKnownTrue
isAdjacentToRiverKnown = isKnownTrue
}
/**
* Sets the "has river" state of one edge of this Tile. Works for all six directions.
* @param otherTile The neighbor tile in the direction the river we wish to change is (If it's not a neighbor, this does nothing).
* @param newValue The new river edge state: `true` to create a river, `false` to remove one.
* @param convertTerrains If true, calls MapGenerator's convertTerrains to apply UniqueType.ChangesTerrain effects.
* @return The state did change (`false`: the edge already had the `newValue`)
*/
fun setConnectedByRiver(otherTile: Tile, newValue: Boolean, convertTerrains: Boolean = false): Boolean {
//todo synergy potential with [MapEditorEditRiversTab]?
val field = when (tileMap.getNeighborTileClockPosition(this, otherTile)) {
2 -> otherTile::hasBottomLeftRiver // we're to the bottom-left of it
4 -> ::hasBottomRightRiver // we're to the top-left of it
6 -> ::hasBottomRiver // we're directly above it
8 -> ::hasBottomLeftRiver // we're to the top-right of it
10 -> otherTile::hasBottomRightRiver // we're to the bottom-right of it
12 -> otherTile::hasBottomRiver // we're directly below it
else -> return false
}
if (field.get() == newValue) return false
field.set(newValue)
val affectedTiles = listOf(this, otherTile)
for (tile in affectedTiles)
tile.resetAdjacentToRiverTransient(newValue)
if (convertTerrains)
MapGenerator.Helpers.convertTerrains(ruleset, affectedTiles)
return true
}
/**
* @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.
@ -751,6 +651,20 @@ class Tile : IsPartOfGameInfoSerialization {
fun isMarkedForCreatesOneImprovement(improvement: String) =
turnsToImprovement < 0 && improvementInProgress == improvement
private fun approximateMajorDepositDistribution(): Double {
// We can't replicate the MapRegions resource distributor, so let's try to get
// a close probability of major deposits per tile
var probability = 0.0
for (unique in allTerrains.flatMap { it.getMatchingUniques(UniqueType.MajorStrategicFrequency) }) {
val frequency = unique.params[0].toIntOrNull() ?: continue
if (frequency <= 0) continue
// The unique param is literally "every N tiles", so to get a probability p=1/f
probability += 1.0 / frequency
}
return if (probability == 0.0) 0.04 // This is the default of 1 per 25 tiles
else probability
}
//endregion
//region state-changing functions
@ -839,20 +753,6 @@ class Tile : IsPartOfGameInfoSerialization {
}
}
private fun approximateMajorDepositDistribution(): Double {
// We can't replicate the MapRegions resource distributor, so let's try to get
// a close probability of major deposits per tile
var probability = 0.0
for (unique in allTerrains.flatMap { it.getMatchingUniques(UniqueType.MajorStrategicFrequency) }) {
val frequency = unique.params[0].toIntOrNull() ?: continue
if (frequency <= 0) continue
// The unique param is literally "every N tiles", so to get a probability p=1/f
probability += 1.0 / frequency
}
return if (probability == 0.0) 0.04 // This is the default of 1 per 25 tiles
else probability
}
fun setTerrainFeatures(terrainFeatureList: List<String>) {
terrainFeatures = terrainFeatureList
terrainFeatureObjects = terrainFeatureList.mapNotNull { ruleset.terrains[it] }
@ -928,6 +828,35 @@ class Tile : IsPartOfGameInfoSerialization {
}
}
/** Does not remove roads */
fun removeImprovement() =
improvementFunctions.changeImprovement(null)
fun changeImprovement(improvementStr: String, civToHandleCompletion: Civilization? = null, unit: MapUnit? = null) =
improvementFunctions.changeImprovement(improvementStr, civToHandleCompletion, unit)
// function handling when adding a road to the tile
fun addRoad(roadType: RoadStatus, creatingCivInfo: Civilization?) {
roadStatus = roadType
roadIsPillaged = false
if (getOwner() != null) {
roadOwner = getOwner()!!.civName
} else if (creatingCivInfo != null) {
roadOwner = creatingCivInfo.civName // neutral tile, use building unit
creatingCivInfo.neutralRoads.add(this.position)
}
}
// function handling when removing a road from the tile
fun removeRoad() {
roadIsPillaged = false
if (roadStatus == RoadStatus.None) return
roadStatus = RoadStatus.None
if (owningCity == null)
getRoadOwner()?.neutralRoads?.remove(this.position)
}
fun startWorkingOnImprovement(improvement: TileImprovement, civInfo: Civilization, unit: MapUnit) {
improvementInProgress = improvement.name
turnsToImprovement = if (civInfo.gameInfo.gameParameters.godMode) 1
@ -978,18 +907,6 @@ class Tile : IsPartOfGameInfoSerialization {
owningCity!!.civ.cache.updateCivResources()
}
private fun clearAllPathfindingCaches() {
val units = tileMap.gameInfo.civilizations.asSequence()
.filter { it.isAlive() }
.flatMap { it.units.getCivUnits() }
Log.debug("%s: road pillaged, clearing cache for %d units", this, { units.count() })
for (otherUnit in units) {
otherUnit.movement.clearPathfindingCache()
}
}
fun isPillaged(): Boolean = improvementIsPillaged || roadIsPillaged
fun setRepaired() {
improvementInProgress = null
turnsToImprovement = 0
@ -1002,6 +919,31 @@ class Tile : IsPartOfGameInfoSerialization {
}
private fun clearAllPathfindingCaches() {
val units = tileMap.gameInfo.civilizations.asSequence()
.filter { it.isAlive() }
.flatMap { it.units.getCivUnits() }
Log.debug("%s: road pillaged, clearing cache for %d units", this, { units.count() })
for (otherUnit in units) {
otherUnit.movement.clearPathfindingCache()
}
}
fun setExplored(player: Civilization, isExplored: Boolean, explorerPosition: Vector2? = null) {
if (isExplored) {
// Disable the undo button if a new tile has been explored
if (!exploredBy.contains(player.civName)) {
GUI.clearUndoCheckpoints()
exploredBy = exploredBy.withItem(player.civName)
}
if (player.playerType == PlayerType.Human)
player.exploredRegion.checkTilePosition(position, explorerPosition)
} else {
exploredBy = exploredBy.withoutItem(player.civName)
}
}
/**
* Assign a continent ID to this tile.
*
@ -1018,5 +960,67 @@ class Tile : IsPartOfGameInfoSerialization {
/** Clear continent ID, for map editor */
fun clearContinent() { continent = -1 }
/** Allows resetting the cached value [isAdjacentToRiver] will return
* @param isKnownTrue Set this to indicate you need to update the cache due to **adding** a river edge
* (removing would need to look at other edges, and that is what isAdjacentToRiver will do)
*/
private fun resetAdjacentToRiverTransient(isKnownTrue: Boolean = false) {
isAdjacentToRiver = isKnownTrue
isAdjacentToRiverKnown = isKnownTrue
}
/**
* Sets the "has river" state of one edge of this Tile. Works for all six directions.
* @param otherTile The neighbor tile in the direction the river we wish to change is (If it's not a neighbor, this does nothing).
* @param newValue The new river edge state: `true` to create a river, `false` to remove one.
* @param convertTerrains If true, calls MapGenerator's convertTerrains to apply UniqueType.ChangesTerrain effects.
* @return The state did change (`false`: the edge already had the `newValue`)
*/
fun setConnectedByRiver(otherTile: Tile, newValue: Boolean, convertTerrains: Boolean = false): Boolean {
//todo synergy potential with [MapEditorEditRiversTab]?
val field = when (tileMap.getNeighborTileClockPosition(this, otherTile)) {
2 -> otherTile::hasBottomLeftRiver // we're to the bottom-left of it
4 -> ::hasBottomRightRiver // we're to the top-left of it
6 -> ::hasBottomRiver // we're directly above it
8 -> ::hasBottomLeftRiver // we're to the top-right of it
10 -> otherTile::hasBottomRightRiver // we're to the bottom-right of it
12 -> otherTile::hasBottomRiver // we're directly below it
else -> return false
}
if (field.get() == newValue) return false
field.set(newValue)
val affectedTiles = listOf(this, otherTile)
for (tile in affectedTiles)
tile.resetAdjacentToRiverTransient(newValue)
if (convertTerrains)
MapGenerator.Helpers.convertTerrains(ruleset, affectedTiles)
return true
}
//endregion
//region Overrides
/** Shows important properties of this tile for debugging _only_, it helps to see what you're doing */
override fun toString(): String {
val lineList = arrayListOf("Tile @$position")
if (!this::baseTerrain.isInitialized) return lineList[0] + ", uninitialized"
if (isCityCenter()) lineList += getCity()!!.name
lineList += baseTerrain
for (terrainFeature in terrainFeatures) lineList += terrainFeature
if (resource != null) {
lineList += if (tileResource.resourceType == ResourceType.Strategic)
"{$resourceAmount} {$resource}"
else
resource!!
}
if (naturalWonder != null) lineList += naturalWonder!!
if (roadStatus !== RoadStatus.None && !isCityCenter()) lineList += roadStatus.name
if (improvement != null) lineList += improvement!!
if (civilianUnit != null) lineList += civilianUnit!!.name + " - " + civilianUnit!!.civ.civName
if (militaryUnit != null) lineList += militaryUnit!!.name + " - " + militaryUnit!!.civ.civName
if (this::baseTerrainObject.isInitialized && isImpassible()) lineList += Constants.impassable
return lineList.joinToString()
}
//endregion
}

View File

@ -33,7 +33,7 @@ enum class ImprovementBuildingProblem(
Other
}
class TileInfoImprovementFunctions(val tile: Tile) {
class TileImprovementFunctions(val tile: Tile) {
/** Returns true if the [improvement] can be built on this [Tile] */
fun canBuildImprovement(improvement: TileImprovement, civInfo: Civilization): Boolean = getImprovementBuildingProblems(improvement, civInfo).none()

View File

@ -119,7 +119,7 @@ class ConsoleTileCommands: ConsoleCommandNode {
.flatMap { civ -> civ.cities }
.firstOrNull { it.name.toCliInput() == param }
// If the user didn't specify a City, they must have given us a Civilization instead -
// copy of TileInfoImprovementFunctions.takeOverTilesAround.fallbackNearestCity
// copy of TileImprovementFunctions.takeOverTilesAround.fallbackNearestCity
?: console.getCivByName(params[0]) // throws if no match
.cities.minByOrNull { it.getCenterTile().aerialDistanceTo(selectedTile) + (if (it.isBeingRazed) 5 else 0) }
}

View File

@ -428,13 +428,12 @@ object UnitActionsFromUniques {
val tile = unit.currentTile
if (!tile.isPillaged()) return 0
if (tile.improvementInProgress == Constants.repair) return tile.turnsToImprovement
var repairTurns = tile.ruleset.tileImprovements[Constants.repair]!!.getTurnsToBuild(unit.civ, unit)
val repairTurns = tile.ruleset.tileImprovements[Constants.repair]!!.getTurnsToBuild(unit.civ, unit)
val pillagedImprovement = tile.getImprovementToRepair()!!
val turnsToBuild = pillagedImprovement.getTurnsToBuild(unit.civ, unit)
// cap repair to number of turns to build original improvement
if (turnsToBuild < repairTurns) repairTurns = turnsToBuild
return repairTurns
return repairTurns.coerceAtMost(turnsToBuild)
}
internal fun getRepairActions(unit: MapUnit, tile: Tile) = sequenceOf(getRepairAction(unit)).filterNotNull()