Update religious AI; buying faith buildings, inquisitor AI updates (#7454)

* Update AI for buying with Faith, allowing it to buy Faith buildings

* Minor bug fixes

* Revamped inquisitor AI, minor changes to missionary AI

* Reviews

* Reviews

* Reviews

* Removed magic number
This commit is contained in:
Xander Lenstra
2022-07-23 19:06:48 +02:00
committed by GitHub
parent 2f52b6bf1a
commit 52d0814e7b
27 changed files with 409 additions and 196 deletions

View File

@ -4,8 +4,8 @@ object Constants {
const val worker = "Worker"
const val settler = "Settler"
const val eraSpecificUnit = "Era Starting Unit"
const val spreadReligionAbilityCount = "Spread Religion"
const val removeHeresyAbilityCount = "Remove Foreign religions from your own cities"
const val spreadReligion = "Spread Religion"
const val removeHeresy = "Remove Foreign religions from your own cities"
const val impassable = "Impassable"
const val ocean = "Ocean"
@ -85,6 +85,7 @@ object Constants {
* _Most_ checks do compare to 0!
*/
const val minimumMovementEpsilon = 0.05f // 0.1f was used previously, too - here for global searches
const val aiPreferInquisitorOverMissionaryPressureDifference = 3000f
const val defaultFontSize = 18
const val headingFontSize = 24

View File

@ -12,7 +12,7 @@ import com.unciv.logic.BackwardCompatibility.removeMissingModReferences
import com.unciv.logic.BackwardCompatibility.updateGreatGeneralUniques
import com.unciv.logic.GameInfo.Companion.CURRENT_COMPATIBILITY_NUMBER
import com.unciv.logic.GameInfo.Companion.FIRST_WITHOUT
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.CivilizationInfoPreview

View File

@ -1,5 +1,7 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.city
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.city.PerpetualConstruction
@ -11,7 +13,6 @@ import com.unciv.models.ruleset.MilestoneType
import com.unciv.models.ruleset.Victory
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import kotlin.math.max
import kotlin.math.sqrt
@ -47,8 +48,6 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private val relativeCostEffectiveness = ArrayList<ConstructionChoice>()
private val faithConstruction = arrayListOf<BaseUnit>()
private data class ConstructionChoice(val choice: String, var choiceModifier: Float, val remainingWork: Int)
private fun addChoice(choices: ArrayList<ConstructionChoice>, choice: String, choiceModifier: Float) {
@ -79,7 +78,6 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
addWorkerChoice()
addWorkBoatChoice()
addMilitaryUnitChoice()
addReligiousUnit()
}
val production = cityInfo.cityStats.currentCityStats.production
@ -106,14 +104,6 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
NotificationIcon.Construction
)
cityConstructions.currentConstructionFromQueue = chosenConstruction
if (civInfo.isPlayerCivilization()) return // don't want the ai to control what a player uses faith for
val chosenItem = faithConstruction.firstOrNull {
it.getStatBuyCost(cityInfo, stat = Stat.Faith)!! <= civInfo.religionManager.storedFaith
} ?: return
cityConstructions.purchaseConstruction(chosenItem.name, -1, false, stat=Stat.Faith)
}
private fun addMilitaryUnitChoice() {
@ -189,9 +179,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addCultureBuildingChoice() {
val cultureBuilding = nonWonders
.filter { it.isStatRelated(Stat.Culture)
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
.isBuildable().minByOrNull { it.cost }
.filter { it.isStatRelated(Stat.Culture)
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
}.isBuildable()
.minByOrNull { it.cost }
if (cultureBuilding != null) {
var modifier = 0.5f
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
@ -266,9 +257,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addUnitTrainingBuildingChoice() {
val unitTrainingBuilding = nonWonders
.filter { it.hasUnique(UniqueType.UnitStartingExperience)
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
.isBuildable().minByOrNull { it.cost }
.filter { it.hasUnique(UniqueType.UnitStartingExperience)
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
}.isBuildable()
.minByOrNull { it.cost }
if (unitTrainingBuilding != null && (!civInfo.wantsToFocusOn(Victory.Focus.Culture) || isAtWar)) {
var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
if (isAtWar) modifier *= 2
@ -280,9 +272,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addDefenceBuildingChoice() {
val defensiveBuilding = nonWonders
.filter { it.cityStrength > 0
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)}
.isBuildable().minByOrNull { it.cost }
.filter { it.cityStrength > 0
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
}.isBuildable()
.minByOrNull { it.cost }
if (defensiveBuilding != null && (isAtWar || !civInfo.wantsToFocusOn(Victory.Focus.Culture))) {
var modifier = 0.2f
if (isAtWar) modifier = 0.5f
@ -299,10 +292,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addHappinessBuildingChoice() {
val happinessBuilding = nonWonders
.filter { (it.isStatRelated(Stat.Happiness)
|| it.uniques.contains("Remove extra unhappiness from annexed cities"))
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
.isBuildable().minByOrNull { it.cost }
.filter { (it.isStatRelated(Stat.Happiness)
|| it.uniques.contains("Remove extra unhappiness from annexed cities"))
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
.isBuildable()
.minByOrNull { it.cost }
if (happinessBuilding != null) {
var modifier = 1f
val civHappiness = civInfo.getHappiness()
@ -359,48 +353,4 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
addChoice(relativeCostEffectiveness, foodBuilding.name, modifier)
}
}
private fun addReligiousUnit() {
// these 4 if conditions are used to determine if an AI should buy units to spread religion, or spend faith to buy things like new military units or new buildings.
// currently this AI can only buy inquisitors and missionaries with faith
// this system will have to be reengineered to support buying other stuff with faith
if (civInfo.wantsToFocusOn(Victory.Focus.Military)) return
if (civInfo.religionManager.religion?.name == null) return
if (cityInfo.religion.getMajorityReligion()?.name != civInfo.religionManager.religion?.name)
return // you don't want to build units of opposing religions.
var modifier = 0f
// The performance of the regular getMatchingUniques is better, since it only tries to find one unique,
// while the canBePurchasedWithStat tries (at time of writing) *6* different uniques.
val missionary = units
.firstOrNull { it -> it.getMatchingUniques("Can [] [] times").any { it.params[0] == "Spread Religion"}
&& it.canBePurchasedWithStat(cityInfo, Stat.Faith) }
val inquisitor = units
.firstOrNull { it.hasUnique("Prevents spreading of religion to the city it is next to")
&& it.canBePurchasedWithStat(cityInfo, Stat.Faith) }
if (civInfo.wantsToFocusOn(Victory.Focus.Culture)) modifier += 1
if (isAtWar) modifier -= 0.5f
val citiesNotFollowingOurReligion = civInfo.cities.asSequence()
.filterNot { it.religion.getMajorityReligion()?.name == civInfo.religionManager.religion!!.name }
val buildInquisitor = citiesNotFollowingOurReligion
.filter { it.religion.getMajorityReligion()?.name == civInfo.religionManager.religion?.name }
.toList().size.toFloat() / 10 + modifier
val possibleSpreadReligionTargets = civInfo.gameInfo.getCities()
.filter { it.getCenterTile().aerialDistanceTo(cityInfo.getCenterTile()) < 30 }
val buildMissionary = possibleSpreadReligionTargets.toList().size.toFloat() / 15 + modifier
if (buildMissionary > buildInquisitor && missionary != null && missionary.isBuildable(cityConstructions)) faithConstruction.add(missionary)
else if (inquisitor != null && inquisitor.isBuildable(cityConstructions)) faithConstruction.add(inquisitor)
}
}

View File

@ -1,6 +1,8 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.civilization
import com.unciv.Constants
import com.unciv.logic.automation.unit.BattleHelper
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit

View File

@ -1,6 +1,9 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.civilization
import com.unciv.Constants
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.ThreatLevel
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.battle.MapUnitCombatant
@ -54,11 +57,14 @@ object NextTurnAutomation {
respondToTradeRequests(civInfo)
if (civInfo.isMajorCiv()) {
if (!civInfo.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
if (!civInfo.gameInfo.ruleSet.modOptions.hasUnique(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
declareWar(civInfo)
offerPeaceTreaty(civInfo)
// offerDeclarationOfFriendship(civInfo)
}
if (civInfo.gameInfo.isReligionEnabled()) {
ReligionAutomation.spendFaithOnReligion(civInfo)
}
offerResearchAgreement(civInfo)
exchangeLuxuries(civInfo)
issueRequests(civInfo)
@ -590,7 +596,7 @@ object NextTurnAutomation {
.flatMap { religion -> religion.getBeliefs(beliefType) }.contains(it.value)
}
.map { it.value }
.maxByOrNull { ChooseBeliefsAutomation.rateBelief(civInfo, it) }
.maxByOrNull { ReligionAutomation.rateBelief(civInfo, it) }
}
private fun potentialLuxuryTrades(civInfo: CivilizationInfo, otherCivInfo: CivilizationInfo): ArrayList<Trade> {

View File

@ -1,6 +1,8 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.civilization
import com.unciv.Constants
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.ReligionState
import com.unciv.logic.map.TileInfo
@ -13,7 +15,181 @@ import kotlin.math.min
import kotlin.math.pow
import kotlin.random.Random
object ChooseBeliefsAutomation {
object ReligionAutomation {
// region faith spending
fun spendFaithOnReligion(civInfo: CivilizationInfo) {
if (civInfo.cities.isEmpty()) return
// Save for great prophet
if (civInfo.religionManager.religionState != ReligionState.EnhancedReligion
&& (civInfo.religionManager.remainingFoundableReligions() != 0 || civInfo.religionManager.religionState > ReligionState.Pantheon)
) {
buyGreatProphetInAnyCity(civInfo)
return
}
// We don't have a religion and no more change of getting it :(
if (civInfo.religionManager.religionState <= ReligionState.Pantheon) {
tryBuyAnyReligiousBuilding(civInfo)
// Todo: buy Great People post industrial era
return
}
// If we don't have majority in all our own cities, build missionaries and inquisitors to solve this
val citiesWithoutOurReligion = civInfo.cities.filter { it.religion.getMajorityReligion() != civInfo.religionManager.religion!! }
// The original had a cap at 4 missionaries total, but 1/4 * the number of cities should be more appropriate imo
if (citiesWithoutOurReligion.count() >
4 * civInfo.getCivUnits().count { it.canDoReligiousAction(Constants.spreadReligion) || it.canDoReligiousAction(Constants.removeHeresy) }
) {
val (city, pressureDifference) = citiesWithoutOurReligion.map { city ->
city to city.religion.getPressureDeficit(civInfo.religionManager.religion?.name)
}.maxBy { it.second }
if (pressureDifference >= Constants.aiPreferInquisitorOverMissionaryPressureDifference)
buyInquisitorNear(civInfo, city)
buyMissionaryInAnyCity(civInfo)
return
}
// Get an inquisitor to defend our holy city
val holyCity = civInfo.religionManager.getHolyCity()
if (holyCity != null
&& holyCity in civInfo.cities
&& civInfo.getCivUnits().count { it.hasUnique(UniqueType.PreventSpreadingReligion) } == 0
&& !holyCity.religion.isProtectedByInquisitor()
) {
buyInquisitorNear(civInfo, holyCity)
return
}
// Buy religious buildings in cities if possible
val citiesWithMissingReligiousBuildings = civInfo.cities.filter { city ->
city.religion.getMajorityReligion() != null
&& !city.cityConstructions.builtBuildings.containsAll(city.religion.getMajorityReligion()!!.buildingsPurchasableByBeliefs)
}
if (citiesWithMissingReligiousBuildings.any()) {
tryBuyAnyReligiousBuilding(civInfo)
return
}
// Todo: buy Great People post industrial era
// Just buy missionaries to spread our religion outside of our civ
if (civInfo.getCivUnits().count { it.canDoReligiousAction(Constants.spreadReligion) } < 4) {
buyMissionaryInAnyCity(civInfo)
return
}
// Todo: buy inquisitors for defence of other cities
}
private fun tryBuyAnyReligiousBuilding(civInfo: CivilizationInfo) {
for (city in civInfo.cities) {
if (city.religion.getMajorityReligion() == null) continue
val buildings = city.religion.getMajorityReligion()!!.buildingsPurchasableByBeliefs
val buildingToBePurchased = buildings
.asSequence()
.map { civInfo.getEquivalentBuilding(it).name }
.map { city.cityConstructions.getConstruction(it) as INonPerpetualConstruction }
.filter { it.isPurchasable(city.cityConstructions) }
.filter { (it.getStatBuyCost(city, Stat.Faith) ?: return@filter false) <= civInfo.religionManager.storedFaith }
.minByOrNull { it.getStatBuyCost(city, Stat.Faith)!! }
?: continue
city.cityConstructions.purchaseConstruction(buildingToBePurchased.name, -1, true, Stat.Faith)
return
}
}
private fun buyMissionaryInAnyCity(civInfo: CivilizationInfo) {
if (civInfo.religionManager.religionState < ReligionState.Religion) return
var missionaries = civInfo.gameInfo.ruleSet.units.values.filter { unit ->
unit.getMatchingUniques(UniqueType.CanActionSeveralTimes).filter { it.params[0] == Constants.spreadReligion }.any()
}.map { it.name }
missionaries = missionaries.map { civInfo.getEquivalentUnit(it).name }
val missionaryConstruction = missionaries
.map { civInfo.cities.first().cityConstructions.getConstruction(it) as INonPerpetualConstruction }
.filter { unit -> civInfo.cities.any { unit.isPurchasable(it.cityConstructions) && unit.canBePurchasedWithStat(it, Stat.Faith) } }
.minByOrNull { it.getStatBuyCost(civInfo.getCapital()!!, Stat.Faith)!! }
?: return
val hasUniqueToTakeCivReligion = civInfo.gameInfo.ruleSet.units[missionaryConstruction.name]!!.hasUnique(UniqueType.TakeReligionOverBirthCity)
val validCitiesToBuy = civInfo.cities.filter {
(hasUniqueToTakeCivReligion || it.religion.getMajorityReligion() == civInfo.religionManager.religion)
&& (missionaryConstruction.getStatBuyCost(it, Stat.Faith) ?: return@filter false) <= civInfo.religionManager.storedFaith
&& missionaryConstruction.isPurchasable(it.cityConstructions)
&& missionaryConstruction.canBePurchasedWithStat(it, Stat.Faith)
}
if (validCitiesToBuy.isEmpty()) return
val citiesWithBonusCharges = validCitiesToBuy.filter { city ->
city.getLocalMatchingUniques(UniqueType.UnitStartingActions).filter { it.params[2] == Constants.spreadReligion }.any()
}
val holyCity = validCitiesToBuy.firstOrNull { it.religion.religionThisIsTheHolyCityOf == civInfo.religionManager.religion!!.name }
val cityToBuyMissionary = when {
citiesWithBonusCharges.any() -> citiesWithBonusCharges.first()
holyCity != null -> holyCity
else -> validCitiesToBuy.first()
}
cityToBuyMissionary.cityConstructions.purchaseConstruction(missionaryConstruction.name, -1, true, Stat.Faith)
return
}
private fun buyGreatProphetInAnyCity(civInfo: CivilizationInfo) {
if (civInfo.religionManager.religionState < ReligionState.Religion) return
var greatProphetUnit = civInfo.religionManager.getGreatProphetEquivalent() ?: return
greatProphetUnit = civInfo.getEquivalentUnit(greatProphetUnit).name
val greatProphetConstruction = civInfo.cities.first().cityConstructions.getConstruction(greatProphetUnit) as INonPerpetualConstruction
val cityToBuyGreatProphet = civInfo.cities
.asSequence()
.filter { greatProphetConstruction.isPurchasable(it.cityConstructions) }
.filter { greatProphetConstruction.canBePurchasedWithStat(it, Stat.Faith) }
.filter { (greatProphetConstruction.getStatBuyCost(it, Stat.Faith) ?: return@filter false) <= civInfo.religionManager.storedFaith }
.minByOrNull { greatProphetConstruction.getStatBuyCost(it, Stat.Faith)!! }
?: return
cityToBuyGreatProphet.cityConstructions.purchaseConstruction(greatProphetUnit, -1, true, Stat.Faith)
}
private fun buyInquisitorNear(civInfo: CivilizationInfo, city: CityInfo) {
if (civInfo.religionManager.religionState < ReligionState.Religion) return
var inquisitors = civInfo.gameInfo.ruleSet.units.values.filter {
it.getMapUnit(civInfo).canDoReligiousAction(Constants.removeHeresy)
|| it.hasUnique(UniqueType.PreventSpreadingReligion)
}.map { it.name }
inquisitors = inquisitors.map { civInfo.getEquivalentUnit(it).name }
val inquisitorConstruction = inquisitors
.map { civInfo.cities.first().cityConstructions.getConstruction(it) as INonPerpetualConstruction }
.filter { unit -> civInfo.cities.any { unit.isPurchasable(it.cityConstructions) } }
.minByOrNull { it.getStatBuyCost(civInfo.getCapital()!!, Stat.Faith)!! }
?: return
val hasUniqueToTakeCivReligion = civInfo.gameInfo.ruleSet.units[inquisitorConstruction.name]!!.hasUnique(UniqueType.TakeReligionOverBirthCity)
val validCitiesToBuy = civInfo.cities.filter {
(hasUniqueToTakeCivReligion || it.religion.getMajorityReligion() == civInfo.religionManager.religion)
&& (inquisitorConstruction.getStatBuyCost(it, Stat.Faith) ?: return@filter false) <= civInfo.religionManager.storedFaith
&& inquisitorConstruction.isPurchasable(it.cityConstructions)
&& inquisitorConstruction.canBePurchasedWithStat(it, Stat.Faith)
}
if (validCitiesToBuy.isEmpty()) return
val cityToBuy = validCitiesToBuy
.filter {
inquisitorConstruction.isPurchasable(it.cityConstructions)
&& inquisitorConstruction.canBePurchasedWithStat(it, Stat.Faith)
}
.minByOrNull { it.getCenterTile().aerialDistanceTo(city.getCenterTile()) } ?: return
cityToBuy.cityConstructions.purchaseConstruction(inquisitorConstruction.name, -1, true, Stat.Faith)
}
// endregion
// region rate beliefs
fun rateBelief(civInfo: CivilizationInfo, belief: Belief): Float {
var score = 0f
@ -206,4 +382,6 @@ object ChooseBeliefsAutomation {
return score
}
//endregion
}

View File

@ -1,4 +1,4 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.unit
import com.unciv.Constants
import com.unciv.logic.battle.Battle

View File

@ -1,5 +1,7 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.unit
import com.unciv.Constants
import com.unciv.logic.automation.Automation
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.GreatGeneralImplementation
import com.unciv.logic.battle.MapUnitCombatant
@ -114,7 +116,7 @@ object SpecificUnitAutomation {
val tileForCitadel = cityToGarrison.getTilesInDistanceRange(3..4)
.firstOrNull {
reachableTest(it) &&
WorkerAutomation.evaluateFortPlacement(it, unit.civInfo, true)
WorkerAutomation.evaluateFortPlacement(it, unit.civInfo, true)
}
if (tileForCitadel == null) {
unit.movement.headTowards(cityToGarrison)
@ -271,7 +273,12 @@ object SpecificUnitAutomation {
}
// if we got here, we're pretty close, start looking!
val chosenTile = applicableTiles.sortedByDescending { Automation.rankTile(it, unit.civInfo) }
val chosenTile = applicableTiles.sortedByDescending {
Automation.rankTile(
it,
unit.civInfo
)
}
.firstOrNull { unit.movement.canReach(it) }
?: continue // to another city
@ -294,78 +301,105 @@ object SpecificUnitAutomation {
}
fun automateMissionary(unit: MapUnit) {
if (unit.religion != unit.civInfo.religionManager.religion?.name)
return unit.destroy()
if (unit.religion != unit.civInfo.religionManager.religion?.name || unit.religion == null)
return unit.disband()
val city = unit.civInfo.gameInfo.getCities().asSequence()
.filter { it.religion.getMajorityReligion()?.name != unit.getReligionDisplayName() }
.filter { it.civInfo.knows(unit.civInfo) && !it.civInfo.isAtWarWith(unit.civInfo) }
.filterNot { it.religion.isProtectedByInquisitor() }
.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.currentTile) } ?: return
val ourCitiesWithoutReligion = unit.civInfo.cities.filter {
it.religion.getMajorityReligion() != unit.civInfo.religionManager.religion
}
val city =
if (ourCitiesWithoutReligion.any())
ourCitiesWithoutReligion.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
else unit.civInfo.gameInfo.getCities().asSequence()
.filter { it.religion.getMajorityReligion() != unit.civInfo.religionManager.religion }
.filter { it.civInfo.knows(unit.civInfo) && !it.civInfo.isAtWarWith(unit.civInfo) }
.filterNot { it.religion.isProtectedByInquisitor(unit.religion) }
.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
if (city == null) return
val destination = city.getTiles().asSequence()
.filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
.sortedBy { it.aerialDistanceTo(unit.getTile()) }
.firstOrNull { unit.movement.canReach(it) } ?: return
unit.movement.headTowards(destination)
if (unit.civInfo.religionManager.maySpreadReligionNow(unit)) {
if (unit.getTile() in city.getTiles() && unit.civInfo.religionManager.maySpreadReligionNow(unit)) {
doReligiousAction(unit, unit.getTile())
}
}
fun automateInquisitor(unit: MapUnit) {
val cityToConvert = unit.civInfo.cities.asSequence()
.filterNot { it.religion.getMajorityReligion()?.name == null }
.filterNot { it.religion.getMajorityReligion()?.name == unit.religion }
.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.currentTile) }
if (unit.religion != unit.civInfo.religionManager.religion?.name || unit.religion == null)
return unit.disband() // No need to keep a unit we can't use, as it only blocks religion spreads of religions other that its own
val cityToProtect = unit.civInfo.cities.asSequence()
.filter { it.religion.getMajorityReligion()?.name == unit.religion }
.filter { isInquisitorInTheCity(it, unit) }
.maxByOrNull { it.population.population } // cities with most populations will be prioritized by the AI
val holyCity = unit.civInfo.religionManager.getHolyCity()
val cityToConvert = determineBestInquisitorCityToConvert(unit) // Also returns null if the inquisitor can't convert cities
val pressureDeficit =
if (cityToConvert == null) 0
else cityToConvert.religion.getPressureDeficit(unit.civInfo.religionManager.religion?.name)
var destination: TileInfo? = null
val citiesToProtect = unit.civInfo.cities.asSequence()
.filter { it.religion.getMajorityReligion() == unit.civInfo.religionManager.religion }
// We only look at cities that are not currently protected or are protected by us
.filter { !it.religion.isProtectedByInquisitor() || unit.getTile() in it.getCenterTile().getTilesInDistance(1) }
if (cityToProtect != null) {
destination = cityToProtect.getCenterTile().neighbors.asSequence()
.filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movement.canReach(it) }
}
if (destination == null) {
if (cityToConvert == null) return
destination = cityToConvert.getCenterTile().neighbors.asSequence()
.filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movement.canReach(it) }
// cities with most populations will be prioritized by the AI
val cityToProtect = citiesToProtect.maxByOrNull { it.population.population }
var destination: TileInfo?
destination = when {
cityToConvert != null
&& (cityToConvert == holyCity || pressureDeficit > Constants.aiPreferInquisitorOverMissionaryPressureDifference)
&& unit.canDoReligiousAction(Constants.removeHeresy) -> {
cityToConvert.getCenterTile()
}
cityToProtect != null && unit.hasUnique(UniqueType.PreventSpreadingReligion) -> {
if (holyCity != null && !holyCity.religion.isProtectedByInquisitor())
holyCity.getCenterTile()
else cityToProtect.getCenterTile()
}
cityToConvert != null -> cityToConvert.getCenterTile()
else -> null
}
if (destination == null) return
unit.movement.headTowards(destination)
if (cityToConvert != null && unit.currentTile.getCity() == destination.getCity()) {
doReligiousAction(unit, destination)
if (!unit.movement.canReach(destination)) {
destination = destination.neighbors.asSequence()
.filter { unit.movement.canMoveTo(it) || it == unit.getTile() }
.sortedBy { it.aerialDistanceTo(unit.currentTile) }
.firstOrNull { unit.movement.canReach(it) }
?: return
}
unit.movement.headTowards(destination)
if (cityToConvert != null && unit.getTile().getCity() == destination.getCity()) {
doReligiousAction(unit, destination)
}
}
private fun isInquisitorInTheCity(city: CityInfo, unit: MapUnit): Boolean {
if (!city.religion.isProtectedByInquisitor())
return false
private fun determineBestInquisitorCityToConvert(
unit: MapUnit,
): CityInfo? {
if (unit.religion != unit.civInfo.religionManager.religion?.name || !unit.canDoReligiousAction(Constants.removeHeresy))
return null
for (tile in city.getCenterTile().neighbors)
if (unit.currentTile == tile)
return true
return false
val holyCity = unit.civInfo.religionManager.getHolyCity()
if (holyCity != null && holyCity.religion.getMajorityReligion() != unit.civInfo.religionManager.religion!!)
return holyCity
return unit.civInfo.cities.asSequence()
.filter { it.religion.getMajorityReligion() != null }
.filter { it.religion.getMajorityReligion()!! != unit.civInfo.religionManager.religion }
// Don't go if it takes too long
.filter { it.getCenterTile().aerialDistanceTo(unit.currentTile) <= 20 }
.maxByOrNull { it.religion.getPressureDeficit(unit.civInfo.religionManager.religion?.name) }
}
fun automateFighter(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val enemyAirUnitsInRange = tilesInRange
@ -472,7 +506,12 @@ object SpecificUnitAutomation {
for (city in immediatelyReachableCitiesAndCarriers) {
if (city.getTilesInDistance(unit.getRange())
.any { BattleHelper.containsAttackableEnemy(it, MapUnitCombatant(unit)) }) {
.any {
BattleHelper.containsAttackableEnemy(
it,
MapUnitCombatant(unit)
)
}) {
unit.movement.moveToTile(city)
return true
}
@ -509,10 +548,10 @@ object SpecificUnitAutomation {
UnitActions.getEnhanceReligionAction(unit)()
}
private fun doReligiousAction(unit: MapUnit, destination: TileInfo){
val actionList: java.util.ArrayList<UnitAction> = ArrayList()
UnitActions.addActionsWithLimitedUses(unit, actionList, destination)
if (actionList.firstOrNull()?.action == null) return
actionList.first().action!!.invoke()
private fun doReligiousAction(unit: MapUnit, destination: TileInfo) {
val religiousActions = ArrayList<UnitAction>()
UnitActions.addActionsWithLimitedUses(unit, religiousActions, destination)
if (religiousActions.firstOrNull()?.action == null) return
religiousActions.first().action!!.invoke()
}
}

View File

@ -1,6 +1,8 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.unit
import com.unciv.Constants
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.battle.*
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.ReligionState
@ -160,18 +162,20 @@ object UnitAutomation {
//todo this now supports "Great General"-like mod units not combining 'aura' and citadel
// abilities, but not additional capabilities if automation finds no use for those two
if (unit.hasStrengthBonusInRadiusUnique && SpecificUnitAutomation.automateGreatGeneral(unit))
if (unit.hasStrengthBonusInRadiusUnique && SpecificUnitAutomation.automateGreatGeneral(
unit
)
)
return
if (unit.hasCitadelPlacementUnique && SpecificUnitAutomation.automateCitadelPlacer(unit))
return
if (unit.hasCitadelPlacementUnique || unit.hasStrengthBonusInRadiusUnique)
return SpecificUnitAutomation.automateGreatGeneralFallback(unit)
if (unit.getMatchingUniques(UniqueType.CanActionSeveralTimes).any{ it.params[0] == "Spread Religion" }
&& unit.civInfo.religionManager.maySpreadReligionAtAll(unit))
if (unit.civInfo.religionManager.maySpreadReligionAtAll(unit))
return SpecificUnitAutomation.automateMissionary(unit)
if (unit.hasUnique(UniqueType.PreventSpreadingReligion))
if (unit.hasUnique(UniqueType.PreventSpreadingReligion) || unit.canDoReligiousAction(Constants.removeHeresy))
return SpecificUnitAutomation.automateInquisitor(unit)
if (unit.hasUnique(UniqueType.ConstructImprovementConsumingUnit)
@ -365,9 +369,9 @@ object UnitAutomation {
unit.getMaxMovement() * CLOSE_ENEMY_TURNS_AWAY_LIMIT
)
var closeEnemies = BattleHelper.getAttackableEnemies(
unit,
unitDistanceToTiles,
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
unit,
unitDistanceToTiles,
tilesToCheck = unit.getTile().getTilesInDistance(CLOSE_ENEMY_TILES_AWAY_LIMIT).toList()
).filter {
// Ignore units that would 1-shot you if you attacked. Account for taking terrain damage after the fact.
BattleDamage.calculateDamageToAttacker(

View File

@ -1,10 +1,13 @@
package com.unciv.logic.automation
package com.unciv.logic.automation.unit
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.HexMath
import com.unciv.logic.automation.UnitAutomation.wander
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.automation.ThreatLevel
import com.unciv.logic.automation.unit.UnitAutomation.wander
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.BFS
@ -452,7 +455,9 @@ class WorkerAutomation(
ThreatLevel.VeryHigh -> 20
}
}
val enemyCivsIsCloseEnough = enemyCivs.filter { NextTurnAutomation.getMinDistanceBetweenCities(civInfo, it) <= threatMapping(it) }
val enemyCivsIsCloseEnough = enemyCivs.filter { NextTurnAutomation.getMinDistanceBetweenCities(
civInfo,
it) <= threatMapping(it) }
// no threat, let's not build fort
if (enemyCivsIsCloseEnough.isEmpty()) return false

View File

@ -3,7 +3,7 @@ package com.unciv.logic.battle
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.CivilizationInfo

View File

@ -4,7 +4,7 @@ import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.logic.automation.SpecificUnitAutomation // for Kdoc
import com.unciv.logic.automation.unit.SpecificUnitAutomation // for Kdoc
object GreatGeneralImplementation {

View File

@ -3,7 +3,7 @@ package com.unciv.logic.city
import com.unciv.UncivGame
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.automation.Automation
import com.unciv.logic.automation.ConstructionAutomation
import com.unciv.logic.automation.city.ConstructionAutomation
import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PopupAlert
@ -496,7 +496,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
}
/**
* Purchase a construction for gold
* Purchase a construction for gold (or another stat)
* called from NextTurnAutomation and the City UI
* Build / place the new item, deduct cost, and maintain queue.
*

View File

@ -280,10 +280,17 @@ class CityInfoReligionManager : IsPartOfGameInfoSerialization {
return addedPressure
}
fun isProtectedByInquisitor(): Boolean {
for (tile in cityInfo.getCenterTile().getTilesInDistance(1))
if (tile.civilianUnit?.hasUnique(UniqueType.PreventSpreadingReligion) == true)
return true
fun isProtectedByInquisitor(fromReligion: String? = null): Boolean {
for (tile in cityInfo.getCenterTile().getTilesInDistance(1)) {
for (unit in listOf(tile.civilianUnit, tile.militaryUnit)) {
if (unit?.religion != null
&& (fromReligion == null || unit.religion != fromReligion)
&& unit.hasUnique(UniqueType.PreventSpreadingReligion)
) {
return true
}
}
}
return false
}
@ -305,4 +312,8 @@ class CityInfoReligionManager : IsPartOfGameInfoSerialization {
return pressure.toInt()
}
fun getPressureDeficit(otherReligion: String?): Int {
return (getPressures()[getMajorityReligionName()] ?: 0) - (getPressures()[otherReligion] ?: 0)
}
}

View File

@ -28,17 +28,18 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons
fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious.
/** Only checks if it has the unique to be bought with this stat, not whether it is purchasable at all */
fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean {
if (stat == Stat.Production || stat == Stat.Happiness) return false
if (hasUnique(UniqueType.CannotBePurchased)) return false
if (stat == Stat.Gold) return !hasUnique(UniqueType.Unbuildable)
// Can be purchased with [Stat] [cityFilter]
if (getMatchingUniques(UniqueType.CanBePurchasedWithStat)
.any { cityInfo != null && it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1]) }
if (cityInfo != null && getMatchingUniques(UniqueType.CanBePurchasedWithStat)
.any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1]) }
) return true
// Can be purchased for [amount] [Stat] [cityFilter]
if (getMatchingUniques(UniqueType.CanBePurchasedForAmountStat)
.any { cityInfo != null && it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) }
if (cityInfo != null && getMatchingUniques(UniqueType.CanBePurchasedForAmountStat)
.any { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) }
) return true
return false
}

View File

@ -1,7 +1,7 @@
package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.civilization.diplomacy.*
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceSupplyList

View File

@ -7,8 +7,8 @@ import com.unciv.json.HashMapVector2
import com.unciv.logic.GameInfo
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.UncivShowableException
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.automation.WorkerAutomation
import com.unciv.logic.automation.civilization.NextTurnAutomation
import com.unciv.logic.automation.unit.WorkerAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.RuinsManager.RuinsManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags

View File

@ -1,6 +1,8 @@
package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.city.CityInfo
import com.unciv.logic.map.MapUnit
import com.unciv.models.Counter
import com.unciv.models.Religion
@ -150,6 +152,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
}
}
/** Calculates the amount of religions that can still be founded */
fun remainingFoundableReligions(): Int {
val foundedReligionsCount = civInfo.gameInfo.civilizations.count {
it.religionManager.religion != null && it.religionManager.religionState >= ReligionState.Religion
@ -328,10 +331,10 @@ class ReligionManager : IsPartOfGameInfoSerialization {
fun maySpreadReligionAtAll(missionary: MapUnit): Boolean {
if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion, no spreading
if (religion == null) return false // need a religion
if (religion == null) return false // Need a religion
if (religionState < ReligionState.Religion) return false // First found an actual religion
if (!civInfo.isMajorCiv()) return false // Only major civs
if (!missionary.canDoReligiousAction(Constants.spreadReligion)) return false
return true
}
@ -340,7 +343,7 @@ class ReligionManager : IsPartOfGameInfoSerialization {
if (missionary.getTile().getOwner() == null) return false
if (missionary.currentTile.owningCity?.religion?.getMajorityReligion()?.name == missionary.religion)
return false
if (missionary.getTile().getCity()!!.religion.isProtectedByInquisitor()) return false
if (missionary.getTile().getCity()!!.religion.isProtectedByInquisitor(religion!!.name)) return false
return true
}
@ -356,6 +359,11 @@ class ReligionManager : IsPartOfGameInfoSerialization {
.filter { it.matchesFilter(cityFilter, civInfo) }
.sumOf { it.religion.getFollowersOf(religion!!.name)!! }
}
fun getHolyCity(): CityInfo? {
if (religion == null) return null
return civInfo.gameInfo.getCities().firstOrNull { it.religion.religionThisIsTheHolyCityOf == religion!!.name }
}
}
enum class ReligionState : IsPartOfGameInfoSerialization {

View File

@ -4,8 +4,8 @@ import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.automation.WorkerAutomation
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.automation.unit.WorkerAutomation
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityInfo
@ -259,8 +259,8 @@ class MapUnit : IsPartOfGameInfoSerialization {
fun getTile(): TileInfo = currentTile
// This SHOULD NOT be a HashSet, because if it is, then promotions with the same text (e.g. barrage I, barrage II)
// will not get counted twice!
// This SHOULD NOT be a HashSet, because if it is, then e.g. promotions with the same uniques
// (e.g. barrage I, barrage II) will not get counted twice!
@Transient
private var tempUniques = ArrayList<Unique>()

View File

@ -274,7 +274,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) {
// If the tile is far away, we need to build a path how to get there, and then take the first step
if (!distanceToTiles.containsKey(finalDestination))
return getShortestPath(finalDestination).firstOrNull()
?: throw UnreachableDestinationException("$unit ${unit.currentTile.position} cannot reach $finalDestination")
?: throw UnreachableDestinationException("$unit ${unit.currentTile} cannot reach $finalDestination")
// we should be able to get there this turn
if (canMoveTo(finalDestination))

View File

@ -4,7 +4,9 @@ import com.unciv.logic.GameInfo
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stat
/** Data object for Religions */
class Religion() : INamed, IsPartOfGameInfoSerialization {
@ -19,6 +21,11 @@ class Religion() : INamed, IsPartOfGameInfoSerialization {
@Transient
lateinit var gameInfo: GameInfo
@delegate:Transient
val buildingsPurchasableByBeliefs by lazy {
unlockedBuildingsPurchasable()
}
constructor(name: String, gameInfo: GameInfo, foundingCivName: String) : this() {
this.name = name
this.foundingCivName = foundingCivName
@ -94,4 +101,12 @@ class Religion() : INamed, IsPartOfGameInfoSerialization {
fun isEnhancedReligion() = getBeliefs(BeliefType.Enhancer).any()
fun getFounder() = gameInfo.civilizations.first { it.civName == foundingCivName }
private fun unlockedBuildingsPurchasable(): List<String> {
return getAllBeliefsOrdered().flatMap { belief ->
belief.getMatchingUniques(UniqueType.BuyBuildingsWithStat).map { it.params[0] } +
belief.getMatchingUniques(UniqueType.BuyBuildingsForAmountStat).map { it.params[0] } +
belief.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost).map { it.params[0] }
}.toList()
}
}

View File

@ -486,8 +486,8 @@ enum class UniqueParameterType(
},
/** For untyped "Can [] [] times" unique */
Action("action", Constants.spreadReligionAbilityCount, "An action that a unit can perform. Currently, there are only two actions part of this: 'Spread Religion' and 'Remove Foreign religions from your own cities'", "Religious Action Filters") {
private val knownValues = setOf(Constants.spreadReligionAbilityCount, Constants.removeHeresyAbilityCount)
Action("action", Constants.spreadReligion, "An action that a unit can perform. Currently, there are only two actions part of this: 'Spread Religion' and 'Remove Foreign religions from your own cities'", "Religious Action Filters") {
private val knownValues = setOf(Constants.spreadReligion, Constants.removeHeresy)
override fun getErrorSeverity(
parameterText: String,
ruleset: Ruleset

View File

@ -672,6 +672,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
OneTimeGainPantheon("Gain enough Faith for a Pantheon", UniqueTarget.Ruins),
OneTimeGainProphet("Gain enough Faith for [amount]% of a Great Prophet", UniqueTarget.Ruins),
// todo: The "up to [All]" used in vanilla json is not nice to read. Split?
// Or just reword it without the 'up to', so it reads "Reveal [amount/'all'] [tileFilter] tiles within [amount] tiles"
OneTimeRevealSpecificMapTiles("Reveal up to [amount/'all'] [tileFilter] within a [amount] tile radius", UniqueTarget.Ruins),
OneTimeRevealCrudeMap("From a randomly chosen tile [amount] tiles away from the ruins, reveal tiles up to [amount] tiles away with [amount]% chance", UniqueTarget.Ruins),
OneTimeTriggerVoting("Triggers voting for the Diplomatic Victory", UniqueTarget.Triggerable), // used in Building

View File

@ -17,8 +17,8 @@ import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.badlogic.gdx.utils.Align
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.automation.BattleHelper
import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.automation.unit.BattleHelper
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityInfo

View File

@ -5,8 +5,8 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.automation.BattleHelper
import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.automation.unit.BattleHelper
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.BattleDamage
import com.unciv.logic.battle.CityCombatant

View File

@ -2,8 +2,8 @@ package com.unciv.ui.worldscreen.unit
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.automation.UnitAutomation
import com.unciv.logic.automation.WorkerAutomation
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.automation.unit.WorkerAutomation
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationIcon
@ -627,8 +627,8 @@ object UnitActions {
if (!unit.abilityUsesLeft.containsKey(action)) continue
if (unit.abilityUsesLeft[action]!! <= 0) continue
when (action) {
Constants.spreadReligionAbilityCount -> addSpreadReligionActions(unit, actionList, city)
Constants.removeHeresyAbilityCount -> addRemoveHeresyActions(unit, actionList, city)
Constants.spreadReligion -> addSpreadReligionActions(unit, actionList, city)
Constants.removeHeresy -> addRemoveHeresyActions(unit, actionList, city)
}
}
}
@ -641,15 +641,7 @@ object UnitActions {
}
fun addSpreadReligionActions(unit: MapUnit, actionList: ArrayList<UnitAction>, city: CityInfo) {
if (!unit.civInfo.gameInfo.isReligionEnabled()) return
val blockedByInquisitor =
city.getCenterTile()
.getTilesInDistance(1)
.flatMap { it.getUnits() }
.any {
it.hasUnique(UniqueType.PreventSpreadingReligion)
&& it.religion != unit.religion
}
if (!unit.civInfo.religionManager.maySpreadReligionAtAll(unit)) return
actionList += UnitAction(UnitActionType.SpreadReligion,
title = "Spread [${unit.getReligionDisplayName()!!}]",
action = {
@ -661,8 +653,8 @@ object UnitActions {
if (unit.hasUnique(UniqueType.RemoveOtherReligions))
city.religion.removeAllPressuresExceptFor(unit.religion!!)
unit.currentMovement = 0f
useActionWithLimitedUses(unit, Constants.spreadReligionAbilityCount)
}.takeIf { unit.currentMovement > 0 && !blockedByInquisitor }
useActionWithLimitedUses(unit, Constants.spreadReligion)
}.takeIf { unit.currentMovement > 0 && unit.civInfo.religionManager.maySpreadReligionNow(unit) }
)
}
@ -677,7 +669,7 @@ object UnitActions {
action = {
city.religion.removeAllPressuresExceptFor(unit.religion!!)
unit.currentMovement = 0f
useActionWithLimitedUses(unit, Constants.removeHeresyAbilityCount)
useActionWithLimitedUses(unit, Constants.removeHeresy)
}.takeIf { unit.currentMovement > 0f }
)
}

View File

@ -169,14 +169,14 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){
unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion())
}
if (unit.canDoReligiousAction(Constants.spreadReligionAbilityCount)) {
if (unit.canDoReligiousAction(Constants.spreadReligion)) {
unitDescriptionTable.add(ImageGetter.getStatIcon("Faith")).size(20f)
unitDescriptionTable.add(unit.getActionString(Constants.spreadReligionAbilityCount))
unitDescriptionTable.add(unit.getActionString(Constants.spreadReligion))
}
if (unit.canDoReligiousAction(Constants.removeHeresyAbilityCount)) {
if (unit.canDoReligiousAction(Constants.removeHeresy)) {
unitDescriptionTable.add(ImageGetter.getImage("OtherIcons/Remove Heresy")).size(20f)
unitDescriptionTable.add(unit.getActionString(Constants.removeHeresyAbilityCount))
unitDescriptionTable.add(unit.getActionString(Constants.removeHeresy))
}
if (unit.baseUnit.religiousStrength > 0) {