chore: 'use gold' automation from NextTurnAutomation

Cleaned up Tile.kt a bit by moving single-use functions to where they are used
This commit is contained in:
Yair Morgenstern
2023-10-08 17:15:53 +03:00
parent dd67a7d3df
commit 6dc7f0ec4c
6 changed files with 223 additions and 202 deletions

View File

@ -32,10 +32,8 @@ import com.unciv.models.Counter
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.INonPerpetualConstruction
import com.unciv.models.ruleset.MilestoneType import com.unciv.models.ruleset.MilestoneType
import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.PerpetualConstruction
import com.unciv.models.ruleset.Policy import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.PolicyBranch import com.unciv.models.ruleset.PolicyBranch
import com.unciv.models.ruleset.Victory import com.unciv.models.ruleset.Victory
@ -47,8 +45,6 @@ import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.screens.victoryscreen.RankingType import com.unciv.ui.screens.victoryscreen.RankingType
import java.util.SortedMap
import java.util.TreeMap
import kotlin.math.min import kotlin.math.min
import kotlin.random.Random import kotlin.random.Random
@ -84,7 +80,7 @@ object NextTurnAutomation {
chooseTechToResearch(civInfo) chooseTechToResearch(civInfo)
automateCityBombardment(civInfo) automateCityBombardment(civInfo)
useGold(civInfo) UseGoldAutomation.useGold(civInfo)
if (!civInfo.isCityState()) { if (!civInfo.isCityState()) {
protectCityStates(civInfo) protectCityStates(civInfo)
bullyCityStates(civInfo) bullyCityStates(civInfo)
@ -291,149 +287,6 @@ object NextTurnAutomation {
civInfo.popupAlerts.clear() // AIs don't care about popups. civInfo.popupAlerts.clear() // AIs don't care about popups.
} }
private fun tryGainInfluence(civInfo: Civilization, cityState: Civilization) {
if (civInfo.gold < 250) return // save up
if (cityState.getDiplomacyManager(civInfo).getInfluence() < 20) {
cityState.cityStateFunctions.receiveGoldGift(civInfo, 250)
return
}
if (civInfo.gold < 500) return // it's not worth it to invest now, wait until you have enough for 2
cityState.cityStateFunctions.receiveGoldGift(civInfo, 500)
return
}
private fun useGoldForCityStates(civ: Civilization) {
// RARE EDGE CASE: If you ally with a city-state, you may reveal more map that includes ANOTHER civ!
// So if we don't lock this list, we may later discover that there are more known civs, concurrent modification exception!
val knownCityStates = civ.getKnownCivs().filter { it.isCityState() }.toList()
// canBeMarriedBy checks actual cost, but it can't be below 500*speedmodifier, and the later check is expensive
if (civ.gold >= 330 && civ.getHappiness() > 0 && civ.hasUnique(UniqueType.CityStateCanBeBoughtForGold)) {
for (cityState in knownCityStates.toList() ) { // Materialize sequence as diplomaticMarriage may kill a CS
if (cityState.cityStateFunctions.canBeMarriedBy(civ))
cityState.cityStateFunctions.diplomaticMarriage(civ)
if (civ.getHappiness() <= 0) break // Stop marrying if happiness is getting too low
}
}
if (civ.gold < 250) return // skip checks if tryGainInfluence will bail anyway
if (civ.wantsToFocusOn(Victory.Focus.Culture)) {
for (cityState in knownCityStates.filter { it.cityStateFunctions.canProvideStat(Stat.Culture) }) {
val diploManager = cityState.getDiplomacyManager(civ)
if (diploManager.getInfluence() < 40) { // we want to gain influence with them
tryGainInfluence(civ, cityState)
}
}
}
if (civ.gold < 250 || knownCityStates.none()) return
val cityState = knownCityStates
.filter { it.getAllyCiv() != civ.civName }
.associateWith { valueCityStateAlliance(civ, it) }
.maxByOrNull { it.value }?.takeIf { it.value > 0 }?.key
if (cityState != null) {
tryGainInfluence(civ, cityState)
}
}
/** allow AI to spend money to purchase city-state friendship, buildings & unit */
private fun useGold(civ: Civilization) {
if (civ.isMajorCiv())
useGoldForCityStates(civ)
for (city in civ.cities.sortedByDescending { it.population.population }) {
val construction = city.cityConstructions.getCurrentConstruction()
if (construction is PerpetualConstruction) continue
if ((construction as INonPerpetualConstruction).canBePurchasedWithStat(city, Stat.Gold)
&& city.civ.gold / 3 >= construction.getStatBuyCost(city, Stat.Gold)!!) {
city.cityConstructions.purchaseConstruction(construction, 0, true)
}
}
maybeBuyCityTiles(civ)
}
private fun maybeBuyCityTiles(civInfo: Civilization) {
if (civInfo.gold <= 0)
return
// Don't buy tiles in the very early game. It is unlikely that we already have the required
// tech, the necessary worker and that there is a reasonable threat from another player to
// grab the tile. We could also check all that, but it would require a lot of cycles each
// turn and this is probably a good approximation.
if (civInfo.gameInfo.turns < (civInfo.gameInfo.speed.scienceCostModifier * 20).toInt())
return
val highlyDesirableTiles: SortedMap<Tile, MutableSet<City>> = TreeMap(
compareByDescending<Tile?> { it?.naturalWonder != null }
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Luxury }
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Strategic }
// This is necessary, so that the map keeps Tiles with the same resource as two
// separate entries.
.thenBy { it.hashCode() }
)
for (city in civInfo.cities.filter { !it.isPuppet && !it.isBeingRazed }) {
val highlyDesirableTilesInCity = city.tilesInRange.filter {
val hasNaturalWonder = it.naturalWonder != null
val hasLuxuryCivDoesntOwn =
it.hasViewableResource(civInfo)
&& it.tileResource.resourceType == ResourceType.Luxury
&& !civInfo.hasResource(it.resource!!)
val hasResourceCivHasNoneOrLittle =
it.hasViewableResource(civInfo)
&& it.tileResource.resourceType == ResourceType.Strategic
&& civInfo.getResourceAmount(it.resource!!) <= 3
it.isVisible(civInfo) && it.getOwner() == null
&& it.neighbors.any { neighbor -> neighbor.getCity() == city }
(hasNaturalWonder || hasLuxuryCivDoesntOwn || hasResourceCivHasNoneOrLittle)
}
for (highlyDesirableTileInCity in highlyDesirableTilesInCity) {
highlyDesirableTiles.getOrPut(highlyDesirableTileInCity) { mutableSetOf() }
.add(city)
}
}
// Always try to buy highly desirable tiles if it can be afforded.
for (highlyDesirableTile in highlyDesirableTiles) {
val cityWithLeastCostToBuy = highlyDesirableTile.value.minBy {
it.getCenterTile().aerialDistanceTo(highlyDesirableTile.key)
}
val bfs = BFS(cityWithLeastCostToBuy.getCenterTile())
{
it.getOwner() == null || it.owningCity == cityWithLeastCostToBuy
}
bfs.stepUntilDestination(highlyDesirableTile.key)
val tilesThatNeedBuying =
bfs.getPathTo(highlyDesirableTile.key).filter { it.getOwner() == null }
.toList().reversed() // getPathTo is from destination to source
// We're trying to acquire everything and revert if it fails, because of the difficult
// way how tile acquisition cost is calculated. Everytime you buy a tile, the next one
// gets more expensive and by how much depends on other things such as game speed. To
// not introduce hidden dependencies on that and duplicate that logic here to calculate
// the price of the whole path, this is probably simpler.
var ranOutOfMoney = false
var goldSpent = 0
for (tileThatNeedsBuying in tilesThatNeedBuying) {
val goldCostOfTile =
cityWithLeastCostToBuy.expansion.getGoldCostOfTile(tileThatNeedsBuying)
if (civInfo.gold >= goldCostOfTile) {
cityWithLeastCostToBuy.expansion.buyTile(tileThatNeedsBuying)
goldSpent += goldCostOfTile
} else {
ranOutOfMoney = true
break
}
}
if (ranOutOfMoney) {
for (tileThatNeedsBuying in tilesThatNeedBuying) {
cityWithLeastCostToBuy.expansion.relinquishOwnership(tileThatNeedsBuying)
}
civInfo.addGold(goldSpent)
}
}
}
internal fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int { internal fun valueCityStateAlliance(civInfo: Civilization, cityState: Civilization): Int {
var value = 0 var value = 0
@ -532,7 +385,7 @@ object NextTurnAutomation {
if (civInfo.tech.techsToResearch.isEmpty()) { if (civInfo.tech.techsToResearch.isEmpty()) {
val costs = getGroupedResearchableTechs() val costs = getGroupedResearchableTechs()
if (costs.isEmpty()) return if (costs.isEmpty()) return
val cheapestTechs = costs[0] val cheapestTechs = costs[0]
//Do not consider advanced techs if only one tech left in cheapest group //Do not consider advanced techs if only one tech left in cheapest group
val techToResearch: Technology = val techToResearch: Technology =

View File

@ -0,0 +1,177 @@
package com.unciv.logic.automation.civilization
import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.BFS
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.INonPerpetualConstruction
import com.unciv.models.ruleset.PerpetualConstruction
import com.unciv.models.ruleset.Victory
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import java.util.SortedMap
import java.util.TreeMap
object UseGoldAutomation {
/** allow AI to spend money to purchase city-state friendship, buildings & unit */
fun useGold(civ: Civilization) {
if (civ.isMajorCiv())
useGoldForCityStates(civ)
for (city in civ.cities.sortedByDescending { it.population.population }) {
val construction = city.cityConstructions.getCurrentConstruction()
if (construction is PerpetualConstruction) continue
if ((construction as INonPerpetualConstruction).canBePurchasedWithStat(city, Stat.Gold)
&& city.civ.gold / 3 >= construction.getStatBuyCost(city, Stat.Gold)!!) {
city.cityConstructions.purchaseConstruction(construction, 0, true)
}
}
maybeBuyCityTiles(civ)
}
private fun useGoldForCityStates(civ: Civilization) {
// RARE EDGE CASE: If you ally with a city-state, you may reveal more map that includes ANOTHER civ!
// So if we don't lock this list, we may later discover that there are more known civs, concurrent modification exception!
val knownCityStates = civ.getKnownCivs().filter { it.isCityState() }.toList()
// canBeMarriedBy checks actual cost, but it can't be below 500*speedmodifier, and the later check is expensive
if (civ.gold >= 330 && civ.getHappiness() > 0 && civ.hasUnique(UniqueType.CityStateCanBeBoughtForGold)) {
for (cityState in knownCityStates.toList() ) { // Materialize sequence as diplomaticMarriage may kill a CS
if (cityState.cityStateFunctions.canBeMarriedBy(civ))
cityState.cityStateFunctions.diplomaticMarriage(civ)
if (civ.getHappiness() <= 0) break // Stop marrying if happiness is getting too low
}
}
if (civ.gold < 250) return // skip checks if tryGainInfluence will bail anyway
if (civ.wantsToFocusOn(Victory.Focus.Culture)) {
for (cityState in knownCityStates.filter { it.cityStateFunctions.canProvideStat(Stat.Culture) }) {
val diploManager = cityState.getDiplomacyManager(civ)
if (diploManager.getInfluence() < 40) { // we want to gain influence with them
tryGainInfluence(civ, cityState)
}
}
}
if (civ.gold < 250 || knownCityStates.none()) return
val cityState = knownCityStates
.filter { it.getAllyCiv() != civ.civName }
.associateWith { NextTurnAutomation.valueCityStateAlliance(civ, it) }
.maxByOrNull { it.value }?.takeIf { it.value > 0 }?.key
if (cityState != null) {
tryGainInfluence(civ, cityState)
}
}
private fun maybeBuyCityTiles(civInfo: Civilization) {
if (civInfo.gold <= 0)
return
// Don't buy tiles in the very early game. It is unlikely that we already have the required
// tech, the necessary worker and that there is a reasonable threat from another player to
// grab the tile. We could also check all that, but it would require a lot of cycles each
// turn and this is probably a good approximation.
if (civInfo.gameInfo.turns < (civInfo.gameInfo.speed.scienceCostModifier * 20).toInt())
return
val highlyDesirableTiles: SortedMap<Tile, MutableSet<City>> = getHighlyDesirableTilesToCityMap(civInfo)
// Always try to buy highly desirable tiles if it can be afforded.
for (highlyDesirableTile in highlyDesirableTiles) {
val cityWithLeastCostToBuy = highlyDesirableTile.value.minBy {
it.getCenterTile().aerialDistanceTo(highlyDesirableTile.key)
}
val bfs = BFS(cityWithLeastCostToBuy.getCenterTile())
{
it.getOwner() == null || it.owningCity == cityWithLeastCostToBuy
}
bfs.stepUntilDestination(highlyDesirableTile.key)
val tilesThatNeedBuying =
bfs.getPathTo(highlyDesirableTile.key).filter { it.getOwner() == null }
.toList().reversed() // getPathTo is from destination to source
// We're trying to acquire everything and revert if it fails, because of the difficult
// way how tile acquisition cost is calculated. Everytime you buy a tile, the next one
// gets more expensive and by how much depends on other things such as game speed. To
// not introduce hidden dependencies on that and duplicate that logic here to calculate
// the price of the whole path, this is probably simpler.
var ranOutOfMoney = false
var goldSpent = 0
for (tileThatNeedsBuying in tilesThatNeedBuying) {
val goldCostOfTile =
cityWithLeastCostToBuy.expansion.getGoldCostOfTile(tileThatNeedsBuying)
if (civInfo.gold >= goldCostOfTile) {
cityWithLeastCostToBuy.expansion.buyTile(tileThatNeedsBuying)
goldSpent += goldCostOfTile
} else {
ranOutOfMoney = true
break
}
}
if (ranOutOfMoney) {
for (tileThatNeedsBuying in tilesThatNeedBuying) {
cityWithLeastCostToBuy.expansion.relinquishOwnership(tileThatNeedsBuying)
}
civInfo.addGold(goldSpent)
}
}
}
private fun getHighlyDesirableTilesToCityMap(civInfo: Civilization): SortedMap<Tile, MutableSet<City>> {
val highlyDesirableTiles: SortedMap<Tile, MutableSet<City>> = TreeMap(
compareByDescending<Tile?> { it?.naturalWonder != null }
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Luxury }
.thenByDescending { it?.resource != null && it.tileResource.resourceType == ResourceType.Strategic }
// This is necessary, so that the map keeps Tiles with the same resource as two
// separate entries.
.thenBy { it.hashCode() }
)
for (city in civInfo.cities.filter { !it.isPuppet && !it.isBeingRazed }) {
val highlyDesirableTilesInCity = city.tilesInRange.filter {
isHighlyDesirableTile(it, civInfo, city)
}
for (highlyDesirableTileInCity in highlyDesirableTilesInCity) {
highlyDesirableTiles.getOrPut(highlyDesirableTileInCity) { mutableSetOf() }
.add(city)
}
}
return highlyDesirableTiles
}
private fun isHighlyDesirableTile(it: Tile, civInfo: Civilization, city: City): Boolean {
if (!it.isVisible(civInfo)) return false
if (it.getOwner() != null) return false
if (it.neighbors.none { neighbor -> neighbor.getCity() == city }) return false
fun hasNaturalWonder() = it.naturalWonder != null
fun hasLuxuryCivDoesntOwn() =
it.hasViewableResource(civInfo)
&& it.tileResource.resourceType == ResourceType.Luxury
&& !civInfo.hasResource(it.resource!!)
fun hasResourceCivHasNoneOrLittle() =
it.hasViewableResource(civInfo)
&& it.tileResource.resourceType == ResourceType.Strategic
&& civInfo.getResourceAmount(it.resource!!) <= 3
return (hasNaturalWonder() || hasLuxuryCivDoesntOwn() || hasResourceCivHasNoneOrLittle())
}
private fun tryGainInfluence(civInfo: Civilization, cityState: Civilization) {
if (civInfo.gold < 250) return // save up
if (cityState.getDiplomacyManager(civInfo).getInfluence() < 20) {
cityState.cityStateFunctions.receiveGoldGift(civInfo, 250)
return
}
if (civInfo.gold < 500) return // it's not worth it to invest now, wait until you have enough for 2
cityState.cityStateFunctions.receiveGoldGift(civInfo, 500)
return
}
}

View File

@ -890,6 +890,22 @@ class MapRegions (val ruleset: Ruleset){
} }
// For dividing the map into Regions to determine start locations
fun Tile.getTileFertility(checkCoasts: Boolean): Int {
var fertility = 0
for (terrain in allTerrains) {
if (terrain.hasUnique(UniqueType.OverrideFertility))
return terrain.getMatchingUniques(UniqueType.OverrideFertility).first().params[0].toInt()
else
fertility += terrain.getMatchingUniques(UniqueType.AddFertility)
.sumOf { it.params[0].toInt() }
}
if (isAdjacentToRiver()) fertility += 1
if (isAdjacentTo(Constants.freshWater)) fertility += 1 // meaning total +2 for river
if (checkCoasts && isCoastalTile()) fertility += 2
return fertility
}
fun getRegionPriority(terrain: Terrain?): Int? { fun getRegionPriority(terrain: Terrain?): Int? {
if (terrain == null) // ie "hybrid" if (terrain == null) // ie "hybrid"
return 99999 // a big number return 99999 // a big number

View File

@ -620,6 +620,16 @@ class MapUnit : IsPartOfGameInfoSerialization {
fun removeFromTile() = currentTile.removeUnit(this) fun removeFromTile() = currentTile.removeUnit(this)
/** Return null if military on tile, or no civilian */
private fun Tile.getUnguardedCivilian(attacker: MapUnit): MapUnit? {
return when {
militaryUnit != null && militaryUnit != attacker -> null
civilianUnit != null -> civilianUnit!!
else -> null
}
}
fun moveThroughTile(tile: Tile) { fun moveThroughTile(tile: Tile) {
// addPromotion requires currentTile to be valid because it accesses ruleset through it. // addPromotion requires currentTile to be valid because it accesses ruleset through it.
// getAncientRuinBonus, if it places a new unit, does too // getAncientRuinBonus, if it places a new unit, does too

View File

@ -205,13 +205,6 @@ open class Tile : IsPartOfGameInfoSerialization {
return null return null
} }
/** Return null if military on tile, or no civilian */
fun getUnguardedCivilian(attacker: MapUnit): MapUnit? {
if (militaryUnit != null && militaryUnit != attacker) return null
if (civilianUnit != null) return civilianUnit!!
return null
}
fun getCity(): City? = owningCity fun getCity(): City? = owningCity
@Transient @Transient
@ -353,16 +346,6 @@ open class Tile : IsPartOfGameInfoSerialization {
// We have to .toList() so that the values are stored together once for caching, // 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 // and the toSequence so that aggregations (like neighbors.flatMap{it.units} don't take up their own space
/** Returns the left shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */
fun getLeftSharedNeighbor(neighbor: Tile): Tile? {
return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) - 2) % 12)
}
/** Returns the right shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */
fun getRightSharedNeighbor(neighbor: Tile): Tile? {
return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) + 2) % 12)
}
fun getRow() = HexMath.getRow(position) fun getRow() = HexMath.getRow(position)
fun getColumn() = HexMath.getColumn(position) fun getColumn() = HexMath.getColumn(position)
@ -444,16 +427,12 @@ open class Tile : IsPartOfGameInfoSerialization {
else -> false // Neutral - unblocks tile; else -> false // Neutral - unblocks tile;
} }
} }
if (isLand) // Only water tiles are blocked if empty if (isLand) // Only water tiles are blocked if empty
return false return false
// For water tiles need also to check neighbors: // For water tiles need also to check neighbors:
// enemy military naval units blockade all adjacent water tiles. // enemy military naval units blockade all adjacent water tiles.
for (neighbor in neighbors) { for (neighbor in neighbors.filter { it.isWater }) {
if (!neighbor.isWater)
continue
val neighborUnit = neighbor.militaryUnit ?: continue val neighborUnit = neighbor.militaryUnit ?: continue
// Embarked units do not blockade adjacent tiles // Embarked units do not blockade adjacent tiles
@ -473,22 +452,6 @@ open class Tile : IsPartOfGameInfoSerialization {
return workingCity != null && workingCity.lockedTiles.contains(position) return workingCity != null && workingCity.lockedTiles.contains(position)
} }
// For dividing the map into Regions to determine start locations
fun getTileFertility(checkCoasts: Boolean): Int {
var fertility = 0
for (terrain in allTerrains) {
if (terrain.hasUnique(UniqueType.OverrideFertility))
return terrain.getMatchingUniques(UniqueType.OverrideFertility).first().params[0].toInt()
else
fertility += terrain.getMatchingUniques(UniqueType.AddFertility)
.sumOf { it.params[0].toInt() }
}
if (isAdjacentToRiver()) fertility += 1
if (isAdjacentTo(Constants.freshWater)) fertility += 1 // meaning total +2 for river
if (checkCoasts && isCoastalTile()) fertility += 2
return fertility
}
fun providesResources(civInfo: Civilization): Boolean { fun providesResources(civInfo: Civilization): Boolean {
if (!hasViewableResource(civInfo)) return false if (!hasViewableResource(civInfo)) return false
if (isCityCenter()) return true if (isCityCenter()) return true
@ -576,17 +539,10 @@ open class Tile : IsPartOfGameInfoSerialization {
resource != null && (tileResource.revealedBy == null || civInfo.tech.isResearched( resource != null && (tileResource.revealedBy == null || civInfo.tech.isResearched(
tileResource.revealedBy!!)) tileResource.revealedBy!!))
fun getViewableTilesList(distance: Int): List<Tile> = fun getViewableTilesList(distance: Int): List<Tile> = tileMap.getViewableTiles(position, distance)
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 getTilesInDistance(distance: Int): Sequence<Tile> = fun getTilesAtDistance(distance: Int): Sequence<Tile> =tileMap.getTilesAtDistance(position, distance)
tileMap.getTilesInDistance(position, distance)
fun getTilesInDistanceRange(range: IntRange): Sequence<Tile> =
tileMap.getTilesInDistanceRange(position, range)
fun getTilesAtDistance(distance: Int): Sequence<Tile> =
tileMap.getTilesAtDistance(position, distance)
fun getDefensiveBonus(): Float { fun getDefensiveBonus(): Float {
var bonus = baseTerrainObject.defenceBonus var bonus = baseTerrainObject.defenceBonus
@ -937,9 +893,7 @@ open class Tile : IsPartOfGameInfoSerialization {
owningCity!!.civ.cache.updateCivResources() owningCity!!.civ.cache.updateCivResources()
} }
fun isPillaged(): Boolean { fun isPillaged(): Boolean = improvementIsPillaged || roadIsPillaged
return improvementIsPillaged || roadIsPillaged
}
fun setRepaired() { fun setRepaired() {
improvementInProgress = null improvementInProgress = null

View File

@ -5,8 +5,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.components.tilegroups.TileGroup import com.unciv.ui.components.tilegroups.TileGroup
import com.unciv.ui.images.ImageGetter
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.atan import kotlin.math.atan
@ -33,6 +33,17 @@ class TileLayerBorders(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup,
} }
} }
/** Returns the left shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */
private fun Tile.getLeftSharedNeighbor(neighbor: Tile): Tile? {
return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) - 2) % 12)
}
/** Returns the right shared neighbor of `this` and [neighbor] (relative to the view direction `this`->[neighbor]), or null if there is no such tile. */
private fun Tile.getRightSharedNeighbor(neighbor: Tile): Tile? {
return tileMap.getClockPositionNeighborTile(this,(tileMap.getNeighborTileClockPosition(this, neighbor) + 2) % 12)
}
private fun updateBorders() { private fun updateBorders() {
// This is longer than it could be, because of performance - // This is longer than it could be, because of performance -