AI for Inquisitor and Missionary (#5590)

* First pull request to add missionaries and inquisitors

* First pull request to add missionaries and inquisitors

* First pull request to add missionaries and inquisitors

* First pull request to add missionaries and inquisitors

* Ai choice

* some fixes

* some fixes

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* adding a system for the ai to buy with faith

* finally done

* finally done

* some fixes

* some fixes

* some fixes

* some fixes

* some fixes

* some fixes

* some fixes
This commit is contained in:
Interdice
2021-12-12 21:27:09 +11:00
committed by GitHub
parent 0c1509bfe8
commit 3f6fe234b3
5 changed files with 170 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package com.unciv.logic.automation
import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.city.PerpetualConstruction
import com.unciv.logic.civilization.CityAction
import com.unciv.logic.civilization.NotificationIcon
@ -10,6 +11,7 @@ import com.unciv.logic.map.BFS
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.VictoryType
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.min
@ -39,12 +41,15 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
val relativeCostEffectiveness = ArrayList<ConstructionChoice>()
private val faithConstruction = arrayListOf<BaseUnit>()
data class ConstructionChoice(val choice:String, var choiceModifier:Float,val remainingWork:Int)
fun addChoice(choices:ArrayList<ConstructionChoice>, choice:String, choiceModifier: Float){
choices.add(ConstructionChoice(choice,choiceModifier,cityConstructions.getRemainingWork(choice)))
}
fun chooseNextConstruction() {
if (!UncivGame.Current.settings.autoAssignCityProduction
&& civInfo.playerType == PlayerType.Human && !cityInfo.isPuppet
@ -62,6 +67,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
addCultureBuildingChoice()
addSpaceshipPartChoice()
addOtherBuildingChoice()
addReligousUnit()
if (!cityInfo.isPuppet) {
addWondersChoice()
@ -94,14 +100,24 @@ 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.asSequence()
.filterNotNull()
.filter { it.getStatBuyCost(cityInfo, stat = Stat.Faith)!! <= civInfo.religionManager.storedFaith }
.firstOrNull() ?: return
cityConstructions.purchaseConstruction(chosenItem.name, -1, false, stat=Stat.Faith)
}
private fun addMilitaryUnitChoice() {
if (!isAtWar && !cityIsOverAverageProduction) return // don't make any military units here. Infrastructure first!
if ((!isAtWar && civInfo.statsForNextTurn.gold > 0 && militaryUnits < max(5, cities * 2))
|| (isAtWar && civInfo.gold > -50)) {
val militaryUnit = Automation.chooseMilitaryUnit(cityInfo)
if (militaryUnit == null) return
val militaryUnit = Automation.chooseMilitaryUnit(cityInfo) ?: return
val unitsToCitiesRatio = cities.toFloat() / (militaryUnits + 1)
// most buildings and civ units contribute the the civ's growth, military units are anti-growth
var modifier = sqrt(unitsToCitiesRatio) / 2
@ -322,4 +338,48 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
}
}
private fun addReligousUnit(){
var modifier = 0f
val missionary = cityInfo.getRuleset().units.values.asSequence()
.firstOrNull { it -> it.canBePurchasedWithStat(cityInfo, Stat.Faith)
&& it.getMatchingUniques("Can [] [] times").any { it.params[0] == "Spread Religion"} }
val inquisitor = cityInfo.getRuleset().units.values.asSequence()
.firstOrNull { it.canBePurchasedWithStat(cityInfo, Stat.Faith)
&& it.hasUnique("Prevents spreading of religion to the city it is next to") }
// 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 (preferredVictoryType == VictoryType.Domination) return
if (civInfo.religionManager.religion?.name == null) return
if (preferredVictoryType == VictoryType.Cultural) modifier += 1
if (isAtWar) modifier -= 0.5f
if (cityInfo.religion.getMajorityReligion()?.name != civInfo.religionManager.religion?.name)
return // you don't want to build units of opposing religions.
val citiesNotFollowingOurReligion = civInfo.cities.asSequence()
.filterNot { it.religion.getMajorityReligion()?.name == civInfo.religionManager.religion!!.name }
val buildInqusitor = 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 > buildInqusitor && missionary != null) faithConstruction.add(missionary)
else if(inquisitor != null) faithConstruction.add(inquisitor)
}
}

View File

@ -1,12 +1,19 @@
package com.unciv.logic.automation
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.logic.battle.Battle
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.ReligionManager
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileResource
import com.unciv.models.stats.Stat
@ -280,6 +287,81 @@ object SpecificUnitAutomation {
}
}
fun automateMissionary(unit: MapUnit){
if (unit.religion != unit.civInfo.religionManager.religion?.name)
return unit.destroy()
val cities = unit.civInfo.gameInfo.getCities().asSequence()
.filter { it.religion.getMajorityReligion()?.name != unit.getReligionDisplayName() }
.filterNot { it.civInfo.isAtWarWith(unit.civInfo) }
.minByOrNull { it.getCenterTile().aerialDistanceTo(unit.currentTile) } ?: return
val destination = cities.getTiles().asSequence()
.filterNot { unit.getTile().owningCity == it.owningCity } // to prevent the ai from moving around randomly
.filter { unit.movement.canMoveTo(it) }
.minByOrNull { it.aerialDistanceTo(unit.currentTile) }
if (destination != null) {
unit.movement.headTowards(destination)
}
if (unit.currentTile.owningCity?.religion?.getMajorityReligion()?.name != unit.religion)
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) }
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
var destination: TileInfo? = null
if (cityToProtect != null){
destination = cityToProtect.getCenterTile().neighbors.asSequence()
.filterNot { unit.getTile().owningCity == it.owningCity } // to prevent the ai from moving around randomly
.filter { unit.movement.canMoveTo(it) }
.minByOrNull { it.aerialDistanceTo(unit.currentTile) }
}
if (destination == null){
if (cityToConvert == null) return
destination = cityToConvert.getCenterTile().neighbors.asSequence()
.filterNot { unit.getTile().owningCity == it.owningCity } // to prevent the ai from moving around randomly
.filter { unit.movement.canMoveTo(it) }
.minByOrNull { it.aerialDistanceTo(unit.currentTile) }
}
if (destination != null)
unit.movement.headTowards(destination)
if (cityToConvert != null && unit.currentTile.getCity() == destination!!.getCity()){
doReligiousAction(unit, destination)
}
}
private fun isInquisitorInTheCity(city: CityInfo, unit: MapUnit): Boolean {
if (!city.religion.isProtectedByInquisitor())
return false
for (tile in city.getCenterTile().neighbors)
if (unit.currentTile == tile)
return true
return false
}
fun automateFighter(unit: MapUnit) {
val tilesInRange = unit.currentTile.getTilesInDistance(unit.getRange())
val enemyAirUnitsInRange = tilesInRange
@ -420,4 +502,11 @@ 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()
}
}

