mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-13 17:28:57 +07:00
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:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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> {
|
@ -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
|
||||
}
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
@ -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(
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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>()
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user