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"
This commit is contained in:
SeventhM 2024-02-17 11:41:05 -08:00 committed by GitHub
parent df7072b550
commit 5730d1b80a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 41 deletions

View File

@ -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 <if [Colosseum] is constructed in all [non-[Puppeted]] cities>", "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 <if [Library] is constructed in all [non-[Puppeted]] cities>", "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 <if [Monument] is constructed in all [non-[Puppeted]] cities>", "[+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 <if [Barracks] is constructed in all [non-[Puppeted]] cities>", "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 <if [Temple] is constructed in all [non-[Puppeted]] cities>", "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 <if [Market] is constructed in all [non-[Puppeted]] cities>", "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 <if [University] is constructed in all [non-[Puppeted]] cities>", "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 <if [Workshop] is constructed in all [non-[Puppeted]] cities>", "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 <if [Opera House] is constructed in all [non-[Puppeted]] cities>", "Cost increases by [30] per owned city"],
"requiredTech": "Architecture"
},
{

View File

@ -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 <if [Colosseum] is constructed in all [non-[Puppeted]] cities>", "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 <if [Library] is constructed in all [non-[Puppeted]] cities>", "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 <if [Monument] is constructed in all [non-[Puppeted]] cities>", "[+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 <if [Barracks] is constructed in all [non-[Puppeted]] cities>", "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 <if [Market] is constructed in all [non-[Puppeted]] cities>", "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 <if [University] is constructed in all [non-[Puppeted]] cities>", "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 <if [Workshop] is constructed in all [non-[Puppeted]] cities>", "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 <if [Opera House] is constructed in all [non-[Puppeted]] cities>", "Cost increases by [30] per owned city"],
"requiredTech": "Acoustics"
},
{

View File

@ -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<RejectionReason> = 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()

View File

@ -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]) } }

View File

@ -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 <if [buildingFilter] is constructed in all [non-[Puppeted]] cities>"))
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 <if [buildingFilter] is constructed in at least [positiveAmount] of [All] cities>"))
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),

View File

@ -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<String>) {
// 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<String>) {
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.
*