Added an advanced system for choosing beliefs (#5400)

This commit is contained in:
Xander Lenstra
2021-10-05 14:33:27 +02:00
committed by GitHub
parent 4c6f1ecfb8
commit e879bb2a14
4 changed files with 197 additions and 10 deletions

View File

@ -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] <during the [Ancient era]>"
},
{
"name": "One with Nature",
@ -248,7 +250,7 @@
{
"name": "Defender of the Faith",
"type": "Enhancer",
"uniques": ["[+20]% Strength <for [All] units> <when fighting in [Friendly Land] tiles>"],
"uniques": ["[+20]% Strength <for [All] units> <when fighting in [Friendly Land] tiles>"]
// ToDo: Should only be friendly territory of cities that follow this religion
},
{
@ -264,13 +266,13 @@
{
"name": "Just War",
"type": "Enhancer",
"uniques": ["[+20]% Strength <for [All] units> <when fighting in [Enemy Land] tiles>"],
"uniques": ["[+20]% Strength <for [All] units> <when fighting in [Enemy Land] tiles>"]
// ToDo: Should only be enemy territory of cities that follow this religion
},
{
"name": "Messiah",
"type": "Enhancer",
"uniques": ["[+25]% Spread Religion Strength <for [Great Prophet] units>", "[-25]% Faith cost of generating Great Prophet equivalents"]
"uniques": ["[+25]% Spread Religion Strength <for [Great Prophet] units>", "[-25]% Faith cost of generating Great Prophet equivalents", "[Faith] cost for [Great Prophet] units [-25]%"]
},
{
"name": "Missionary Zeal",

View File

@ -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
}
}

View File

@ -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<Trade> {

View File

@ -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())