diff --git a/android/assets/jsons/Civ V - Vanilla/Beliefs.json b/android/assets/jsons/Civ V - Vanilla/Beliefs.json index 3def03c3e8..22f4dd87d9 100644 --- a/android/assets/jsons/Civ V - Vanilla/Beliefs.json +++ b/android/assets/jsons/Civ V - Vanilla/Beliefs.json @@ -46,7 +46,7 @@ "name": "God of War", "type": "Pantheon", "uniques": ["Earn [50]% of [Military] unit's [Strength] as [Faith] when killed within 4 tiles of a city following this religion"] - } + }, { "name": "Goddess of Festivals", "type": "Pantheon", @@ -82,6 +82,8 @@ //"uniques": ["[+15]% Production when constructing [{Ancient Era} {Wonders}]", // "[+15]% Production when constructing [{Classical Era} {Wonders}]"] // For now this feels like overkill, but I'll leave this here for the future + + // Alternatively, we could approximate this with "[+15]% Production when constructing [Wonders] " }, { "name": "One with Nature", @@ -248,7 +250,7 @@ { "name": "Defender of the Faith", "type": "Enhancer", - "uniques": ["[+20]% Strength "], + "uniques": ["[+20]% Strength "] // ToDo: Should only be friendly territory of cities that follow this religion }, { @@ -264,13 +266,13 @@ { "name": "Just War", "type": "Enhancer", - "uniques": ["[+20]% Strength "], + "uniques": ["[+20]% Strength "] // ToDo: Should only be enemy territory of cities that follow this religion }, { "name": "Messiah", "type": "Enhancer", - "uniques": ["[+25]% Spread Religion Strength ", "[-25]% Faith cost of generating Great Prophet equivalents"] + "uniques": ["[+25]% Spread Religion Strength ", "[-25]% Faith cost of generating Great Prophet equivalents", "[Faith] cost for [Great Prophet] units [-25]%"] }, { "name": "Missionary Zeal", diff --git a/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt new file mode 100644 index 0000000000..4d94a356d1 --- /dev/null +++ b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt @@ -0,0 +1,185 @@ +package com.unciv.logic.automation + +import com.unciv.logic.city.CityInfo +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.logic.map.TileInfo +import com.unciv.models.ruleset.Belief +import com.unciv.models.ruleset.BeliefType +import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.stats.Stat +import com.unciv.models.stats.Stats +import kotlin.math.min +import kotlin.random.Random + +object ChooseBeliefsAutomation { + + fun rateBelief(civInfo: CivilizationInfo, belief: Belief): Float { + var score = 0f + + for (city in civInfo.cities) { + for (tile in city.getCenterTile().getTilesInDistance(3)) { + val tileScore = beliefBonusForTile(belief, tile, city) + score += tileScore * when { + city.workedTiles.contains(tile.position) -> 8 + tile.getCity() == city -> 5 + else -> 3 + } * (Random.nextFloat() * 0.05f + 0.975f) + } + + score += beliefBonusForCity(civInfo, belief, city) * (Random.nextFloat() * 0.1f + 0.95f) + } + + score += beliefBonusForPlayer(civInfo, belief) * (Random.nextFloat() * 0.3f + 0.85f) + + // All of these Random.nextFloat() don't exist in the original, but I've added them to make things a bit more random. + + if (belief.type == BeliefType.Pantheon) + score *= 0.9f + + return score + } + + private fun beliefBonusForTile(belief: Belief, tile: TileInfo, city: CityInfo): Float { + var bonusYield = 0f + for (unique in belief.uniqueObjects) { + when (unique.placeholderText) { + "[] from every []" -> if (tile.matchesFilter(unique.params[1])) bonusYield += unique.stats.values.sum() + "[] from [] tiles without [] []" -> + if (city.matchesFilter(unique.params[3]) + && tile.matchesFilter(unique.params[1]) + && !tile.matchesFilter(unique.params[2]) + ) bonusYield += unique.stats.values.sum() + // ToDo: Also calculate add stats for improvements that will be buildable + } + } + return bonusYield + } + + private fun beliefBonusForCity(civInfo: CivilizationInfo, belief: Belief, city: CityInfo): Float { + var score = 0f + val ruleSet = civInfo.gameInfo.ruleSet + for (unique in belief.uniqueObjects) { + var modifier = 1f + if (unique.conditionals.any { it.placeholderText == "when at war" || it.placeholderText == "when not at war" }) + modifier *= 0.5f + // Multiply by 3/10 if has an obsoleted era + // Multiply by 2 if enough pop/followers (best implemented with conditionals, so left open for now) + // If obsoleted, continue + score += modifier * when (unique.placeholderText) { + "[] growth []" -> unique.params[0].toFloat() / 3f + "[]% cost of natural border growth" -> -unique.params[0].toFloat() * 2f / 10f + "[]% attacking Strength for cities" -> unique.params[0].toFloat() / 10f // Modified by personality + "[] Units adjacent to this city heal [] HP per turn when healing" -> unique.params[1].toFloat() / 10f + "+[]% Production when constructing []" -> unique.params[0].toFloat() / 3f + "[] in cities on [] tiles" -> + if (city.getCenterTile().matchesFilter(unique.params[1])) + unique.stats.values.sum() // Modified by personality + else 0f + "[] from every []", "[] from every [] in cities where this religion has at least [] followers" -> + when { + ruleSet.buildings.containsKey(unique.params[1]) -> { + unique.stats.values.sum() / + if (ruleSet.buildings[unique.params[1]]!!.isWonder) 2f + else 1f + + } + ruleSet.specialists.containsKey(unique.params[1]) -> { + unique.stats.values.sum() * + if (city.population.population > 8f) 3f + else 1f + } + else -> 0f + } + "[] in cities with [] or more population", "[] if this city has at least [] specialists" -> + unique.stats.values.sum() // Modified by personality + "[] from each Trade Route" -> + unique.stats.values.sum() * + if (city.isConnectedToCapital()) 2f + else 1f + "[]% [] from every follower, up to []%" -> + min(unique.params[0].toFloat() * city.population.population, unique.params[2].toFloat()) + "[] []" -> + if (city.matchesFilter(unique.params[1])) + unique.stats.values.sum() + else 0f + else -> 0f + } + } + + return score + } + + private fun beliefBonusForPlayer(civInfo: CivilizationInfo, belief: Belief): Float { + var score = 0f + val amountOfEnhancedReligions = civInfo.religionManager.amountOfFoundableReligions() + val goodEarlyModifier = when { + amountOfEnhancedReligions < 33 -> 1f + amountOfEnhancedReligions < 66 -> 2f + else -> 4f + } + val goodLateModifier = when { + amountOfEnhancedReligions < 33 -> 2f + amountOfEnhancedReligions < 66 -> 1f + else -> 1/2f + } + + for (unique in belief.uniqueObjects) { + val modifier = + if (unique.conditionals.any { it.type == UniqueType.ConditionalOurUnit && it.params[0] == civInfo.religionManager.getGreatProphetEquivalent() }) 1/2f + else 1f + // Some city-filters are modified by personality (non-enemy foreign cities) + score += modifier * when (unique.placeholderText) { + "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion" -> + unique.params[0].toFloat() * 4f * + if (civInfo.victoryType() == VictoryType.Domination) 2f + else 1f + "May buy [] buildings for [] [] []", "May buy [] units for [] [] []" -> + if (civInfo.religionManager.religion != null + && civInfo.religionManager.religion!!.getFollowerUniques() + .any { it.placeholderText == unique.placeholderText } + ) 0f + // This is something completely different from the original, but I have no idea + // what happens over there + else civInfo.statsForNextTurn[Stat.valueOf(unique.params[2])] * 5f / unique.params[1].toFloat() + "May buy [] buildings with []", "May buy [] units with []" -> + if (civInfo.religionManager.religion != null + && civInfo.religionManager.religion!!.getFollowerUniques() + .any { it.placeholderText == unique.placeholderText } + ) 0f + // This is something completely different from the original, but I have no idea + // what happens over there + else civInfo.statsForNextTurn[Stat.valueOf(unique.params[1])] * 10f / civInfo.getEra().baseUnitBuyCost + "when a city adopts this religion for the first time (modified by game speed)" -> // Modified by personality + unique.stats.values.sum() * 10f + "When spreading religion to a city, gain [] times the amount of followers of other religions as []" -> + unique.params[0].toInt() / 5f + "[] when a city adopts this religion for the first time (modified by game speed)" -> + unique.stats.values.sum() / 50f + "Resting point for influence with City-States following this religion []" -> + unique.params[0].toInt() / 7f + "[] for each global city following this religion" -> + 50f / unique.stats.values.sum() + "whenever a Great Person is expended" -> + unique.stats.values.sum() / 2f + "[]% Natural religion spread to []" -> + unique.params[0].toFloat() / 4f + "[]% Strength" -> + unique.params[0].toInt() / 4f + "Religion naturally spreads to cities [] tiles away" -> + (10 + unique.params[0].toInt()) / goodEarlyModifier + "[]% Natural religion spread []", "[]% Natural religion spread [] with []" -> + (10 + unique.params[0].toInt()) / goodEarlyModifier + "[]% Spread Religion Strength" -> + unique.params[0].toInt() / goodLateModifier + "[]% Faith cost of generating Great Prophet equivalents" -> + unique.params[0].toInt() / goodLateModifier / 2f + "[] cost for [] units []%" -> + unique.params[2].toInt() / goodLateModifier + else -> 0f + } + } + + return score + } +} diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index bb98777bd7..353a4b92f1 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -314,10 +314,8 @@ 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. - val availablePantheons = civInfo.gameInfo.ruleSet.beliefs.values - .filter { civInfo.religionManager.isPickablePantheonBelief(it) } - if (availablePantheons.isEmpty()) return // panic! - val chosenPantheon = availablePantheons.random() // Why calculate stuff? + val chosenPantheon = chooseBeliefOfType(civInfo, BeliefType.Pantheon) + ?: return // panic! civInfo.religionManager.choosePantheonBelief(chosenPantheon) } @@ -364,7 +362,7 @@ object NextTurnAutomation { .flatMap { religion -> religion.getBeliefs(beliefType) }.contains(it.value) } .map { it.value } - .randomOrNull() // ToDo: Better algorithm + .maxByOrNull { ChooseBeliefsAutomation.rateBelief(civInfo, it) } } private fun potentialLuxuryTrades(civInfo: CivilizationInfo, otherCivInfo: CivilizationInfo): ArrayList { diff --git a/core/src/com/unciv/logic/civilization/ReligionManager.kt b/core/src/com/unciv/logic/civilization/ReligionManager.kt index df418ebbdf..a0333ccc7e 100644 --- a/core/src/com/unciv/logic/civilization/ReligionManager.kt +++ b/core/src/com/unciv/logic/civilization/ReligionManager.kt @@ -157,6 +157,8 @@ class ReligionManager { civInfo.civConstructions.boughtItemsWithIncreasingPrice.add(prophetUnitName, 1) } } + + fun amountOfFoundableReligions() = civInfo.gameInfo.civilizations.count { it.isMajorCiv() } / 2 + 1 fun mayFoundReligionAtAll(prophet: MapUnit): Boolean { if (!civInfo.gameInfo.isReligionEnabled()) return false // No religion @@ -172,7 +174,7 @@ class ReligionManager { it.religionManager.religion != null && it.religionManager.religionState >= ReligionState.Religion } - if (foundedReligionsCount >= civInfo.gameInfo.civilizations.count { it.isMajorCiv() } / 2 + 1) + if (foundedReligionsCount >= amountOfFoundableReligions()) return false // Too bad, too many religions have already been founded if (foundedReligionsCount >= civInfo.gameInfo.ruleSet.religions.count())