diff --git a/android/assets/jsons/Civ V - Vanilla/Beliefs.json b/android/assets/jsons/Civ V - Vanilla/Beliefs.json index 3d6f95afc1..f8081838da 100644 --- a/android/assets/jsons/Civ V - Vanilla/Beliefs.json +++ b/android/assets/jsons/Civ V - Vanilla/Beliefs.json @@ -128,7 +128,7 @@ { "name": "Cathedrals", "type": "Follower", - "uniques": ["May buy [Cathedral] buildings for [200] [Faith] [in cities following this religion]"] + "uniques": ["May buy [Cathedral] buildings with [Faith] [in cities following this religion]"] }, { "name": "Choral Music", @@ -163,17 +163,17 @@ { "name": "Monasteries", "type": "Follower", - "uniques": ["May buy [Monastery] buildings for [200] [Faith] [in cities following this religion]"] + "uniques": ["May buy [Monastery] buildings with [Faith] [in cities following this religion]"] }, { "name": "Mosques", "type": "Follower", - "uniques": ["May buy [Mosque] buildings for [200] [Faith] [in cities following this religion]"] + "uniques": ["May buy [Mosque] buildings with [Faith] [in cities following this religion]"] }, { "name": "Pagodas", "type": "Follower", - "uniques": ["May buy [Pagoda] buildings for [200] [Faith] [in cities following this religion]"] + "uniques": ["May buy [Pagoda] buildings with [Faith] [in cities following this religion]"] }, { "name": "Peace Gardens", diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index ec5f245281..e48398d3e2 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -2,6 +2,7 @@ package com.unciv.logic.city import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.ruleset.IHasUniques +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.INamed import com.unciv.models.stats.Stat @@ -31,11 +32,11 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { if (stat == Stat.Gold) return !hasUnique(UniqueType.Unbuildable) // Can be purchased with [Stat] [cityFilter] if (getMatchingUniques(UniqueType.CanBePurchasedWithStat) - .any { it.params[0] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[1])) } + .any { it.params[0] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[1])) } ) return true // Can be purchased for [amount] [Stat] [cityFilter] - if (getMatchingUniques("Can be purchased for [] [] []") - .any { it.params[1] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[2])) } + if (getMatchingUniques(UniqueType.CanBePurchasedForAmountStat) + .any { it.params[1] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[2])) } ) return true return false } @@ -58,18 +59,24 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() + val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) + // Can be purchased for [amount] [Stat] [cityFilter] - val lowestCostUnique = getMatchingUniques("Can be purchased for [] [] []") + val lowestCostUnique = getMatchingUniques(UniqueType.CanBePurchasedForAmountStat, conditionalState) .filter { it.params[1] == stat.name && cityInfo.matchesFilter(it.params[2]) } .minByOrNull { it.params[0].toInt() } if (lowestCostUnique != null) return lowestCostUnique.params[0].toInt() // Can be purchased with [Stat] [cityFilter] - if (getMatchingUniques(UniqueType.CanBePurchasedWithStat) - .any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1])} + if (getMatchingUniques(UniqueType.CanBePurchasedWithStat, conditionalState) + .any { it.params[0] == stat.name && cityInfo.matchesFilter(it.params[1]) } ) return cityInfo.civInfo.getEra().baseUnitBuyCost return null } + + fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int { + return (baseCost + increaseCost / 2f * ( previouslyBought * previouslyBought + previouslyBought )).toInt() + } } diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index e735456fe4..a5f07c9ff7 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -6,10 +6,7 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Counter import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement -import com.unciv.models.ruleset.unique.Unique -import com.unciv.models.ruleset.unique.UniqueTarget -import com.unciv.models.ruleset.unique.UniqueTriggerActivation -import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.ruleset.unique.* import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.fillPlaceholders @@ -343,27 +340,76 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean { if (stat == Stat.Gold && isAnyWonder()) return false - // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] - if (cityInfo != null && 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) + if (cityInfo == null) return super.canBePurchasedWithStat(cityInfo, stat) + + val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) + return ( + cityInfo.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost, conditionalState) + .any { + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + } + || cityInfo.getMatchingUniques(UniqueType.BuyBuildingsByProductionCost, conditionalState) + .any { it.params[1] == stat.name && matchesFilter(it.params[0]) } + || cityInfo.getMatchingUniques(UniqueType.BuyBuildingsWithStat, conditionalState) + .any { + it.params[1] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[2]) + } + || cityInfo.getMatchingUniques(UniqueType.BuyBuildingsForAmountStat, conditionalState) + .any { + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + } + || return super.canBePurchasedWithStat(cityInfo, stat) + ) } 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 [] [] []") + val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) + + return sequence { + val baseCost = super.getBaseBuyCost(cityInfo, stat) + if (baseCost != null) + yield(baseCost) + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost, conditionalState) .filter { - it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter( - it.params[3] + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + }.map { + getCostForConstructionsIncreasingInPrice( + it.params[1].toInt(), + it.params[4].toInt(), + cityInfo.civInfo.civConstructions.boughtItemsWithIncreasingPrice[name] ?: 0 ) } - .map { it.params[1].toInt() } - ).minOrNull() + ) + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyBuildingsByProductionCost, conditionalState) + .filter { it.params[1] == stat.name && matchesFilter(it.params[0]) } + .map { getProductionCost(cityInfo.civInfo) * it.params[2].toInt() } + ) + if (cityInfo.getMatchingUniques(UniqueType.BuyBuildingsWithStat, conditionalState) + .any { + it.params[1] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[2]) + } + ) { + yield(cityInfo.civInfo.getEra().baseUnitBuyCost) + } + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyBuildingsForAmountStat, conditionalState) + .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? { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index c45f682d74..1208800906 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -150,12 +150,20 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { FreeExtraBeliefs("May choose [amount] additional [beliefType] beliefs when [foundingOrEnhancing] a religion", UniqueTarget.Global), FreeExtraAnyBeliefs("May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion", UniqueTarget.Global), + // There is potential to merge these BuyUnitsIncreasingCost("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount])", UniqueTarget.Global), BuyBuildingsIncreasingCost("May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount])", UniqueTarget.Global), - @Deprecated("As of 3.17.9", ReplaceWith ("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount]) ")) - BuyUnitsIncreasingCostEra("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] starting from the [era] at an increasing price ([amount])", UniqueTarget.Global), + BuyUnitsForAmountStat("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), + BuyBuildingsForAmountStat("May buy [buildingFilter] buildings for [amount] [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), + BuyUnitsWithStat("May buy [baseUnitFilter] units with [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), + BuyBuildingsWithStat("May buy [buildingFilter] buildings with [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), + BuyUnitsByProductionCost("May buy [baseUnitFilter] units with [stat] for [amount] times their normal Production cost", UniqueTarget.FollowerBelief, UniqueTarget.Global), + BuyBuildingsByProductionCost("May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost", UniqueTarget.FollowerBelief, UniqueTarget.Global), + @Deprecated("As of 3.17.9", ReplaceWith ("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount]) ")) + BuyUnitsIncreasingCostEra("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] starting from the [era] at an increasing price ([amount])", UniqueTarget.Global), + MayanGainGreatPerson("Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once.", UniqueTarget.Nation), MayanCalendarDisplay("Once The Long Count activates, the year on the world screen displays as the traditional Mayan Long Count.", UniqueTarget.Nation), @@ -165,6 +173,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { Unbuildable("Unbuildable", UniqueTarget.Building, UniqueTarget.Unit), CannotBePurchased("Cannot be purchased", UniqueTarget.Building, UniqueTarget.Unit), CanBePurchasedWithStat("Can be purchased with [stat] [cityFilter]", UniqueTarget.Building, UniqueTarget.Unit), + CanBePurchasedForAmountStat("Can be purchased for [amount] [stat] [cityFilter]", UniqueTarget.Building, UniqueTarget.Unit), ///////////////////////////////////////// BUILDING UNIQUES ///////////////////////////////////////// diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 5abe6abda1..6bffdf2ad8 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -250,53 +250,56 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { } override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean { - // May buy [unitFilter] units for [amount] [Stat] [cityFilter] starting from the [eraName] at an increasing price ([amount]) - if (cityInfo != null && cityInfo.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.eras[it.params[4]]!!.eraNumber - && it.params[2] == stat.name - } - ) return true + if (cityInfo == null) return super.canBePurchasedWithStat(cityInfo, stat) + val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) - // May buy [unitFilter] units for [amount] [Stat] [cityFilter] at an increasing price ([amount]) - if (cityInfo != null && cityInfo.getMatchingUniques("May buy [] units for [] [] [] at an increasing price ([])") - .any { - matchesFilter(it.params[0]) - && cityInfo.matchesFilter(it.params[3]) - && it.params[2] == stat.name - } - ) return true - - if (cityInfo != null && cityInfo.getMatchingUniques( - UniqueType.BuyUnitsByProductionCost, - stateForConditionals = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) - ).any { - matchesFilter(it.params[0]) - && it.params[1] == stat.name - } - ) return true - - return super.canBePurchasedWithStat(cityInfo, stat) - } - - private fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int { - return (baseCost + increaseCost / 2f * ( previouslyBought * previouslyBought + previouslyBought )).toInt() + return ( + cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCostEra, conditionalState) + .any { + it.params[2] == stat.name + && cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + } + || cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState) + .any { + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + } + || cityInfo.getMatchingUniques(UniqueType.BuyUnitsByProductionCost, conditionalState) + .any { it.params[1] == stat.name && matchesFilter(it.params[0]) } + || cityInfo.getMatchingUniques(UniqueType.BuyUnitsWithStat, conditionalState) + .any { + it.params[1] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[2]) + } + || cityInfo.getMatchingUniques(UniqueType.BuyUnitsForAmountStat, conditionalState) + .any { + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + } + || return super.canBePurchasedWithStat(cityInfo, stat) + ) } override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { if (stat == Stat.Gold) return getBaseGoldCost(cityInfo.civInfo).toInt() val conditionalState = StateForConditionals(civInfo = cityInfo.civInfo, cityInfo = cityInfo) - return ( - sequenceOf(super.getBaseBuyCost(cityInfo, stat)).filterNotNull() + + return sequence { + val baseCost = super.getBaseBuyCost(cityInfo, stat) + if (baseCost != null) + yield(baseCost) // Deprecated since 3.17.9 - + (cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCostEra, conditionalState) + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCostEra, conditionalState) .filter { - matchesFilter(it.params[0]) + it.params[2] == stat.name + && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) && cityInfo.civInfo.getEraNumber() >= ruleset.eras[it.params[4]]!!.eraNumber - && it.params[2] == stat.name }.map { getCostForConstructionsIncreasingInPrice( it.params[1].toInt(), @@ -306,28 +309,40 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { } ) // - + (cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState) + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState) .filter { - matchesFilter(it.params[0]) + it.params[2] == stat.name + && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) - && it.params[2] == stat.name }.map { getCostForConstructionsIncreasingInPrice( it.params[1].toInt(), it.params[4].toInt(), cityInfo.civInfo.civConstructions.boughtItemsWithIncreasingPrice[name] ?: 0 - ) + ) } ) - + (cityInfo.getMatchingUniques(UniqueType.BuyUnitsByProductionCost, conditionalState) - .filter { + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyUnitsByProductionCost, conditionalState) + .filter { it.params[1] == stat.name && matchesFilter(it.params[0]) } + .map { getProductionCost(cityInfo.civInfo) * it.params[2].toInt() } + ) + if (cityInfo.getMatchingUniques(UniqueType.BuyUnitsWithStat, conditionalState) + .any { it.params[1] == stat.name && matchesFilter(it.params[0]) - }.map { - getProductionCost(cityInfo.civInfo) * it.params[2].toInt() + && cityInfo.matchesFilter(it.params[2]) } + ) { + yield(cityInfo.civInfo.getEra().baseUnitBuyCost) + } + yieldAll(cityInfo.getMatchingUniques(UniqueType.BuyUnitsForAmountStat, conditionalState) + .filter { + it.params[2] == stat.name + && matchesFilter(it.params[0]) + && cityInfo.matchesFilter(it.params[3]) + }.map { it.params[1].toInt() } ) - ).minOrNull() + }.minOrNull() } override fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? {