Added founder beliefs, updates to pantheon spreading (#4827)

* Added founder beliefs, minor changes to pantheons

* Fixed the tests

* Implemented recommended chances

* Implemented requested changes
This commit is contained in:
Xander Lenstra 2021-08-11 08:56:36 +02:00 committed by GitHub
parent 3c4bb1a558
commit 4aed0f0f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 201 additions and 44 deletions

View File

@ -175,5 +175,37 @@
"name": "Swords into Ploughshares",
"type": "Follower",
"uniques": ["[+15]% growth [in this city] when not at war"]
}
},
///////////////////////////////////////// Founder beliefs //////////////////////////////////////////
{
"name": "Ceremonial Burial",
"type": "Founder",
"uniques": ["[+1 Happiness] for each global city following this religion"]
},
{
"name": "Church Property",
"type": "Founder",
"uniques": ["[+2 Gold] for each global city following this religion"]
},
{
"name": "Initiation Rites",
"type": "Founder",
"uniques": ["[+100 Gold] when a city adopts this religion for the first time (modified by game speed)"]
},
// Missing: Interfaith Dialogue (requires followers)
{
"name": "Papal Primacy",
"type": "Founder",
"uniques": ["Resting point for influence with City-States following this religion [+15]"]
},
// Missing: Peace Loving (requires followers)
{
"name": "Pilgrimage",
"type": "Founder",
"uniques": ["[+2 Faith] for each global city following this religion"]
},
// Missing: Tithe (requires followers)
// Missing: World Churhc (requires followers)
]

View File

@ -530,7 +530,11 @@ Our [name] took [tileDamage] tile damage =
[civName] has adopted the [policyName] policy =
An unknown civilization has adopted the [policyName] policy =
Our influence with City-States has started dropping faster! =
You gained [Stats] as your religion was spread to [cityName] =
You gained [Stats] as your religion was spread to an unknown city =
Your city [cityName] was converted to [religionName]! =
# World Screen UI
Working... =
@ -1152,4 +1156,4 @@ in all cities with a garrison =
Only available after [] turns =
This Unit upgrades for free =
[stats] when a city adopts this religion for the first time =

View File

