AI will now found & enhance religions -- minor improvement to civilian AI (#5031)

* AI will now found & enhance religions -- improvements to civilian AI

* This is a better order imo

* Implemented requested changes
This commit is contained in:
Xander Lenstra 2021-08-30 20:41:18 +02:00 committed by GitHub
parent 06c7f049b7
commit 1771604a4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 198 additions and 53 deletions

View File

@ -13,6 +13,8 @@ import com.unciv.logic.map.BFS
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
import com.unciv.logic.trade.*
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tech.Technology
@ -20,6 +22,7 @@ import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.BeliefContainer
import kotlin.math.min
object NextTurnAutomation {
@ -40,8 +43,7 @@ object NextTurnAutomation {
offerResearchAgreement(civInfo)
exchangeLuxuries(civInfo)
issueRequests(civInfo)
adoptPolicy(civInfo) //todo can take a second - why?
choosePantheon(civInfo)
adoptPolicy(civInfo) // todo can take a second - why?
} else {
getFreeTechForCityStates(civInfo)
updateDiplomaticRelationshipForCityStates(civInfo)
@ -55,10 +57,15 @@ object NextTurnAutomation {
bullyCityStates(civInfo)
}
automateUnits(civInfo) // this is the most expensive part
if (civInfo.isMajorCiv()) {
// Can only be done now, as the prophet first has to decide to found/enhance a religion
chooseReligiousBeliefs(civInfo)
}
reassignWorkedTiles(civInfo) // second most expensive
trainSettler(civInfo)
tryVoteForDiplomaticVictory(civInfo)
}
private fun respondToTradeRequests(civInfo: CivilizationInfo) {
@ -280,6 +287,12 @@ object NextTurnAutomation {
}
}
private fun chooseReligiousBeliefs(civInfo: CivilizationInfo) {
choosePantheon(civInfo)
foundReligion(civInfo)
enhanceReligion(civInfo)
}
private fun choosePantheon(civInfo: CivilizationInfo) {
if (!civInfo.religionManager.canFoundPantheon()) return
// So looking through the source code of the base game available online,
@ -288,15 +301,71 @@ object NextTurnAutomation {
// line 4426 through 4870.
// This is way too much work for now, so I'll just choose a random pantheon instead.
// Should probably be changed later, but it works for now.
// If this is omitted, the AI will never choose a religion,
// instead automatically choosing the same one as the player,
// which is not good.
val availablePantheons = civInfo.gameInfo.ruleSet.beliefs.values
.filter { civInfo.religionManager.isPickablePantheonBelief(it) }
if (availablePantheons.isEmpty()) return // panic!
val chosenPantheon = availablePantheons.random() // Why calculate stuff?
civInfo.religionManager.choosePantheonBelief(chosenPantheon)
}
private fun foundReligion(civInfo: CivilizationInfo) {
println("Founding check?")
if (civInfo.religionManager.religionState != ReligionState.FoundingReligion) return
println("Founding check!!")
val religionIcon = civInfo.gameInfo.ruleSet.religions
.filterNot { civInfo.gameInfo.religions.values.map { religion -> religion.iconName }.contains(it) }
.randomOrNull()
?: return // Wait what? How did we pass the checking when using a great prophet but not this?
println("Icon has been chosen")
val chosenBeliefs = chooseBeliefs(civInfo, civInfo.religionManager.getBeliefsToChooseAtFounding()).toList()
println("Beliefs have been chosen")
civInfo.religionManager.chooseBeliefs(religionIcon, religionIcon, chosenBeliefs)
}
private fun enhanceReligion(civInfo: CivilizationInfo) {
civInfo.religionManager.chooseBeliefs(
null,
null,
chooseBeliefs(civInfo, civInfo.religionManager.getBeliefsToChooseAtEnhancing()).toList()
)
}
private fun chooseBeliefs(civInfo: CivilizationInfo, beliefContainer: BeliefContainer): HashSet<Belief> {
val chosenBeliefs = hashSetOf<Belief>()
// The 'continues' should never be reached, but just in case I'd rather have AI have a
// belief less than make the game crash. The 'continue's should only be reached whenever
// there are not enough beliefs to choose, but there should be, as otherwise we could
// not have used a great prophet to found/enhance our religion.
for (counter in 0 until beliefContainer.pantheonBeliefCount)
chosenBeliefs.add(
chooseBeliefOfType(civInfo, BeliefType.Pantheon, chosenBeliefs) ?: continue
)
for (counter in 0 until beliefContainer.followerBeliefCount)
chosenBeliefs.add(
chooseBeliefOfType(civInfo, BeliefType.Follower, chosenBeliefs) ?: continue
)
for (counter in 0 until beliefContainer.founderBeliefCount)
chosenBeliefs.add(
chooseBeliefOfType(civInfo, BeliefType.Founder, chosenBeliefs) ?: continue
)
for (counter in 0 until beliefContainer.enhancerBeliefCount)
chosenBeliefs.add(
chooseBeliefOfType(civInfo, BeliefType.Enhancer, chosenBeliefs) ?: continue
)
return chosenBeliefs
}
private fun chooseBeliefOfType(civInfo: CivilizationInfo, beliefType: BeliefType, additionalBeliefsToExclude: HashSet<Belief> = hashSetOf()): Belief? {
return civInfo.gameInfo.ruleSet.beliefs
.filter {
it.value.type == beliefType
&& !additionalBeliefsToExclude.contains(it.value)
&& !civInfo.gameInfo.religions.values
.flatMap { religion -> religion.getBeliefs(beliefType) }.contains(it.value)
}
.map { it.value }
.randomOrNull() // ToDo: Better algorithm
}
private fun potentialLuxuryTrades(civInfo: CivilizationInfo, otherCivInfo: CivilizationInfo): ArrayList<Trade> {
val tradeLogic = TradeLogic(civInfo, otherCivInfo)

View File

@ -369,4 +369,27 @@ object SpecificUnitAutomation {
}
return false
}
fun foundReligion(unit: MapUnit) {
val cityToFoundReligionAt = unit.civInfo.cities.first { !it.isHolyCity() }
if (unit.getTile() != cityToFoundReligionAt.getCenterTile()) {
unit.movement.headTowards(cityToFoundReligionAt.getCenterTile())
return
}
UnitActions.getFoundReligionAction(unit)()
}
fun enhanceReligion(unit: MapUnit) {
// Try go to a nearby city
if (!unit.getTile().isCityCenter())
UnitAutomation.tryEnterOwnClosestCity(unit)
// If we were unable to go there this turn, unable to do anything else
if (!unit.getTile().isCityCenter())
return
UnitActions.getEnhanceReligionAction(unit)()
}
}

View File

@ -3,6 +3,7 @@ package com.unciv.logic.automation
import com.unciv.Constants
import com.unciv.logic.battle.*
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.ReligionState
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo
@ -87,12 +88,26 @@ object UnitAutomation {
throw IllegalStateException("Barbarians is not allowed here.")
if (unit.isCivilian()) {
if (tryRunAwayIfNeccessary(unit)) return
if (unit.hasUnique(Constants.settlerUnique))
return SpecificUnitAutomation.automateSettlerActions(unit)
if (unit.hasUniqueToBuildImprovements)
return WorkerAutomation.automateWorkerAction(unit)
if (unit.hasUnique("May found a religion")
&& unit.civInfo.religionManager.religionState < ReligionState.Religion
&& unit.civInfo.religionManager.mayFoundReligionAtAll(unit)
)
return SpecificUnitAutomation.foundReligion(unit)
if (unit.hasUnique("May enhance a religion")
&& unit.civInfo.religionManager.religionState < ReligionState.EnhancedReligion
&& unit.civInfo.religionManager.mayEnhanceReligionAtAll(unit)
)
return SpecificUnitAutomation.enhanceReligion(unit)
if (unit.hasUnique(Constants.workBoatsUnique))
return SpecificUnitAutomation.automateWorkBoats(unit)
@ -102,6 +117,8 @@ object UnitAutomation {
if (unit.hasUnique("Can construct []"))
return SpecificUnitAutomation.automateImprovementPlacer(unit) // includes great people plus moddable units
// 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
}
@ -275,12 +292,12 @@ object UnitAutomation {
private fun tryAccompanySettlerOrGreatPerson(unit: MapUnit): Boolean {
val settlerOrGreatPersonToAccompany = unit.civInfo.getCivUnits()
.firstOrNull {
val tile = it.currentTile
it.isCivilian() &&
(it.hasUnique(Constants.settlerUnique) || unit.isGreatPerson())
&& tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile)
} ?: return false
.firstOrNull {
val tile = it.currentTile
it.isCivilian() &&
(it.hasUnique(Constants.settlerUnique) || unit.isGreatPerson())
&& tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile)
} ?: return false
unit.movement.headTowards(settlerOrGreatPersonToAccompany.currentTile)
return true
}
@ -305,7 +322,6 @@ object UnitAutomation {
return false
}
private fun tryHeadTowardsEnemyCity(unit: MapUnit): Boolean {
if (unit.civInfo.cities.isEmpty()) return false
@ -375,6 +391,18 @@ object UnitAutomation {
return true
}
fun tryEnterOwnClosestCity(unit: MapUnit): Boolean {
val closestCity = unit.civInfo.cities
.asSequence()
.sortedBy { it.getCenterTile().aerialDistanceTo(unit.getTile()) }
.firstOrNull { unit.movement.canReach(it.getCenterTile()) }
if (closestCity == null) return false // Panic!
unit.movement.headTowards(closestCity.getCenterTile())
return true
}
fun tryBombardEnemy(city: CityInfo): Boolean {
if (!city.canBombard()) return false
@ -469,16 +497,43 @@ object UnitAutomation {
unit.civInfo.addNotification("[${unit.displayName()}] finished exploring.", unit.currentTile.position, unit.name, "OtherIcons/Sleep")
unit.action = null
}
/** Returns whether the civilian spends its turn hiding and not moving */
fun tryRunAwayIfNeccessary(unit: MapUnit): Boolean {
// This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
// Cheaper than determining which enemies could attack us next turn
//todo - stay when we're stacked with a good military unit???
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { containsEnemyMilitaryUnit(unit, it) }
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()) {
if (unit.getTile().militaryUnit == null && !unit.getTile().isCityCenter())
runAway(unit)
return true
}
return false
}
fun runAway(unit: MapUnit) {
val reachableTiles = unit.movement.getDistanceToTiles()
val enterableCity = reachableTiles.keys.firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) }
val enterableCity = reachableTiles.keys
.firstOrNull { it.isCityCenter() && unit.movement.canMoveTo(it) }
if (enterableCity != null) {
unit.movement.moveToTile(enterableCity)
return
}
val tileFurthestFromEnemy = reachableTiles.keys.filter { unit.movement.canMoveTo(it) }
val defensiveUnit = reachableTiles.keys
.firstOrNull {
it.militaryUnit != null && it.militaryUnit!!.civInfo == unit.civInfo && it.civilianUnit == null
}
if (defensiveUnit != null) {
unit.movement.moveToTile(defensiveUnit)
return
}
val tileFurthestFromEnemy = reachableTiles.keys
.filter { unit.movement.canMoveTo(it) }
.maxByOrNull { countDistanceToClosestEnemy(unit, it) }
?: return // can't move anywhere!
unit.movement.moveToTile(tileFurthestFromEnemy)
@ -492,7 +547,8 @@ object UnitAutomation {
return 4
}
fun containsEnemyMilitaryUnit(unit: MapUnit, tileInfo: TileInfo) = tileInfo.militaryUnit != null
&& tileInfo.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo)
private fun containsEnemyMilitaryUnit(unit: MapUnit, tileInfo: TileInfo) =
tileInfo.militaryUnit != null
&& tileInfo.militaryUnit!!.civInfo.isAtWarWith(unit.civInfo)
}

View File

@ -124,19 +124,6 @@ class WorkerAutomation(
* Automate one Worker - decide what to do and where, move, start or continue work.
*/
fun automateWorkerAction(unit: MapUnit) {
// This is a little 'Bugblatter Beast of Traal': Run if we can attack an enemy
// Cheaper than determining which enemies could attack us next turn
//todo - stay when we're stacked with a good military unit???
val enemyUnitsInWalkingDistance = unit.movement.getDistanceToTiles().keys
.filter { UnitAutomation.containsEnemyMilitaryUnit(unit, it) }
if (enemyUnitsInWalkingDistance.isNotEmpty() && !unit.baseUnit.isMilitary()) {
if (WorkerAutomationConst.consoleOutput)
println("WorkerAutomation: ${unit.label()} -> run away")
return UnitAutomation.runAway(unit)
}
val currentTile = unit.getTile()
val tileToWork = findTileToWork(unit)

View File

@ -131,17 +131,6 @@ class ReligionManager {
greatProphetsEarned += 1
}
}
fun useGreatProphet(prophet: MapUnit) {
if (religionState <= ReligionState.Pantheon) {
if (!mayFoundReligionNow(prophet)) return // How did you do this?
religionState = ReligionState.FoundingReligion
foundingCityId = prophet.getTile().getCity()!!.id
} else if (religionState == ReligionState.Religion) {
if (!mayEnhanceReligionNow(prophet)) return
religionState = ReligionState.EnhancingReligion
}
}
fun mayFoundReligionAtAll(prophet: MapUnit): Boolean {
if (religionState >= ReligionState.Religion) return false // Already created a major religion
@ -173,8 +162,16 @@ class ReligionManager {
fun mayFoundReligionNow(prophet: MapUnit): Boolean {
if (!mayFoundReligionAtAll(prophet)) return false
if (!prophet.getTile().isCityCenter()) return false
if (prophet.getTile().getCity()!!.isHolyCity()) return false
// No double holy cities. Not sure if these were allowed in the base game
return true
}
fun useProphetForFoundingReligion(prophet: MapUnit) {
if (!mayFoundReligionNow(prophet)) return // How did you do this?
religionState = ReligionState.FoundingReligion
civInfo.religionManager.foundingCityId = prophet.getTile().getCity()!!.id
}
fun getBeliefsToChooseAtFounding(): BeliefContainer {
if (religionState == ReligionState.None)
@ -248,6 +245,11 @@ class ReligionManager {
return true
}
fun useProphetForEnhancingReligion(prophet: MapUnit) {
if (!mayFoundReligionNow(prophet)) return // How did you do this?
religionState = ReligionState.EnhancingReligion
}
fun getBeliefsToChooseAtEnhancing(): BeliefContainer {
return BeliefContainer(followerBeliefCount = 1, enhancerBeliefCount = 1)
}

View File

@ -289,7 +289,6 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
.filter { unit.movement.canParadropOn(it) }
else ->
unit.movement.getDistanceToTiles().keys.asSequence()
}
}

View File

@ -8,6 +8,7 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.civilization.ReligionState
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.map.MapUnit
@ -486,26 +487,34 @@ object UnitActions {
if (!unit.hasUnique("May found a religion")) return
if (!unit.civInfo.religionManager.mayFoundReligionAtAll(unit)) return
actionList += UnitAction(UnitActionType.FoundReligion,
action = {
addStatsPerGreatPersonUsage(unit)
unit.civInfo.religionManager.useGreatProphet(unit)
unit.destroy()
}.takeIf { unit.civInfo.religionManager.mayFoundReligionNow(unit) }
action = getFoundReligionAction(unit).takeIf { unit.civInfo.religionManager.mayFoundReligionNow(unit) }
)
}
fun getFoundReligionAction(unit: MapUnit): () -> Unit {
return {
addStatsPerGreatPersonUsage(unit)
unit.civInfo.religionManager.useProphetForFoundingReligion(unit)
unit.destroy()
}
}
private fun addEnhanceReligionAction(unit: MapUnit, actionList: ArrayList<UnitAction>) {
if (!unit.hasUnique("May enhance a religion")) return
if (!unit.civInfo.religionManager.mayEnhanceReligionAtAll(unit)) return
actionList += UnitAction(UnitActionType.EnhanceReligion,
title = "Enhance [${unit.civInfo.religionManager.religion!!.name}]",
action = {
addStatsPerGreatPersonUsage(unit)
unit.civInfo.religionManager.useGreatProphet(unit)
unit.destroy()
}.takeIf { unit.civInfo.religionManager.mayEnhanceReligionNow(unit) }
action = getEnhanceReligionAction(unit).takeIf { unit.civInfo.religionManager.mayEnhanceReligionNow(unit) }
)
}
fun getEnhanceReligionAction(unit: MapUnit): () -> Unit {
return {
addStatsPerGreatPersonUsage(unit)
unit.civInfo.religionManager.useProphetForEnhancingReligion(unit)
unit.destroy()
}
}
private fun addActionsWithLimitedUses(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
val actionsToAdd = unit.religiousActionsUnitCanDo()
@ -650,7 +659,7 @@ object UnitActions {
otherCiv.addNotification("[${unit.civInfo}] has stolen your territory!", unit.currentTile.position, unit.civInfo.civName, NotificationIcon.War)
}
private fun addStatsPerGreatPersonUsage(unit: MapUnit) {
fun addStatsPerGreatPersonUsage(unit: MapUnit) {
if (!unit.isGreatPerson()) return
val civInfo = unit.civInfo