mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 13:27:22 -04:00
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:
parent
06c7f049b7
commit
1771604a4a
@ -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)
|
||||
|
@ -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)()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -289,7 +289,6 @@ class UnitMovementAlgorithms(val unit:MapUnit) {
|
||||
.filter { unit.movement.canParadropOn(it) }
|
||||
else ->
|
||||
unit.movement.getDistanceToTiles().keys.asSequence()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user