Added a few pantheons; religion uniques affect cities instead of civs; updated pantheon picker screen (#4296)

* Added a few simple beliefs for testing

* Moved CityReligion to its own file

* Created a picker screen for choosing pantheons

* Pantheon uniques are now calculated only for cities with the specific pantheon

* Added all the pantheon beliefs that can easily be added and commments for the ones still missing

* Games only have religion if either the user specifically asked for it, or uses a mod with religion

* Implemented requested changes
This commit is contained in:
Xander Lenstra 2021-07-01 21:48:18 +02:00 committed by GitHub
parent 95ae1cea30
commit c6fac03067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 228 additions and 99 deletions

View File

@ -0,0 +1,71 @@
[
{
"name": "Ancestor Worship",
"type": "Pantheon",
"uniques": ["[+1 Culture] from every [Shrine]"]
},
// Missing: Dance of the aurora
{
"name": "Desert Folklore",
"type": "Pantheon",
"uniques": ["[+1 Faith] from every [Desert]"]
},
// Missing: Faith Healers
{
"name": "Fertility Rates",
"type": "Pantheon",
"uniques": ["+[10]% Growth [in this city]"]
// Preferably I would not have a cityFilter here, but doing so requires no additional implementation
},
// Missing: God of Craftsman
{
"name": "God of the Open Sky",
"type": "Pantheon",
"uniques": ["[+1 Culture] from every [Pasture]"]
},
{
"name": "God of the Sea",
"type": "Pantheon",
"uniques": ["[+1 Production] from every [Fishing Boats]"]
},
// Missing: God of War
{
"name": "Goddess of Festivals",
"type": "Pantheon",
"uniques": ["[+1 Culture, +1 Faith] from every [Wine]", "[+1 Culture, +1 Faith] from every [Incense]"]
},
// Missing: Goddess of Love
// Missing: Godess of Protection
{
"name": "Goddess of the Hunt",
"type": "Pantheon",
"uniques": ["[+1 Food] from every [Camp]"]
},
// Missing: Messenger of the Gods
// Missing: Monument to the Gods
// Missing: One with Nature
{
"name": "Oral Tradition",
"type": "Pantheon",
"uniques": ["[+1 Culture] from every [Plantation]"]
},
{
"name": "Religious Idols",
"type": "Pantheon",
"uniques": ["[+1 Culture, +1 Faith] from every [Gold]", "[+1 Culture, +1 Faith] from every [Silver]"]
},
// Missing: Religious Settlements
{
"name": "Sacred Path",
"type": "Pantheon",
"uniques": ["[+1 Culture] from every [Jungle]"]
},
// Missing: Sacred Waters
{
"name": "Stone Circles",
"type": "Pantheon",
"uniques": ["[+2 Faith] from every [Quarry]"]
},
]

View File

@ -78,7 +78,7 @@ class CityConstructions {
fun getStats(): Stats {
val stats = Stats()
for (building in getBuiltBuildings())
stats.add(building.getStats(cityInfo.civInfo))
stats.add(building.getStats(cityInfo))
for (unique in builtBuildingUniqueMap.getAllUniques()) when (unique.placeholderText) {
"[] per [] population []" -> if (cityInfo.matchesFilter(unique.params[2]))
@ -113,7 +113,7 @@ class CityConstructions {
fun getStatPercentBonuses(): Stats {
val stats = Stats()
for (building in getBuiltBuildings())
stats.add(building.getStatPercentageBonuses(cityInfo.civInfo))
stats.add(building.getStatPercentageBonuses(cityInfo))
return stats
}

View File

@ -6,7 +6,6 @@ import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.models.Counter
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit
@ -486,51 +485,3 @@ class CityInfo {
//endregion
}
class CityInfoReligionManager: Counter<String>() {
@Transient
lateinit var cityInfo: CityInfo
fun getNumberOfFollowers(): Counter<String> {
val totalInfluence = values.sum()
val population = cityInfo.population.population
if (totalInfluence > 100 * population) {
val toReturn = Counter<String>()
for ((key, value) in this)
if (value > 100)
toReturn.add(key, value / 100)
return toReturn
}
val toReturn = Counter<String>()
for ((key, value) in this) {
val percentage = value.toFloat() / totalInfluence
val relativePopulation = (percentage * population).roundToInt()
toReturn.add(key, relativePopulation)
}
return toReturn
}
fun getMajorityReligion(): String? {
val followersPerReligion = getNumberOfFollowers()
if (followersPerReligion.isEmpty()) return null
val religionWithMaxFollowers = followersPerReligion.maxByOrNull { it.value }!!
if (religionWithMaxFollowers.value >= cityInfo.population.population) return religionWithMaxFollowers.key
else return null
}
fun getAffectedBySurroundingCities() {
val allCitiesWithin10Tiles =
cityInfo.civInfo.gameInfo.civilizations.asSequence().flatMap { it.cities }
.filter {
it != cityInfo && it.getCenterTile()
.aerialDistanceTo(cityInfo.getCenterTile()) <= 10
}
for (city in allCitiesWithin10Tiles) {
val majorityReligionOfCity = city.religion.getMajorityReligion()
if (majorityReligionOfCity == null) continue
else add(majorityReligionOfCity, 6) // todo - when holy cities are implemented, *5
}
}
}

View File

@ -0,0 +1,64 @@
package com.unciv.logic.city
import com.unciv.models.Counter
import com.unciv.models.ruleset.Unique
import kotlin.math.roundToInt
class CityInfoReligionManager: Counter<String>() {
@Transient
lateinit var cityInfo: CityInfo
fun getUniques(): List<Unique> {
val majorityReligion = getMajorityReligion()
if (majorityReligion == null) return listOf()
// This should later be changed when religions can have multiple beliefs
return cityInfo.civInfo.gameInfo.ruleSet.beliefs[majorityReligion]!!.uniqueObjects
}
fun getMatchingUniques(unique: String): List<Unique> {
return getUniques().filter { it.placeholderText == unique }
}
fun getNumberOfFollowers(): Counter<String> {
val totalInfluence = values.sum()
val population = cityInfo.population.population
if (totalInfluence > 100 * population) {
val toReturn = Counter<String>()
for ((key, value) in this)
if (value > 100)
toReturn.add(key, value / 100)
return toReturn
}
val toReturn = Counter<String>()
for ((key, value) in this) {
val percentage = value.toFloat() / totalInfluence
val relativePopulation = (percentage * population).roundToInt()
toReturn.add(key, relativePopulation)
}
return toReturn
}
fun getMajorityReligion(): String? {
val followersPerReligion = getNumberOfFollowers()
if (followersPerReligion.isEmpty()) return null
val religionWithMaxFollowers = followersPerReligion.maxByOrNull { it.value }!!
if (religionWithMaxFollowers.value >= cityInfo.population.population) return religionWithMaxFollowers.key
else return null
}
fun getAffectedBySurroundingCities() {
val allCitiesWithin10Tiles =
cityInfo.civInfo.gameInfo.civilizations.asSequence().flatMap { it.cities }
.filter {
it != cityInfo && it.getCenterTile()
.aerialDistanceTo(cityInfo.getCenterTile()) <= 10
}
for (city in allCitiesWithin10Tiles) {
val majorityReligionOfCity = city.religion.getMajorityReligion()
if (majorityReligionOfCity == null) continue
else add(majorityReligionOfCity, 6) // todo - when holy cities are implemented, *5
}
}
}

View File

@ -264,7 +264,7 @@ class CityStats {
stats.add(unique.stats)
// "[stats] per [amount] population [cityfilter]"
if (unique.placeholderText=="[] per [] population []" && cityInfo.matchesFilter(unique.params[2])) {
if (unique.placeholderText == "[] per [] population []" && cityInfo.matchesFilter(unique.params[2])) {
val amountOfEffects = (cityInfo.population.population / unique.params[1].toInt()).toFloat()
stats.add(unique.stats.times(amountOfEffects))
}
@ -509,7 +509,8 @@ class CityStats {
citySpecificUniques: Sequence<Unique> = getCitySpecificUniques()
): Sequence<Unique> {
return citySpecificUniques.filter { it.placeholderText == unique } +
cityInfo.civInfo.getMatchingUniques(unique).filter { cityInfo.matchesFilter(it.params[1]) }
cityInfo.civInfo.getMatchingUniques(unique).filter { cityInfo.matchesFilter(it.params[1]) } +
cityInfo.religion.getMatchingUniques(unique)
}
private fun getBuildingMaintenanceCosts(citySpecificUniques: Sequence<Unique>): Float {

View File

@ -257,7 +257,6 @@ class CivilizationInfo {
} +
policies.policyUniques.getUniques(uniqueTemplate) +
tech.getTechUniques().filter { it.placeholderText == uniqueTemplate } +
religionManager.getUniques().filter { it.placeholderText == uniqueTemplate } +
temporaryUniques.filter { it.first.placeholderText == uniqueTemplate }.map { it.first }
}

View File

@ -11,12 +11,7 @@ class ReligionManager {
var pantheonBelief: String? = null
fun getUniques(): Sequence<Unique> {
if(pantheonBelief==null) return sequenceOf()
else return civInfo.gameInfo.ruleSet.beliefs[pantheonBelief!!]!!.uniqueObjects.asSequence()
}
fun faithForPantheon() = 10 + civInfo.gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.pantheonBelief != null } * 5
private fun faithForPantheon() = 10 + civInfo.gameInfo.civilizations.count { it.isMajorCiv() && it.religionManager.pantheonBelief != null } * 5
fun canFoundPantheon(): Boolean {
if (pantheonBelief != null) return false
@ -40,6 +35,8 @@ class ReligionManager {
fun choosePantheonBelief(belief: Belief){
storedFaith -= faithForPantheon()
pantheonBelief = belief.name
// This should later be changed when religions can have multiple beliefs
civInfo.getCapital().religion[belief.name] = 100 // Capital is religious, other cities are not
}
fun clone(): ReligionManager {

View File

@ -235,7 +235,9 @@ open class TileInfo {
if (city != null) {
val cityWideUniques = city.cityConstructions.builtBuildingUniqueMap.getUniques("[] from [] tiles in this city")
val civWideUniques = city.civInfo.getMatchingUniques("[] from every []")
for (unique in cityWideUniques + civWideUniques) {
val religionUniques = city.religion.getMatchingUniques( "[] from every []")
// Should be refactored to use city.getUniquesForThisCity(), probably
for (unique in cityWideUniques + civWideUniques + religionUniques) {
val tileType = unique.params[1]
if (tileType == improvement) continue // This is added to the calculation in getImprovementStats. we don't want to add it twice
if (matchesTerrainFilter(tileType, observingCiv)
@ -295,14 +297,14 @@ open class TileInfo {
}
for (unique in cityWideUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1])
// Freshwater and non-freshwater cannot be moved to matchesUniqueFilter since that creates an enless feedback.
// Freshwater and non-freshwater cannot be moved to matchesUniqueFilter since that creates an endless feedback.
// If you're attempting that, check that it works!
|| unique.params[1] == "Fresh water" && isAdjacentToFreshwater
|| unique.params[1] == "non-fresh water" && !isAdjacentToFreshwater)
stats.add(unique.stats)
}
for (unique in city.civInfo.getMatchingUniques("[] from every []")) {
for (unique in city.civInfo.getMatchingUniques("[] from every []") + city.religion.getMatchingUniques("[] from every []")) {
if (improvement.matchesFilter(unique.params[1])) {
stats.add(unique.stats)
}

View File

@ -1,6 +1,7 @@
package com.unciv.models.ruleset
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.IConstruction
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.Counter
@ -97,8 +98,8 @@ class Building : NamedStats(), IConstruction {
return finalUniques
}
fun getDescription(forBuildingPickerScreen: Boolean, civInfo: CivilizationInfo?, ruleset: Ruleset): String {
val stats = getStats(civInfo)
fun getDescription(forBuildingPickerScreen: Boolean, cityInfo: CityInfo?, ruleset: Ruleset): String {
val stats = getStats(cityInfo)
val stringBuilder = StringBuilder()
if (uniqueTo != null) stringBuilder.appendLine("Unique to [$uniqueTo], replaces [$replaces]".tr())
if (!forBuildingPickerScreen) stringBuilder.appendLine("{Cost}: $cost".tr())
@ -123,7 +124,7 @@ class Building : NamedStats(), IConstruction {
if (!stats.isEmpty())
stringBuilder.appendLine(stats.toString())
val percentStats = getStatPercentageBonuses(civInfo)
val percentStats = getStatPercentageBonuses(cityInfo)
if (percentStats.production != 0f) stringBuilder.append("+" + percentStats.production.toInt() + "% {Production}\n".tr())
if (percentStats.gold != 0f) stringBuilder.append("+" + percentStats.gold.toInt() + "% {Gold}\n".tr())
if (percentStats.science != 0f) stringBuilder.append("+" + percentStats.science.toInt() + "% {Science}\r\n".tr())
@ -152,12 +153,14 @@ class Building : NamedStats(), IConstruction {
return stringBuilder.toString().trim()
}
fun getStats(civInfo: CivilizationInfo?): Stats {
fun getStats(city: CityInfo?): Stats {
val stats = this.clone()
val civInfo = city?.civInfo
if (civInfo != null) {
val baseBuildingName = getBaseBuilding(civInfo.gameInfo.ruleSet).name
for (unique in civInfo.getMatchingUniques("[] from every []")) {
// We don't have to check whether 'city' is null, as if it was, cityInfo would also be null, and we wouldn't be here.
for (unique in civInfo.getMatchingUniques("[] from every []") + city.religion.getMatchingUniques("[] from every []")) {
if (unique.params[1] != baseBuildingName) continue
stats.add(unique.stats)
}
@ -175,13 +178,13 @@ class Building : NamedStats(), IConstruction {
else
for (unique in civInfo.getMatchingUniques("[] from every Wonder"))
stats.add(unique.stats)
}
return stats
}
fun getStatPercentageBonuses(civInfo: CivilizationInfo?): Stats {
fun getStatPercentageBonuses(cityInfo: CityInfo?): Stats {
val stats = if (percentStatBonus == null) Stats() else percentStatBonus!!.clone()
val civInfo = cityInfo?.civInfo
if (civInfo == null) return stats // initial stats
val baseBuildingName = getBaseBuilding(civInfo.gameInfo.ruleSet).name
@ -454,6 +457,7 @@ class Building : NamedStats(), IConstruction {
name -> true
"Building", "Buildings" -> !(isWonder || isNationalWonder)
"Wonder", "Wonders" -> isWonder || isNationalWonder
replaces -> true
else -> {
if (uniques.contains(filter)) return true
return false

View File

@ -42,6 +42,8 @@ class Ruleset {
private val jsonParser = JsonParser()
var modWithReligionLoaded = false
var name = ""
val buildings = LinkedHashMap<String, Building>()
val terrains = LinkedHashMap<String, Terrain>()
@ -93,6 +95,7 @@ class Ruleset {
for (unitToRemove in ruleset.modOptions.unitsToRemove) units.remove(unitToRemove)
for (nationToRemove in ruleset.modOptions.nationsToRemove) nations.remove(nationToRemove)
mods += ruleset.mods
modWithReligionLoaded = modWithReligionLoaded || ruleset.modWithReligionLoaded
}
fun clear() {
@ -112,6 +115,7 @@ class Ruleset {
specialists.clear()
units.clear()
mods.clear()
modWithReligionLoaded = false
}
@ -211,7 +215,7 @@ class Ruleset {
}
fun getEras(): List<String> = technologies.values.map { it.column!!.era }.distinct()
fun hasReligion() = beliefs.any()
fun hasReligion() = beliefs.any() && modWithReligionLoaded
fun getEraNumber(era: String) = getEras().indexOf(era)
fun getSummary(): String {
@ -425,6 +429,9 @@ object RulesetCache :HashMap<String,Ruleset>() {
if (mod.modOptions.isBaseRuleset) {
newRuleset.modOptions = mod.modOptions
}
if (mod.beliefs.any()) {
newRuleset.modWithReligionLoaded = true
}
}
newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs

View File

@ -92,7 +92,7 @@ class CityInfoTable(private val cityScreen: CityScreen) : Table(CameraStageBaseS
wonderDetailsTable.clear()
else {
val detailsString = building.getDescription(true,
cityScreen.city.civInfo, cityScreen.city.civInfo.gameInfo.ruleSet)
cityScreen.city, cityScreen.city.civInfo.gameInfo.ruleSet)
wonderDetailsTable.add(detailsString.toLabel().apply { wrap = true })
.width(cityScreen.stage.width / 4 - 2 * pad).row() // when you set wrap, then you need to manually set the size of the label
if (building.isSellable()) {

View File

@ -59,7 +59,7 @@ class ConstructionInfoTable(val city: CityInfo): Table() {
val description: String = when (construction) {
is BaseUnit -> construction.getDescription(true)
is Building -> construction.getDescription(true, city.civInfo, city.civInfo.gameInfo.ruleSet)
is Building -> construction.getDescription(true, city, city.civInfo.gameInfo.ruleSet)
is PerpetualConstruction -> construction.description.replace("[rate]", "[${construction.getConversionRate(city)}]").tr()
else -> "" // Should never happen
}

View File

@ -0,0 +1,48 @@
package com.unciv.ui.pickerscreens
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.GameInfo
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Belief
import com.unciv.models.translations.tr
import com.unciv.ui.utils.ImageGetter
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.toLabel
class PantheonPickerScreen(choosingCiv: CivilizationInfo, gameInfo: GameInfo) : PickerScreen() {
private var chosenPantheon: Belief? = null
init {
closeButton.isVisible = true
setDefaultCloseAction()
rightSideButton.setText("Choose a pantheon".tr())
topTable.apply { defaults().pad(10f) }
for (belief in gameInfo.ruleSet.beliefs.values) {
if (!choosingCiv.religionManager.isPickablePantheonBelief(belief)) continue
val beliefTable = Table().apply { touchable = Touchable.enabled;
background =
// Ideally I want to this to be the darker blue we use for pressed buttons, but I suck at UI so I'll leave it like this.
if (belief == chosenPantheon) ImageGetter.getBackground(ImageGetter.getBlue())
else ImageGetter.getBackground(ImageGetter.getBlue())
}
beliefTable.pad(10f)
beliefTable.add(belief.name.toLabel(fontSize = 24)).row()
beliefTable.add(belief.uniques.joinToString().toLabel())
beliefTable.onClick {
chosenPantheon = belief
pick("Follow [${chosenPantheon!!.name}]".tr())
}
topTable.add(beliefTable).fillX().row()
}
rightSideButton.onClick(UncivSound.Choir) {
choosingCiv.religionManager.choosePantheonBelief(chosenPantheon!!)
UncivGame.Current.setWorldScreen()
}
}
}

View File

@ -28,10 +28,7 @@ import com.unciv.models.translations.tr
import com.unciv.ui.cityscreen.CityScreen
import com.unciv.ui.civilopedia.CivilopediaScreen
import com.unciv.ui.overviewscreen.EmpireOverviewScreen
import com.unciv.ui.pickerscreens.GreatPersonPickerScreen
import com.unciv.ui.pickerscreens.PolicyPickerScreen
import com.unciv.ui.pickerscreens.TechButton
import com.unciv.ui.pickerscreens.TechPickerScreen
import com.unciv.ui.pickerscreens.*
import com.unciv.ui.saves.LoadGameScreen
import com.unciv.ui.saves.SaveGameScreen
import com.unciv.ui.trade.DiplomacyScreen
@ -694,19 +691,7 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Cam
viewingCiv.religionManager.canFoundPantheon() ->
NextTurnAction("Found Pantheon", Color.WHITE) {
val pantheonPopup = Popup(this)
val beliefsTable = Table().apply { defaults().pad(10f) }
for (belief in gameInfo.ruleSet.beliefs.values) {
if (!viewingCiv.religionManager.isPickablePantheonBelief(belief)) continue
val beliefTable = Table().apply { touchable = Touchable.enabled; background = ImageGetter.getBackground(ImageGetter.getBlue()) }
beliefTable.pad(10f)
beliefTable.add(belief.name.toLabel(fontSize = 24)).row()
beliefTable.add(belief.uniques.joinToString().toLabel())
beliefTable.onClick { viewingCiv.religionManager.choosePantheonBelief(belief); pantheonPopup.close(); shouldUpdate = true }
beliefsTable.add(beliefTable).fillX().row()
}
pantheonPopup.add(ScrollPane(beliefsTable)).maxHeight(stage.height * .8f)
pantheonPopup.open()
game.setScreen(PantheonPickerScreen(viewingCiv, gameInfo))
}
else ->