diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 488fc5e9ab..fcbfc9efe1 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -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 { + val chosenBeliefs = hashSetOf() + // 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 = 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 { val tradeLogic = TradeLogic(civInfo, otherCivInfo) diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 291618b9c5..41fe0fe6cc 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -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)() + } } diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 8cd96d70ad..cd7e0e2638 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -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) } diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index 58abb684e5..a04d25c023 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -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) diff --git a/core/src/com/unciv/logic/civilization/ReligionManager.kt b/core/src/com/unciv/logic/civilization/ReligionManager.kt index eb9b2dc4cd..5740eed74a 100644 --- a/core/src/com/unciv/logic/civilization/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/ReligionManager.kt @@ -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) } diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index 322724b280..f678dadeb0 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -289,7 +289,6 @@ class UnitMovementAlgorithms(val unit:MapUnit) { .filter { unit.movement.canParadropOn(it) } else -> unit.movement.getDistanceToTiles().keys.asSequence() - } } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 31a5960f72..cbd3b5b04b 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -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) { 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, 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