From 5730d1b80a76db631dad390abce08cc81be9b98c Mon Sep 17 00:00:00 2001 From: SeventhM <127357473+SeventhM@users.noreply.github.com> Date: Sat, 17 Feb 2024 11:41:05 -0800 Subject: [PATCH] Conditional for building in amount of cities (#11101) * Conditional for building in amount of cities * fix order of operations * Deprecate old require in amount cities uniques * import * Fix referring to the unique instead of the conditional * Fix yield message * I have no clue how this isn't imported, but I'mma assume this translates fine anyways * Rename text for unique * revert unique text * Whoops * Remove unnecessary puppet check * Move additional description to it own function * Split missing city text into its own function * Move ToDo comment * flip to be easier to read * Move onlyAvailable rejections to its own function to be easier to work with * Add in the word "of" --- .../jsons/Civ V - Gods & Kings/Buildings.json | 18 +++---- .../jsons/Civ V - Vanilla/Buildings.json | 16 +++--- core/src/com/unciv/models/ruleset/Building.kt | 41 ++++++++++++++- .../models/ruleset/unique/Conditionals.kt | 6 +++ .../unciv/models/ruleset/unique/UniqueType.kt | 6 ++- .../BuildingDescriptions.kt | 51 +++++++++++-------- 6 files changed, 97 insertions(+), 41 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json index 6e5767abeb..c1520fcb8e 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json @@ -252,7 +252,7 @@ "happiness": 5, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Colosseum] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Horseback Riding" }, { @@ -328,7 +328,7 @@ "culture": 1, "isNationalWonder": true, "percentStatBonus": {"science": 50}, - "uniques": ["Requires a [Library] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Philosophy" }, { @@ -355,7 +355,7 @@ "cost": 125, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Monument] in all cities", "[+25]% Great Person generation [in this city]", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "[+25]% Great Person generation [in this city]", "Cost increases by [30] per owned city"], "requiredTech": "Drama and Poetry" }, { @@ -417,7 +417,7 @@ "cost": 125, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Barracks] in all cities", "All newly-trained [non-[Air]] units [in this city] receive the [Morale] promotion", + "uniques": ["Only available ", "All newly-trained [non-[Air]] units [in this city] receive the [Morale] promotion", "Cost increases by [30] per owned city"], "requiredTech": "Iron Working" }, @@ -468,7 +468,7 @@ "cost": 125, "culture": 1, "faith": 8, - "uniques": ["Requires a [Temple] in all cities", "Cost increases by [30] per owned city", + "uniques": ["Only available ", "Cost increases by [30] per owned city", "[+100]% Natural religion spread [in this city]", "Hidden when religion is disabled", "Can only be built [in holy cities]"], "requiredTech": "Theology", "isNationalWonder": true @@ -489,7 +489,7 @@ "gold": 8, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Market] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Guilds" }, { @@ -572,7 +572,7 @@ "science": 3, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [University] in all cities", "Free Technology", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Free Technology", "Cost increases by [30] per owned city"], "requiredTech": "Education" }, { @@ -623,7 +623,7 @@ "production": 8, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Workshop] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Machinery" }, { @@ -774,7 +774,7 @@ "percentStatBonus": {"culture": 50}, "culture": 5, "isNationalWonder": true, - "uniques": ["Requires a [Opera House] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Architecture" }, { diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index 350b367f4b..d683838cf5 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -246,7 +246,7 @@ "happiness": 5, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Colosseum] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Construction" }, { @@ -300,7 +300,7 @@ "culture": 1, "isNationalWonder": true, "percentStatBonus": {"science": 50}, - "uniques": ["Requires a [Library] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Philosophy" }, { @@ -317,7 +317,7 @@ "cost": 125, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Monument] in all cities", "[+25]% Great Person generation [in this city]", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "[+25]% Great Person generation [in this city]", "Cost increases by [30] per owned city"], "requiredTech": "Philosophy" }, { @@ -360,7 +360,7 @@ "cost": 125, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Barracks] in all cities", "All newly-trained [non-[Air]] units [in this city] receive the [Morale] promotion", + "uniques": ["Only available ", "All newly-trained [non-[Air]] units [in this city] receive the [Morale] promotion", "Cost increases by [30] per owned city"], "requiredTech": "Iron Working" }, @@ -426,7 +426,7 @@ "gold": 8, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Market] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Currency" }, { @@ -509,7 +509,7 @@ "science": 3, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [University] in all cities", "Free Technology", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Free Technology", "Cost increases by [30] per owned city"], "requiredTech": "Education" }, { @@ -559,7 +559,7 @@ "production": 8, "culture": 1, "isNationalWonder": true, - "uniques": ["Requires a [Workshop] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Machinery" }, { @@ -656,7 +656,7 @@ "percentStatBonus": {"culture": 50}, "culture": 5, "isNationalWonder": true, - "uniques": ["Requires a [Opera House] in all cities", "Cost increases by [30] per owned city"], + "uniques": ["Only available ", "Cost increases by [30] per owned city"], "requiredTech": "Acoustics" }, { diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 01b8e78e97..e0f62969f6 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -8,6 +8,7 @@ import com.unciv.logic.civilization.Civilization 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.Conditionals import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique @@ -256,8 +257,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { yield(RejectionReasonType.Unbuildable.toInstance()) UniqueType.OnlyAvailable -> - if (!unique.conditionalsApply(civ, cityConstructions.city)) - yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance()) + yieldAll(onlyAvailableRejections(unique, cityConstructions)) UniqueType.Unavailable -> yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance()) @@ -428,6 +428,43 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { } } + private fun onlyAvailableRejections(unique: Unique, cityConstructions: CityConstructions): Sequence = sequence { + val civ = cityConstructions.city.civ + for (conditional in unique.conditionals) { + if (Conditionals.conditionalApplies(unique, conditional, StateForConditionals(civ, cityConstructions.city))) + continue + when (conditional.type) { + UniqueType.ConditionalBuildingBuiltAmount -> { + val building = civ.getEquivalentBuilding(conditional.params[0]).name + val amount = conditional.params[1].toInt() + val cityFilter = conditional.params[2] + val numberOfCities = civ.cities.count { + it.cityConstructions.containsBuildingOrEquivalent(building) && it.matchesFilter(cityFilter) + } + if (numberOfCities < amount) + { + yield(RejectionReasonType.RequiresBuildingInSomeCities.toInstance( + "Requires a [$building] in at least [$amount] cities" + + " ($numberOfCities/$numberOfCities)")) + } + } + UniqueType.ConditionalBuildingBuiltAll -> { + val building = civ.getEquivalentBuilding(conditional.params[0]).name + val cityFilter = conditional.params[1] + if(civ.cities.any { it.matchesFilter(cityFilter) + !it.isPuppet && !it.cityConstructions.containsBuildingOrEquivalent(building) + }) { + yield(RejectionReasonType.RequiresBuildingInAllCities.toInstance( + "Requires a [${building}] in all cities")) + } + } + else -> { + yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance()) + } + } + } + } + override fun isBuildable(cityConstructions: CityConstructions): Boolean = getRejectionReasons(cityConstructions).none() diff --git a/core/src/com/unciv/models/ruleset/unique/Conditionals.kt b/core/src/com/unciv/models/ruleset/unique/Conditionals.kt index 63d657a07f..b1b586dd8e 100644 --- a/core/src/com/unciv/models/ruleset/unique/Conditionals.kt +++ b/core/src/com/unciv/models/ruleset/unique/Conditionals.kt @@ -165,6 +165,12 @@ object Conditionals { UniqueType.ConditionalBuildingBuilt -> checkOnCiv { cities.any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } + UniqueType.ConditionalBuildingBuiltAll -> + checkOnCiv { cities.filter { it.matchesFilter(condition.params[1]) }.all { + it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } + UniqueType.ConditionalBuildingBuiltAmount -> + checkOnCiv { cities.count { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) + && it.matchesFilter(condition.params[2]) } >= condition.params[1].toInt() } UniqueType.ConditionalBuildingBuiltByAnybody -> checkOnGameInfo { getCities().any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 6b9c7f5cbf..a0bcd1b740 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -283,8 +283,10 @@ enum class UniqueType( CostIncreasesPerCity("Cost increases by [amount] per owned city", UniqueTarget.Building, UniqueTarget.Unit), CostIncreasesWhenBuilt("Cost increases by [amount] when built", UniqueTarget.Building, UniqueTarget.Unit), + @Deprecated("as of 4.10.17", ReplaceWith("Only available ")) RequiresBuildingInAllCities("Requires a [buildingFilter] in all cities", UniqueTarget.Building), - RequiresBuildingInSomeCities("Requires a [buildingFilter] in at least [amount] cities", UniqueTarget.Building), + @Deprecated("as of 4.10.17", ReplaceWith("Only available ")) + RequiresBuildingInSomeCities("Requires a [buildingFilter] in at least [positiveAmount] cities", UniqueTarget.Building), CanOnlyBeBuiltInCertainCities("Can only be built [cityFilter]", UniqueTarget.Building), MustHaveOwnedWithinTiles("Must have an owned [tileFilter] within [amount] tiles", UniqueTarget.Building), @@ -642,6 +644,8 @@ enum class UniqueType( ConditionalAfterGeneratingGreatProphet("after generating a Great Prophet", UniqueTarget.Conditional), ConditionalBuildingBuilt("if [buildingFilter] is constructed", UniqueTarget.Conditional), + ConditionalBuildingBuiltAll("if [buildingFilter] is constructed in all [cityFilter] cities", UniqueTarget.Conditional), + ConditionalBuildingBuiltAmount("if [buildingFilter] is constructed in at least [positiveAmount] of [cityFilter] cities", UniqueTarget.Conditional), ConditionalBuildingBuiltByAnybody("if [buildingFilter] is constructed by anybody", UniqueTarget.Conditional), ConditionalWithResource("with [resource]", UniqueTarget.Conditional), diff --git a/core/src/com/unciv/ui/objectdescriptions/BuildingDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/BuildingDescriptions.kt index d3e163fd0a..23aeef9645 100644 --- a/core/src/com/unciv/ui/objectdescriptions/BuildingDescriptions.kt +++ b/core/src/com/unciv/ui/objectdescriptions/BuildingDescriptions.kt @@ -45,7 +45,6 @@ object BuildingDescriptions { val isFree = city.civ.civConstructions.hasFreeBuilding(city, this) if (uniqueTo != null) translatedLines += if (replaces == null) "Unique to [$uniqueTo]".tr() else "Unique to [$uniqueTo], replaces [$replaces]".tr() - val missingUnique = getMatchingUniques(UniqueType.RequiresBuildingInAllCities).firstOrNull() if (isWonder) translatedLines += "Wonder".tr() if (isNationalWonder) translatedLines += "National Wonder".tr() if (!isFree) { @@ -59,21 +58,9 @@ object BuildingDescriptions { } } - // Inefficient in theory. In practice, buildings seem to have only a small handful of uniques. - val missingCities = if (missingUnique != null) - // TODO: Unify with rejection reasons? - city.civ.cities.filterNot { - it.isPuppet - || it.cityConstructions.containsBuildingOrEquivalent(missingUnique.params[0]) - } - else listOf() if (uniques.isNotEmpty()) { if (replacementTextForUniques.isNotEmpty()) translatedLines += replacementTextForUniques.tr() - else translatedLines += getUniquesStringsWithoutDisablers( - filterUniques = if (missingCities.isEmpty()) null - else { unique -> unique.type != UniqueType.RequiresBuildingInAllCities } - // Filter out the "Requires a [] in all cities" unique if any cities are still missing the required building, since in that case the list of cities will be appended at the end. - ).map { it.tr() } + else translatedLines += getUniquesStringsWithoutDisablers().map { it.tr() } } if (!stats.isEmpty()) translatedLines += stats.toString() @@ -93,16 +80,38 @@ object BuildingDescriptions { if (cityStrength != 0) translatedLines += "{City strength} +$cityStrength".tr() if (cityHealth != 0) translatedLines += "{City health} +$cityHealth".tr() if (maintenance != 0 && !isFree) translatedLines += "{Maintenance cost}: $maintenance {Gold}".tr() - if (showAdditionalInfo && missingCities.isNotEmpty()) { - // Could be red. But IMO that should be done by enabling GDX's ColorMarkupLanguage globally instead of adding a separate label. - translatedLines += "\n" + - "[${city.civ.getEquivalentBuilding(missingUnique!!.params[0])}] required:".tr() + - " " + missingCities.joinToString(", ") { it.name.tr(hideIcons = true) } - // Can't nest square bracket placeholders inside curlies, and don't see any way to define wildcard placeholders. So run translation explicitly on base text. - } + if (showAdditionalInfo) additionalDecription(building, city, translatedLines) return translatedLines.joinToString("\n").trim() } + fun additionalDecription (building: Building, city: City, lines: ArrayList) { + // Inefficient in theory. In practice, buildings seem to have only a small handful of uniques. + for (unique in building.uniqueObjects) { + if (unique.type == UniqueType.RequiresBuildingInAllCities) { + missingCityText(unique.params[0], city, "non-[Puppeted]", lines) + } + + else if (unique.type == UniqueType.OnlyAvailable) + for (conditional in unique.conditionals) { + if (conditional.type == UniqueType.ConditionalBuildingBuiltAll) { + missingCityText(conditional.params[0], city, conditional.params[1], lines) + } + } + } + } + + // TODO: Unify with rejection reasons? + fun missingCityText (building: String, city: City, filter: String, lines: ArrayList) { + val missingCities = city.civ.cities.filter { + it.matchesFilter(filter) && !it.cityConstructions.containsBuildingOrEquivalent(building) + } + // Could be red. But IMO that should be done by enabling GDX's ColorMarkupLanguage globally instead of adding a separate label. + if (missingCities.isNotEmpty()) lines += "\n" + + "[${city.civ.getEquivalentBuilding(building)}] required:".tr() + + " " + missingCities.joinToString(", ") { it.name.tr(hideIcons = true) } + // Can't nest square bracket placeholders inside curlies, and don't see any way to define wildcard placeholders. So run translation explicitly on base text. + } + /** * Lists differences: how a nation-unique Building compares to its replacement. *