View File

@ -159,6 +159,14 @@ object UnitAutomation {
if (unit.hasUnique("Can construct []"))
return SpecificUnitAutomation.automateImprovementPlacer(unit) // includes great people plus moddable units
if (unit.getMatchingUniques("Can [] [] times").any{ it.params[0] == "Spread Religion" })
return SpecificUnitAutomation.automateMissionary(unit)
if (unit.hasUnique("Prevents spreading of religion to the city it is next to"))
return SpecificUnitAutomation.automateInquisitor(unit)
// ToDo: automation of great people skills (may speed up construction, provides a science boost, etc.)
return // The AI doesn't know how to handle unknown civilian units

View File

@ -284,6 +284,14 @@ class CityInfoReligionManager {
}
return addedPressure
}
fun isProtectedByInquisitor(): Boolean {
for (tile in cityInfo.getCenterTile().neighbors)
if (tile.civilianUnit?.hasUnique("Prevents spreading of religion to the city it is next to") == true)
return true
if (cityInfo.getCenterTile().civilianUnit?.name == "Inquisitor") return true
return false
}
private fun pressureAmountToAdjacentCities(pressuredCity: CityInfo): Int {
var pressure = pressureFromAdjacentCities.toFloat()

View File

@ -528,7 +528,8 @@ object UnitActions {
}
}
private fun addActionsWithLimitedUses(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
fun addActionsWithLimitedUses(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
val actionsToAdd = unit.religiousActionsUnitCanDo()
if (actionsToAdd.none()) return
if (unit.religion == null || unit.civInfo.gameInfo.religions[unit.religion]!!.isPantheon()) return
@ -551,7 +552,7 @@ object UnitActions {
}
}
private fun addSpreadReligionActions(unit: MapUnit, actionList: ArrayList<UnitAction>, city: CityInfo) {
fun addSpreadReligionActions(unit: MapUnit, actionList: ArrayList<UnitAction>, city: CityInfo) {
if (!unit.civInfo.gameInfo.isReligionEnabled()) return
val blockedByInquisitor =
city.getCenterTile()