From cffe8e441e4c6db49d1b50ad2fe8f024befb85b9 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Wed, 1 Sep 2021 08:46:27 +0200 Subject: [PATCH] Finishing the later five policy trees now allows you to buy great people with faith (#5038) --- .../jsons/Civ V - Vanilla/Policies.json | 21 +++-- .../logic/automation/NextTurnAutomation.kt | 4 - .../com/unciv/logic/city/CityConstructions.kt | 10 +-- .../src/com/unciv/logic/city/IConstruction.kt | 5 +- .../logic/civilization/CivilizationInfo.kt | 6 +- .../unciv/logic/civilization/PolicyManager.kt | 3 +- core/src/com/unciv/models/ruleset/Building.kt | 62 ++++++------- .../com/unciv/models/ruleset/unit/BaseUnit.kt | 89 +++++++++++++++---- 8 files changed, 130 insertions(+), 70 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index a2ca2c2859..a110223d36 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -275,7 +275,9 @@ }, { "name": "Commerce Complete", - "uniques": ["[+1 Gold] from every [Trading post]", "Double gold from Great Merchant trade missions"] + "uniques": ["[+1 Gold] from every [Trading post]", "Double gold from Great Merchant trade missions", + "May buy [Great Merchant] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])" + ] } ] }, @@ -320,7 +322,9 @@ }, { "name": "Rationalism Complete", - "uniques": ["[2] Free Technologies"] + "uniques": ["[2] Free Technologies", + "May buy [Great Scientist] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])" + ] } ] }, @@ -363,7 +367,9 @@ }, { "name": "Freedom Complete", - "uniques": ["+[100]% yield from every [Great Improvement]", "Golden Age length increased by [50]%"] + "uniques": ["+[100]% yield from every [Great Improvement]", "Golden Age length increased by [50]%", + "May buy [Great Artist] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])" + ] } ] }, @@ -409,7 +415,10 @@ }, { "name": "Autocracy Complete", - "uniques": ["+[25]% attack strength to all [Military] units for [50] turns"] + "uniques": ["+[25]% attack strength to all [Military] units for [50] turns", + "May buy [Great General] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])", + "May buy [Great Admiral] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])" + ] } ] }, @@ -452,7 +461,9 @@ }, { "name": "Order Complete", - "uniques": ["[+2 Food, +2 Production, +2 Science, +2 Gold, +2 Culture] [in all cities]"] + "uniques": ["[+2 Food, +2 Production, +2 Science, +2 Gold, +2 Culture] [in all cities]", + "May buy [Great Engineer] units for [1000] [Faith] [in all cities in which the majority religion is a major religion] starting from the [Industrial era] at an increasing price ([500])" + ] } ] } diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 42ecf707d5..700f6677ba 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -328,16 +328,12 @@ object NextTurnAutomation { } 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) } diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index e6a3a95a8d..deecd3a9bc 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -251,8 +251,8 @@ class CityConstructions { isBuilt(building) || getBuiltBuildings().any { it.replaces == building } fun getWorkDone(constructionName: String): Int { - if (inProgressConstructions.containsKey(constructionName)) return inProgressConstructions[constructionName]!! - else return 0 + return if (inProgressConstructions.containsKey(constructionName)) inProgressConstructions[constructionName]!! + else 0 } fun getRemainingWork(constructionName: String, useStoredProduction: Boolean = true): Int { @@ -425,7 +425,7 @@ class CityConstructions { } } - private fun constructionComplete(construction: IConstruction) { + private fun constructionComplete(construction: INonPerpetualConstruction) { construction.postBuildEvent(this) if (construction.name in inProgressConstructions) inProgressConstructions.remove(construction.name) @@ -501,7 +501,7 @@ class CityConstructions { automatic: Boolean, stat: Stat = Stat.Gold ): Boolean { - if (!getConstruction(constructionName).postBuildEvent(this, true)) + if (!(getConstruction(constructionName) as INonPerpetualConstruction).postBuildEvent(this, stat)) return false // nothing built - no pay if (!cityInfo.civInfo.gameInfo.gameParameters.godMode) { @@ -535,7 +535,7 @@ class CityConstructions { return null val cultureBuildingToBuild = buildableCultureBuildings.minByOrNull { it.cost }!!.name - constructionComplete(getConstruction(cultureBuildingToBuild)) + constructionComplete(getConstruction(cultureBuildingToBuild) as INonPerpetualConstruction) return cultureBuildingToBuild } diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index f946b82757..f6bfd365c7 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -13,7 +13,6 @@ import kotlin.math.roundToInt interface IConstruction : INamed { fun isBuildable(cityConstructions: CityConstructions): Boolean fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean - fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean = false): Boolean // Yes I'm hilarious. fun getResourceRequirements(): HashMap } @@ -23,6 +22,7 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { fun getProductionCost(civInfo: CivilizationInfo): Int fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? fun getRejectionReason(cityConstructions: CityConstructions): String + fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious. fun getMatchingUniques(uniqueTemplate: String): Sequence { return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } @@ -118,9 +118,6 @@ open class PerpetualConstruction(override var name: String, val description: Str override fun isBuildable(cityConstructions: CityConstructions): Boolean = throw Exception("Impossible!") - override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean) = - throw Exception("Impossible!") - override fun getResourceRequirements(): HashMap = hashMapOf() } \ No newline at end of file diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 283b5de2ab..73e30d5c2e 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -122,7 +122,10 @@ class CivilizationInfo { * Pairs of Uniques and the amount of turns they are still active * If the counter reaches 0 at the end of a turn, it is removed immediately */ - var temporaryUniques = ArrayList>() + val temporaryUniques = ArrayList>() + + /** Maps the name of the construction to the amount of times bouhgt */ + val boughtConstructionsWithGloballyIncreasingPrice = HashMap() // if we only use lists, and change the list each time the cities are changed, // we won't get concurrent modification exceptions. @@ -174,6 +177,7 @@ class CivilizationInfo { toReturn.cityStateResource = cityStateResource toReturn.flagsCountdown.putAll(flagsCountdown) toReturn.temporaryUniques.addAll(temporaryUniques) + toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice) toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital return toReturn } diff --git a/core/src/com/unciv/logic/civilization/PolicyManager.kt b/core/src/com/unciv/logic/civilization/PolicyManager.kt index 0959aa9b60..13c4cac138 100644 --- a/core/src/com/unciv/logic/civilization/PolicyManager.kt +++ b/core/src/com/unciv/logic/civilization/PolicyManager.kt @@ -1,5 +1,6 @@ package com.unciv.logic.civilization +import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.logic.map.MapSize import com.unciv.models.ruleset.Policy import com.unciv.models.ruleset.Policy.PolicyBranchType @@ -214,7 +215,7 @@ class PolicyManager { } for (city in candidateCities) { - city.cityConstructions.getConstruction(building).postBuildEvent(city.cityConstructions, false) + (city.cityConstructions.getConstruction(building) as INonPerpetualConstruction).postBuildEvent(city.cityConstructions) citiesAlreadyGivenBuilding.add(city.id) } } diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 48eab5276b..5c3d7b7c88 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -205,38 +205,6 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { return stats } - override fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean): Boolean { - if (stat == Stat.Gold && isAnyWonder()) return false - // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] - if (!ignoreCityRequirements && cityInfo.getMatchingUniques("May buy [] buildings for [] [] []") - .any { it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) } - ) return true - return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) - } - - override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { - if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() - - val lowestCostFromUnique = - ( - // Can be purchased for [amount] [Stat] [cityFilter] - getMatchingUniques("Can be purchased for [] [] []") - .filter { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) } - .map { it.params[0].toInt() } - // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] - + cityInfo.getMatchingUniques("May buy [] buildings for [] [] []") - .filter { it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3])} - .map { it.params[1].toInt() } - ).minOrNull() - if (lowestCostFromUnique != null) return lowestCostFromUnique - - // Can be purchased with [Stat] [cityFilter] - if (getMatchingUniques("Can be purchased with [] []") - .any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1])} - ) return cityInfo.civInfo.gameInfo.ruleSet.eras[cityInfo.civInfo.getEra()]!!.baseUnitBuyCost - return null - } - override fun getCivilopediaTextHeader() = FormattedLine(name, header=2, icon=makeLink()) override fun makeLink() = if (isAnyWonder()) "Wonder/$name" else "Building/$name" override fun hasCivilopediaTextLines() = true @@ -385,6 +353,32 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { return productionCost.toInt() } + + override fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean): Boolean { + if (stat == Stat.Gold && isAnyWonder()) return false + // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] + if (!ignoreCityRequirements && cityInfo.getMatchingUniques("May buy [] buildings for [] [] []") + .any { it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) } + ) return true + return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) + } + + override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { + if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() + + return ( + sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull() + // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] + + cityInfo.getMatchingUniques("May buy [] buildings for [] [] []") + .filter { + it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter( + it.params[3] + ) + } + .map { it.params[1].toInt() } + ).minOrNull() + } + override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? { var cost = getBaseBuyCost(cityInfo, stat)?.toDouble() if (cost == null) return null @@ -603,7 +597,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { override fun isBuildable(cityConstructions: CityConstructions): Boolean = getRejectionReason(cityConstructions) == "" - override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean): Boolean { + override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { val civInfo = cityConstructions.cityInfo.civInfo if ("Spaceship part" in uniques) { @@ -627,7 +621,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { var freeBuildingUniques = uniqueObjects.asSequence().filter { it.placeholderText=="Provides a free [] []" } if (providesFreeBuilding!=null) freeBuildingUniques += sequenceOf(Unique("Provides a free [$providesFreeBuilding] [in this city]")) - for(unique in freeBuildingUniques) { + for (unique in freeBuildingUniques) { val affectedCities = if (unique.params[1] == "in this city") sequenceOf(cityConstructions.cityInfo) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 0610814cf1..3037eaa95e 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -15,6 +15,7 @@ import com.unciv.models.translations.tr import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.ICivilopediaText import com.unciv.ui.utils.Fonts +import com.unciv.ui.utils.toPercent import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap @@ -212,6 +213,49 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { return productionCost.toInt() } + override fun canBePurchasedWithStat( + cityInfo: CityInfo, + stat: Stat, + ignoreCityRequirements: Boolean + ): Boolean { + // May buy [unitFilter] units for [amount] [Stat] starting from the [eraName] at an increasing price ([amount]) + if (cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])") + .any { + matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + && cityInfo.civInfo.getEraNumber() >= ruleset.getEraNumber(it.params[4]) + && it.params[2] == stat.name + } + ) return true + + return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) + } + + private fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int { + return (baseCost + increaseCost / 2f * ( previouslyBought * previouslyBought + previouslyBought )).toInt() + } + + override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { + if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() + return ( + sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull() + // May buy [unitFilter] units for [amount] [Stat] starting from the [eraName] at an increasing price ([amount]) + + cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])") + .filter { + matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + && cityInfo.civInfo.getEraNumber() >= ruleset.getEraNumber(it.params[4]) + && it.params[2] == stat.name + }.map { + getCostForConstructionsIncreasingInPrice( + it.params[1].toInt(), + it.params[5].toInt(), + cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0 + ) + } + ).minOrNull() + } + override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? { var cost = getBaseBuyCost(cityInfo, stat)?.toDouble() if (cost == null) return null @@ -226,11 +270,11 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { for (unique in cityInfo.getMatchingUniques("[] cost of purchasing [] units []%")) { if (stat.name == unique.params[0] && matchesFilter(unique.params[1])) - cost *= 1f + unique.params[2].toFloat() / 100f + cost *= unique.params[2].toPercent() } for (unique in cityInfo.getMatchingUniques("[] cost of purchasing items in cities []%")) if (stat.name == unique.params[0]) - cost *= 1f + (unique.params[1].toFloat() / 100f) + cost *= unique.params[1].toPercent() return (cost / 10f).toInt() * 10 } @@ -316,13 +360,13 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { fun isBuildableIgnoringTechs(civInfo: CivilizationInfo) = getRejectionReason(civInfo, true) == "" - override fun postBuildEvent(cityConstructions: CityConstructions, wasBought: Boolean): Boolean { + override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { val civInfo = cityConstructions.cityInfo.civInfo val unit = civInfo.placeUnitNearTile(cityConstructions.cityInfo.location, name) - ?: return false // couldn't place the unit, so there's actually no unit =( + ?: return false // couldn't place the unit, so there's actually no unit =( //movement penalty - if (wasBought && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought")) + if (boughtWith != null && !civInfo.gameInfo.gameParameters.godMode && !unit.hasUnique("Can move immediately once bought")) unit.currentMovement = 0f // If this unit has special abilities that need to be kept track of, start doing so here @@ -330,6 +374,18 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { unit.religion = cityConstructions.cityInfo.religion.getMajorityReligionName() unit.setupAbilityUses(cityConstructions.cityInfo) } + + if (boughtWith != null && cityConstructions.cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])") + .filter { + matchesFilter(it.params[0]) + && cityConstructions.cityInfo.matchesFilter(it.params[3]) + && cityConstructions.cityInfo.civInfo.getEraNumber() >= ruleset.getEraNumber(it.params[4]) + && it.params[2] == boughtWith.name + }.any() + ) { + cityConstructions.cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] = + (cityConstructions.cityInfo.civInfo.boughtConstructionsWithGloballyIncreasingPrice[name] ?: 0) + 1 + } if (this.isCivilian()) return true // tiny optimization makes save files a few bytes smaller @@ -340,6 +396,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { fun addConstructionBonuses(unit: MapUnit, cityConstructions: CityConstructions) { val civInfo = cityConstructions.cityInfo.civInfo + @Suppress("LocalVariableName") var XP = 0 for (unique in @@ -354,22 +411,22 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { for (unique in cityConstructions.cityInfo.getMatchingUniques("All newly-trained [] units [] receive the [] promotion") .filter { cityConstructions.cityInfo.matchesFilter(it.params[1]) } + - // Deprecated since 3.15.9 + // Deprecated since 3.15.9 cityConstructions.cityInfo.getLocalMatchingUniques("All newly-trained [] units in this city receive the [] promotion") - // + // ) { val filter = unique.params[0] val promotion = unique.params.last() - if (unit.matchesFilter(filter) || - ( - filter == "relevant" && - civInfo.gameInfo.ruleSet.unitPromotions.values - .any { - it.name == promotion - && unit.type.name in it.unitTypes - } - ) + if (unit.matchesFilter(filter) + || ( + filter == "relevant" + && civInfo.gameInfo.ruleSet.unitPromotions.values + .any { + it.name == promotion + && unit.type.name in it.unitTypes + } + ) ) { unit.promotions.addPromotion(promotion, isFree = true) }