mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 15:27:50 +07:00
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:
@ -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 =
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 -
|
||||||
|
Reference in New Issue
Block a user