@ -92,11 +92,9 @@ class CityInfo {
val startingEra = civInfo.gameInfo.gameParameters.startingEra
addStartingBuildings(civInfo, startingEra)
expansion.reset()
tryUpdateRoadStatus()
val tile = getCenterTile()
@ -106,6 +104,10 @@ class CityInfo {
tile.improvement = null
tile.improvementInProgress = null
if (civInfo.religionManager.religion != null && civInfo.religionManager.religion!!.isPantheon()) {
religion.addPressure(civInfo.religionManager.religion!!.name, 100)
}
val ruleset = civInfo.gameInfo.ruleSet
workedTiles = hashSetOf() //reassign 1st working tile
if (startingEra in ruleset.eras)
@ -204,6 +206,7 @@ class CityInfo {
toReturn.population = population.clone()
toReturn.cityConstructions = cityConstructions.clone()
toReturn.expansion = expansion.clone()
toReturn.religion = religion.clone()
toReturn.tiles = tiles
toReturn.workedTiles = workedTiles
toReturn.lockedTiles = lockedTiles
@ -214,7 +217,6 @@ class CityInfo {
toReturn.turnAcquired = turnAcquired
toReturn.isPuppet = isPuppet
toReturn.isOriginalCapital = isOriginalCapital
toReturn.religion = CityInfoReligionManager().apply { putAll(religion) }
return toReturn
}
@ -431,7 +433,7 @@ class CityInfo {
cityStats.cityInfo = this
cityConstructions.cityInfo = this
cityConstructions.setTransients()
religion.cityInfo = this
religion.setTransients(this)
}
fun startTurn() {

View File

@ -1,13 +1,33 @@
package com.unciv.logic.city
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.models.Counter
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.Stats
import kotlin.math.roundToInt
class CityInfoReligionManager: Counter<String>() {
class CityInfoReligionManager {
@Transient
lateinit var cityInfo: CityInfo
// This needs to be kept track of for the
// "[Stats] when a city adopts this religion for the first time" unique
val religionsAtSomePointAdopted: HashSet<String> = hashSetOf()
private val pressures: Counter<String> = Counter()
fun clone(): CityInfoReligionManager {
val toReturn = CityInfoReligionManager()
toReturn.religionsAtSomePointAdopted.addAll(religionsAtSomePointAdopted)
toReturn.pressures.putAll(pressures)
return toReturn
}
fun setTransients(cityInfo: CityInfo) {
this.cityInfo = cityInfo
}
fun getUniques(): Sequence<Unique> {
val majorityReligion = getMajorityReligion()
if (majorityReligion == null) return sequenceOf()
@ -18,12 +38,45 @@ class CityInfoReligionManager: Counter<String>() {
return getUniques().filter { it.placeholderText == unique }
}
fun clearAllPressures() {
pressures.clear()
}
fun addPressure(religionName: String, amount: Int) {
val oldMajorityReligion = getMajorityReligion()
pressures.add(religionName, amount)
val newMajorityReligion = getMajorityReligion()
if (oldMajorityReligion != newMajorityReligion && newMajorityReligion != null) {
triggerReligionAdoption(newMajorityReligion)
}
}
private fun triggerReligionAdoption(newMajorityReligion: String) {
cityInfo.civInfo.addNotification("Your city [${cityInfo.name}] was converted to [$newMajorityReligion]!", cityInfo.location, NotificationIcon.Faith)
if (newMajorityReligion in religionsAtSomePointAdopted) return
val religionOwningCiv = cityInfo.civInfo.gameInfo.getCivilization(cityInfo.civInfo.gameInfo.religions[newMajorityReligion]!!.foundingCivName)
for (unique in cityInfo.civInfo.gameInfo.religions[newMajorityReligion]!!.getFounderUniques()) {
val statsGranted = when (unique.placeholderText) {
"[] when a city adopts this religion for the first time (modified by game speed)" -> unique.stats.times(cityInfo.civInfo.gameInfo.gameParameters.gameSpeed.modifier)
"[] when a city adopts this religion for the first time" -> unique.stats
else -> continue
}
religionOwningCiv.addStats(statsGranted)
if (cityInfo.location in religionOwningCiv.exploredTiles)
religionOwningCiv.addNotification("You gained [$statsGranted] as your religion was spread to [${cityInfo.name}]", cityInfo.location, NotificationIcon.Faith)
else
religionOwningCiv.addNotification("You gained [$statsGranted] as your religion was spread to an unknown city", NotificationIcon.Faith)
}
religionsAtSomePointAdopted.add(newMajorityReligion)
}
fun getNumberOfFollowers(): Counter<String> {
val totalInfluence = values.sum()
val totalInfluence = pressures.values.sum()
val population = cityInfo.population.population
if (totalInfluence > 100 * population) {
val toReturn = Counter<String>()
for ((key, value) in this)
for ((key, value) in pressures)
if (value > 100)
toReturn.add(key, value / 100)
return toReturn
@ -31,7 +84,7 @@ class CityInfoReligionManager: Counter<String>() {
val toReturn = Counter<String>()
for ((key, value) in this) {
for ((key, value) in pressures) {
val percentage = value.toFloat() / totalInfluence
val relativePopulation = (percentage * population).roundToInt()
toReturn.add(key, relativePopulation)
@ -57,7 +110,11 @@ class CityInfoReligionManager: Counter<String>() {
for (city in allCitiesWithin10Tiles) {
val majorityReligionOfCity = city.religion.getMajorityReligion()
if (majorityReligionOfCity == null) continue
else add(majorityReligionOfCity, 6) // todo - when holy cities are implemented, *5
else addPressure(
majorityReligionOfCity,
if (city.isHolyCity()) 30
else 6
)
}
}
}

View File

@ -108,6 +108,18 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
statMap["Transportation upkeep"] = Stats().apply { gold = -getTransportationUpkeep().toFloat() }
statMap["Unit upkeep"] = Stats().apply { gold = -getUnitMaintenance().toFloat() }
if (civInfo.religionManager.religion != null) {
for (unique in civInfo.religionManager.religion!!.getFounderBeliefs().flatMap { it.uniqueObjects }) {
if (unique.placeholderText == "[] for each global city following this religion") {
statMap.add(
"Religion",
unique.stats.times(civInfo.religionManager.numberOfCitiesFollowingThisReligion().toFloat())
)
}
}
}
if (civInfo.hasUnique("50% of excess happiness added to culture towards policies")) {
val happiness = civInfo.getHappiness()
if (happiness > 0) statMap.add("Policies", Stats().apply { culture = happiness / 2f })
@ -185,6 +197,19 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
statMap["Natural Wonders"] = happinessPerNaturalWonder * civInfo.naturalWonders.size
if (civInfo.religionManager.religion != null) {
statMap["Religion"] = 0f
for (unique in civInfo.religionManager.religion!!.getFounderBeliefs().flatMap { it.uniqueObjects }) {
if (unique.placeholderText == "[] for each global city following this religion") {
statMap["Religion"] =
statMap["Religion"]!! +
unique.stats.happiness * civInfo.religionManager.numberOfCitiesFollowingThisReligion().toFloat()
}
}
if (statMap["Religion"] == 0f)
statMap.remove("Religion")
}
//From city-states
for (otherCiv in civInfo.getKnownCivs()) {
if (otherCiv.isCityState() && otherCiv.cityStateType == CityStateType.Mercantile

View File

@ -262,7 +262,9 @@ class CivilizationInfo {
} +
policies.policyUniques.getUniques(uniqueTemplate) +
tech.techUniques.getUniques(uniqueTemplate) +
temporaryUniques.filter { it.first.placeholderText == uniqueTemplate }.map { it.first }
temporaryUniques.filter { it.first.placeholderText == uniqueTemplate }.map { it.first } +
if (religionManager.religion != null) religionManager.religion!!.getFounderUniques().asSequence()
else sequenceOf()
}
//region Units
@ -712,6 +714,13 @@ class CivilizationInfo {
}
}
/** Rounds each of the stats down to the nearest integer */
fun addStats(stats: Stats) {
for (stat in stats.toHashMap()) {
addStat(stat.key, stat.value.toInt())
}
}
fun getStatReserve(stat: Stat): Int {
return when (stat) {
Stat.Culture -> policies.storedCulture

View File

@ -87,8 +87,9 @@ class ReligionManager {
religion = Religion(belief.name, civInfo.gameInfo, civInfo.civName)
religion!!.followerBeliefs.add(belief.name)
civInfo.gameInfo.religions[belief.name] = religion!!
// This should later be changed when religions can have multiple beliefs
civInfo.getCapital().religion[belief.name] = 100 // Capital is religious, other cities are not
// ToDo: This should later be changed when religions can have multiple beliefs
civInfo.getCapital().religion.clearAllPressures()
civInfo.getCapital().religion.addPressure(belief.name, 100) // Capital is religious, other cities are not
religionState = ReligionState.Pantheon
}
@ -102,6 +103,7 @@ class ReligionManager {
private fun canGenerateProphet(): Boolean {
if (religion == null || religionState == ReligionState.None) return false // First get a pantheon, then we'll talk about a real religion
if (storedFaith < faithForNextGreatProphet()) return false
if (!civInfo.isMajorCiv()) return false
// In the base game, great prophets shouldn't generate anymore starting from the industrial era
// This is difficult to implement in the current codebase, probably requires an additional variable in eras.json
return true
@ -123,53 +125,60 @@ class ReligionManager {
}
}
fun mayUseGreatProphetAtAll(prophet: MapUnit): Boolean {
fun mayFoundReligionAtAll(prophet: MapUnit): Boolean {
if (religion == null) return false // First found a pantheon
if (religion!!.isMajorReligion()) return false // Already created a major religion
if (prophet.abilityUsedCount["Religion Spread"] != 0) return false // Already used its power for other things
if (!civInfo.isMajorCiv()) return false // Only major civs may use religion
val foundedReligionsCount = civInfo.gameInfo.civilizations.count {
it.religionManager.religion != null && it.religionManager.religion!!.isMajorReligion()
}
if (foundedReligionsCount >= civInfo.gameInfo.civilizations.count { it.isMajorCiv() } / 2 + 1)
return false // Too bad, too many religions have already been founded.
return false // Too bad, too many religions have already been founded
if (foundedReligionsCount >= civInfo.gameInfo.ruleSet.religions.count())
return false
// Mod maker did not provide enough religions for the amount of civs present
return false // Mod maker did not provide enough religions for the amount of civs present
if (foundedReligionsCount >= civInfo.gameInfo.ruleSet.beliefs.values.count { it.type == BeliefType.Follower })
return false // Mod maker did not provide enough follower beliefs
if (foundedReligionsCount >= civInfo.gameInfo.ruleSet.beliefs.values.count { it.type == BeliefType.Founder })
return false // Mod maker did not provide enough founder beliefs
return true
}
fun mayUseGreatProphetNow(prophet: MapUnit): Boolean {
if (!mayUseGreatProphetAtAll(prophet)) return false
fun mayFoundReligionNow(prophet: MapUnit): Boolean {
if (!mayFoundReligionAtAll(prophet)) return false
if (!prophet.getTile().isCityCenter()) return false
return true
}
fun useGreatProphet(prophet: MapUnit) {
if (!mayUseGreatProphetNow(prophet)) return // How did you do this?
if (!mayFoundReligionNow(prophet)) return // How did you do this?
religionState = ReligionState.FoundingReligion
foundingCityId = prophet.getTile().getCity()!!.id
}
fun foundReligion(iconName: String, name: String, founderBelief: String, followerBeliefs: List<String>) {
fun foundReligion(iconName: String, name: String, founderBelief: List<String>, followerBeliefs: List<String>) {
val newReligion = Religion(name, civInfo.gameInfo, civInfo.civName)
newReligion.iconName = iconName
if (religion != null) {
newReligion.followerBeliefs.addAll(religion!!.followerBeliefs)
}
newReligion.followerBeliefs.addAll(followerBeliefs)
newReligion.founderBeliefs.add(founderBelief)
newReligion.founderBeliefs.addAll(founderBelief)
newReligion.holyCityId = foundingCityId
religion = newReligion
civInfo.gameInfo.religions[name] = newReligion
religionState = ReligionState.Religion
val holyCity = civInfo.cities.firstOrNull { it.id == newReligion.holyCityId }!!
holyCity.religion.clear()
holyCity.religion[name] = 100
// ToDo: check this when implementing followers
holyCity.religion.clearAllPressures()
holyCity.religion.addPressure(name, 100)
foundingCityId = null
}

View File

@ -193,8 +193,14 @@ class DiplomacyManager() {
// To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different.
fun getCityStateInfluenceRestingPoint(): Float {
var restingPoint = 0f
for (unique in otherCiv().getMatchingUniques("Resting point for Influence with City-States is increased by []"))
restingPoint += unique.params[0].toInt()
for (unique in otherCiv().getMatchingUniques("Resting point for Influence with City-States following this religion []"))
if (otherCiv().religionManager.religion?.name == civInfo.getCapital().religion.getMajorityReligion())
restingPoint += unique.params[0].toInt()
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 5
return restingPoint
}

View File

@ -42,7 +42,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
return counter
}
var greatPersonPoints= Counter<String>()
var greatPersonPoints = Counter<String>()
/** Extra cost percentage when purchasing */
override var hurryCostModifier = 0

View File

@ -46,14 +46,13 @@ class FoundReligionPickerScreen (
middlePanes.add(ScrollPane(rightBeliefsToChoose))
topTable.add(topReligionIcons).row()
// commented out, as the middle panes will always be empty for now, and this will create a random line otherwise
topTable.addSeparator()
topTable.add(middlePanes)
rightSideButton.label = "Choose a religion".toLabel()
rightSideButton.onClick(UncivSound.Choir) {
choosingCiv.religionManager.foundReligion(
iconName!!, religionName!!, "" /**chosenFollowerBeliefs.map {it!!.name} */, chosenFollowerBeliefs.map { it!!.name}
iconName!!, religionName!!, chosenFounderBeliefs.map {it!!.name}, chosenFollowerBeliefs.map { it!!.name}
)
UncivGame.Current.setWorldScreen()
}
@ -61,8 +60,8 @@ class FoundReligionPickerScreen (
private fun checkAndEnableRightSideButton() {
if (religionName == null) return
println(chosenFollowerBeliefs)
if (chosenFollowerBeliefs.any { it == null }) return
if (chosenFounderBeliefs.any { it == null }) return
// check if founder belief chosen
rightSideButton.enable()
}
@ -113,21 +112,32 @@ class FoundReligionPickerScreen (
for (newFollowerBelief in chosenFollowerBeliefs.withIndex()) {
val newFollowerBeliefButton =
if (newFollowerBelief.value == null) emptyBeliefButton("Follower")
if (newFollowerBelief.value == null) emptyBeliefButton(BeliefType.Follower)
else convertBeliefToButton(newFollowerBelief.value!!)
leftChosenBeliefs.add(newFollowerBeliefButton).pad(10f).row()
newFollowerBeliefButton.onClick {
loadRightTable("Follower", newFollowerBelief.index)
loadRightTable(BeliefType.Follower, newFollowerBelief.index)
}
}
for (newFounderBelief in chosenFounderBeliefs.withIndex()) {
val newFounderBeliefButton =
if (newFounderBelief.value == null) emptyBeliefButton(BeliefType.Founder)
else convertBeliefToButton(newFounderBelief.value!!)
leftChosenBeliefs.add(newFounderBeliefButton).pad(10f).row()
newFounderBeliefButton.onClick {
loadRightTable(BeliefType.Founder, newFounderBelief.index)
}
}
}
private fun loadRightTable(beliefType: String, leftButtonIndex: Int) {
private fun loadRightTable(beliefType: BeliefType, leftButtonIndex: Int) {
rightBeliefsToChoose.clear()
val availableBeliefs = gameInfo.ruleSet.beliefs.values
.filter {
it.type.name == beliefType
it.type == beliefType
&& gameInfo.religions.values.none {
religion -> religion.hasBelief(it.name)
}
@ -136,8 +146,8 @@ class FoundReligionPickerScreen (
for (belief in availableBeliefs) {
val beliefButton = convertBeliefToButton(belief)
beliefButton.onClick {
if (beliefType == BeliefType.Follower.name) chosenFollowerBeliefs[leftButtonIndex] = belief
else if (beliefType == BeliefType.Founder.name) chosenFounderBeliefs[leftButtonIndex] = belief
if (beliefType == BeliefType.Follower) chosenFollowerBeliefs[leftButtonIndex] = belief
else if (beliefType == BeliefType.Founder) chosenFounderBeliefs[leftButtonIndex] = belief
updateLeftTable()
checkAndEnableRightSideButton()
}
@ -153,9 +163,9 @@ class FoundReligionPickerScreen (
return Button(contentsTable, skin)
}
private fun emptyBeliefButton(beliefType: String): Button {
private fun emptyBeliefButton(beliefType: BeliefType): Button {
val contentsTable = Table()
contentsTable.add("Choose a [$beliefType] belief!".toLabel())
contentsTable.add("Choose a [${beliefType.name}] belief!".toLabel())
return Button(contentsTable, skin)
}
}

View File

@ -450,34 +450,37 @@ object UnitActions {
private fun addFoundReligionAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
if (!unit.hasUnique("May found a religion")) return // should later also include enhance religion
if (!unit.civInfo.religionManager.mayUseGreatProphetAtAll(unit)) return
if (!unit.civInfo.religionManager.mayFoundReligionAtAll(unit)) return
actionList += UnitAction(UnitActionType.FoundReligion,
action = {
addGoldPerGreatPersonUsage(unit.civInfo)
unit.civInfo.religionManager.useGreatProphet(unit)
unit.destroy()
}.takeIf { unit.civInfo.religionManager.mayUseGreatProphetNow(unit) }
}.takeIf { unit.civInfo.religionManager.mayFoundReligionNow(unit) }
)
}
private fun addSpreadReligionActions(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo) {
if (!unit.hasUnique("Can spread religion [] times")) return
if (unit.religion == null) return
if (unit.religion == null || unit.civInfo.gameInfo.religions[unit.religion]!!.isPantheon()) return
val maxReligionSpreads = unit.maxReligionSpreads()
if (!unit.abilityUsedCount.containsKey("Religion Spread")) return // This should be impossible anyways, but just in case
if (maxReligionSpreads <= unit.abilityUsedCount["Religion Spread"]!!) return
val city = tile.getCity()
if (city == null) return
actionList += UnitAction(UnitActionType.SpreadReligion,
title = "Spread [${unit.religion!!}]",
action = {
unit.abilityUsedCount["Religion Spread"] = unit.abilityUsedCount["Religion Spread"]!! + 1
city!!.religion[unit.religion!!] = 100
// ToDo: implement followers
city.religion.clearAllPressures()
city.religion.addPressure(unit.religion!!, 100)
unit.currentMovement = 0f
if (unit.abilityUsedCount["Religion Spread"] == maxReligionSpreads) {
addGoldPerGreatPersonUsage(unit.civInfo)
unit.destroy()
}
}.takeIf { unit.currentMovement > 0 && city != null && city.civInfo == unit.civInfo } // For now you can only convert your own cities
}.takeIf { unit.currentMovement > 0 }
)
}