mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-24 03:53:12 -04:00
Expand CanOnlyBeBuiltInCertainCities to include Units and convert to use Conditionals (#11274)
* Remove Transform requirement checks * Add back in requirement for OnlyAvailable New BuildableOnly unique * Instead of a new unique, expand CanOnlyBeBuiltInCertainCities to instead take conditionals * Rename to notMetRejections and copy to BaseUnit Add CanOnlyBeBuiltInSpecificCities to constructionRejectionReasonType * Setup CanOnlyBeBuiltInCertainCities as depreciated (renamed CanOnlyBeBuiltInCertainCities_dep) * Redirect Depreciation * Quick Camel Case rename * Function renaming and moving Unique to general Construction Uniques * spelling * Move Unique. Update Error message * version
This commit is contained in:
parent
1542b92e63
commit
e9c3350ec5
@ -260,7 +260,7 @@
|
||||
"maintenance": 4,
|
||||
"hurryCostModifier": 50,
|
||||
"uniques": ["Remove extra unhappiness from annexed cities",
|
||||
"Can only be built [in annexed cities]"],
|
||||
"Can only be built <in [Annexed] cities>"],
|
||||
"requiredTech": "Mathematics"
|
||||
},
|
||||
{
|
||||
@ -469,7 +469,7 @@
|
||||
"culture": 1,
|
||||
"faith": 8,
|
||||
"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]"],
|
||||
"[+100]% Natural religion spread [in this city]", "Hidden when religion is disabled", "Can only be built <in [Holy] cities>"],
|
||||
"requiredTech": "Theology",
|
||||
"isNationalWonder": true
|
||||
},
|
||||
|
@ -221,7 +221,7 @@
|
||||
"maintenance": 4,
|
||||
"hurryCostModifier": 50,
|
||||
"uniques": ["Remove extra unhappiness from annexed cities",
|
||||
"Can only be built [in annexed cities]"],
|
||||
"Can only be built <in [Annexed] cities>"],
|
||||
"requiredTech": "Mathematics"
|
||||
},
|
||||
{
|
||||
|
@ -251,7 +251,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
yield(RejectionReasonType.AlreadyBuilt.toInstance())
|
||||
|
||||
for (unique in uniqueObjects) {
|
||||
if (unique.type != UniqueType.OnlyAvailable &&
|
||||
// skip uniques that don't have conditionals apply
|
||||
// EXCEPT for [UniqueType.OnlyAvailable] and [UniqueType.CanOnlyBeBuiltInCertainCities]
|
||||
// since they trigger (reject) only if conditionals ARE NOT met
|
||||
if (unique.type != UniqueType.OnlyAvailable && unique.type != UniqueType.CanOnlyBeBuiltWhen &&
|
||||
!unique.conditionalsApply(StateForConditionals(civ, cityConstructions.city))) continue
|
||||
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
@ -262,7 +265,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
yield(RejectionReasonType.Unbuildable.toInstance())
|
||||
|
||||
UniqueType.OnlyAvailable ->
|
||||
yieldAll(onlyAvailableRejections(unique, cityConstructions))
|
||||
yieldAll(notMetRejections(unique, cityConstructions))
|
||||
|
||||
UniqueType.CanOnlyBeBuiltWhen ->
|
||||
yieldAll(notMetRejections(unique, cityConstructions, true))
|
||||
|
||||
UniqueType.Unavailable ->
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
@ -433,9 +439,14 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onlyAvailableRejections(unique: Unique, cityConstructions: CityConstructions): Sequence<RejectionReason> = sequence {
|
||||
/**
|
||||
* Handles inverted conditional rejections and cumulative conditional reporting
|
||||
* See also [com.unciv.models.ruleset.unit.BaseUnit.notMetRejections]
|
||||
*/
|
||||
private fun notMetRejections(unique: Unique, cityConstructions: CityConstructions, built: Boolean=false): Sequence<RejectionReason> = sequence {
|
||||
val civ = cityConstructions.city.civ
|
||||
for (conditional in unique.conditionals) {
|
||||
// We yield a rejection only when conditionals are NOT met
|
||||
if (Conditionals.conditionalApplies(unique, conditional, StateForConditionals(civ, cityConstructions.city)))
|
||||
continue
|
||||
when (conditional.type) {
|
||||
@ -464,7 +475,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
if (built)
|
||||
yield(RejectionReasonType.CanOnlyBeBuiltInSpecificCities.toInstance(unique.text))
|
||||
else
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,12 +141,14 @@ class RejectionReason(val type: RejectionReasonType,
|
||||
RejectionReasonType.MaxNumberBuildable,
|
||||
)
|
||||
private val orderedImportantRejectionTypes = listOf(
|
||||
RejectionReasonType.ShouldNotBeDisplayed,
|
||||
RejectionReasonType.WonderBeingBuiltElsewhere,
|
||||
RejectionReasonType.NationalWonderBeingBuiltElsewhere,
|
||||
RejectionReasonType.RequiresBuildingInAllCities,
|
||||
RejectionReasonType.RequiresBuildingInThisCity,
|
||||
RejectionReasonType.RequiresBuildingInSomeCity,
|
||||
RejectionReasonType.RequiresBuildingInSomeCities,
|
||||
RejectionReasonType.CanOnlyBeBuiltInSpecificCities,
|
||||
RejectionReasonType.CannotBeBuiltUnhappiness,
|
||||
RejectionReasonType.PopulationRequirement,
|
||||
RejectionReasonType.ConsumesResources,
|
||||
@ -154,11 +156,12 @@ class RejectionReason(val type: RejectionReasonType,
|
||||
RejectionReasonType.MaxNumberBuildable,
|
||||
RejectionReasonType.NoPlaceToPutUnit,
|
||||
)
|
||||
// Used for units spawned, not built
|
||||
// Exceptions. Used for units spawned/upgrade path, not built
|
||||
private val constructionRejectionReasonType = listOf(
|
||||
RejectionReasonType.Unbuildable,
|
||||
RejectionReasonType.CannotBeBuiltUnhappiness,
|
||||
RejectionReasonType.CannotBeBuilt,
|
||||
RejectionReasonType.CanOnlyBeBuiltInSpecificCities,
|
||||
)
|
||||
}
|
||||
|
||||
@ -178,7 +181,7 @@ enum class RejectionReasonType(val shouldShow: Boolean, val errorMessage: String
|
||||
MustNotBeNextToTile(false, "Must not be next to a specific tile"),
|
||||
MustOwnTile(false, "Must own a specific tile close by"),
|
||||
WaterUnitsInCoastalCities(false, "May only built water units in coastal cities"),
|
||||
CanOnlyBeBuiltInSpecificCities(false, "Can only be built in specific cities"),
|
||||
CanOnlyBeBuiltInSpecificCities(false, "Build requirements not met in this city"),
|
||||
MaxNumberBuildable(false, "Maximum number have been built or are being constructed"),
|
||||
|
||||
UniqueToOtherNation(false, "Unique to another nation"),
|
||||
|
@ -47,7 +47,7 @@ interface IHasUniques : INamed {
|
||||
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
|
||||
getMatchingUniques(uniqueType.placeholderText, stateForConditionals).any()
|
||||
|
||||
fun availabilityUniques(): Sequence<Unique> = getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals)
|
||||
fun availabilityUniques(): Sequence<Unique> = getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals) + getMatchingUniques(UniqueType.CanOnlyBeBuiltWhen, StateForConditionals.IgnoreConditionals)
|
||||
|
||||
fun techsRequiredByUniques(): Sequence<String> {
|
||||
return availabilityUniques()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.models.ruleset.unique
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.models.ruleset.RejectionReasonType
|
||||
import com.unciv.models.ruleset.validation.RulesetErrorSeverity
|
||||
import com.unciv.models.ruleset.validation.RulesetValidator
|
||||
import com.unciv.models.ruleset.validation.Suppression
|
||||
@ -257,18 +258,24 @@ enum class UniqueType(
|
||||
|
||||
///////////////////////////////////////// region 02 CONSTRUCTION UNIQUES /////////////////////////////////////////
|
||||
|
||||
Unbuildable("Unbuildable", UniqueTarget.Building, UniqueTarget.Unit, UniqueTarget.Improvement),
|
||||
Unbuildable("Unbuildable", UniqueTarget.Building, UniqueTarget.Unit, UniqueTarget.Improvement,
|
||||
docDescription = "Blocks from being built, possibly by conditional. However it can still appear in the menu and be bought with other means such as Gold or Faith"),
|
||||
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),
|
||||
MaxNumberBuildable("Limited to [amount] per Civilization", UniqueTarget.Building, UniqueTarget.Unit),
|
||||
HiddenBeforeAmountPolicies("Hidden until [amount] social policy branches have been completed", UniqueTarget.Building, UniqueTarget.Unit),
|
||||
// Meant to be used together with conditionals, like "Only available <after adopting [policy]> <while the empire is happy>"
|
||||
/** A special unique, as it only activates when it has conditionals that *do not* apply */
|
||||
/** A special unique, as it only activates [RejectionReasonType] when it has conditionals that *do not* apply.
|
||||
* Meant to be used together with conditionals, like "Buildable only <after adopting [policy]> <while the empire is happy>".
|
||||
* Restricts Upgrade/Transform pathways.
|
||||
* @See [CanOnlyBeBuiltWhen]
|
||||
*/
|
||||
OnlyAvailable("Only available", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief),
|
||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins, UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief,
|
||||
docDescription = "Meant to be used together with conditionals, like \"Only available <after adopting [policy]> <while the empire is happy>\". Only allows Building when ALL conditionals are met. Will also block Upgrade and Transform actions. See also CanOnlyBeBuiltWhen"),
|
||||
Unavailable("Unavailable", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Improvement,
|
||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins),
|
||||
UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion, UniqueTarget.Ruins,
|
||||
docDescription = "Meant to be used together with conditionals, like \"Unavailable <after generating a Great Prophet>\"."),
|
||||
|
||||
ConvertFoodToProductionWhenConstructed("Excess Food converted to Production when under construction", UniqueTarget.Building, UniqueTarget.Unit),
|
||||
RequiresPopulation("Requires at least [amount] population", UniqueTarget.Building, UniqueTarget.Unit),
|
||||
@ -288,7 +295,14 @@ enum class UniqueType(
|
||||
RequiresBuildingInAllCities("Requires a [buildingFilter] in all 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),
|
||||
@Deprecated("as of 4.10.18", ReplaceWith("Can only be built <in [cityFilter] cities>"))
|
||||
CanOnlyBeBuiltInCertainCities("Can only be built [cityFilter]", UniqueTarget.Building),
|
||||
/** Triggers [RejectionReasonType] when any conditional does NOT apply.
|
||||
* Doesn't restrict Upgrade/Transform pathways.
|
||||
* @see [OnlyAvailable]
|
||||
*/
|
||||
CanOnlyBeBuiltWhen("Can only be built", UniqueTarget.Building, UniqueTarget.Unit,
|
||||
docDescription = "Meant to be used together with conditionals, like \"Can only be built <after adopting [policy]> <while the empire is happy>\". Only allows Building when ALL conditionals are met. Will also NOT block Upgrade and Transform actions. See also OnlyAvailable"),
|
||||
|
||||
MustHaveOwnedWithinTiles("Must have an owned [tileFilter] within [amount] tiles", UniqueTarget.Building),
|
||||
|
||||
@ -912,7 +926,7 @@ enum class UniqueType(
|
||||
StrengthWithinTilesOfTile("+[amount]% Strength if within [amount2] tiles of a [tileFilter]", UniqueTarget.Global),
|
||||
@Deprecated("as of 3.19.7", ReplaceWith("[stats] <with [resource]>"), DeprecationLevel.ERROR)
|
||||
StatsWithResource("[stats] with [resource]", UniqueTarget.Building),
|
||||
@Deprecated("as of 3.19.16", ReplaceWith("Can only be built [in annexed cities]"), DeprecationLevel.ERROR)
|
||||
@Deprecated("as of 3.19.16", ReplaceWith("Can only be built <in [Annexed] cities>"), DeprecationLevel.ERROR)
|
||||
CanOnlyBeBuiltInAnnexedCities("Can only be built in annexed cities", UniqueTarget.Building),
|
||||
@Deprecated("as of 4.0.3", ReplaceWith("Defense bonus when embarked <for [All] units>"), DeprecationLevel.ERROR)
|
||||
DefenceBonusWhenEmbarkedCivwide("Embarked units can defend themselves", UniqueTarget.Global),
|
||||
|
@ -12,6 +12,7 @@ import com.unciv.models.ruleset.RejectionReason
|
||||
import com.unciv.models.ruleset.RejectionReasonType
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetObject
|
||||
import com.unciv.models.ruleset.unique.Conditionals
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
@ -170,8 +171,10 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
val civInfo = cityConstructions.city.civ
|
||||
|
||||
for (unique in getMatchingUniques(UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals))
|
||||
if (!unique.conditionalsApply(civInfo, cityConstructions.city))
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
yieldAll(notMetRejections(unique, cityConstructions))
|
||||
|
||||
for (unique in getMatchingUniques(UniqueType.CanOnlyBeBuiltWhen, StateForConditionals.IgnoreConditionals))
|
||||
yieldAll(notMetRejections(unique, cityConstructions, true))
|
||||
|
||||
for (unique in getMatchingUniques(UniqueType.Unavailable, StateForConditionals(civInfo, cityConstructions.city)))
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
@ -238,6 +241,52 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of [com.unciv.models.ruleset.Building.notMetRejections] to handle inverted conditionals.
|
||||
* Also custom handles [UniqueType.ConditionalBuildingBuiltAmount], and
|
||||
* [UniqueType.ConditionalBuildingBuiltAll]
|
||||
*/
|
||||
private fun notMetRejections(unique: Unique, cityConstructions: CityConstructions, built: Boolean=false): Sequence<RejectionReason> = sequence {
|
||||
val civ = cityConstructions.city.civ
|
||||
for (conditional in unique.conditionals) {
|
||||
// We yield a rejection only when conditionals are NOT met
|
||||
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 -> {
|
||||
if (built)
|
||||
yield(RejectionReasonType.CanOnlyBeBuiltInSpecificCities.toInstance(unique.text))
|
||||
else
|
||||
yield(RejectionReasonType.ShouldNotBeDisplayed.toInstance())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isBuildable(civInfo: Civilization) = getRejectionReasons(civInfo).none()
|
||||
|
||||
override fun isBuildable(cityConstructions: CityConstructions): Boolean =
|
||||
|
@ -80,18 +80,18 @@ 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) additionalDecription(building, city, translatedLines)
|
||||
if (showAdditionalInfo) additionalDescription(building, city, translatedLines)
|
||||
return translatedLines.joinToString("\n").trim()
|
||||
}
|
||||
|
||||
fun additionalDecription (building: Building, city: City, lines: ArrayList<String>) {
|
||||
fun additionalDescription (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)
|
||||
else if (unique.type == UniqueType.OnlyAvailable || unique.type == UniqueType.CanOnlyBeBuiltWhen)
|
||||
for (conditional in unique.conditionals) {
|
||||
if (conditional.type == UniqueType.ConditionalBuildingBuiltAll) {
|
||||
missingCityText(conditional.params[0], city, conditional.params[1], lines)
|
||||
|
@ -268,6 +268,11 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
var maxButtonWidth = constructionsQueueTable.width
|
||||
for (dto in constructionButtonDTOList) {
|
||||
|
||||
/** filter out showing buildings that have RequiresBuildingInThisCity
|
||||
* rejection (eg requiredBuilding entry) which are buildable.
|
||||
* The rejection for RequiresBuildingInThisCity isn't yielded if
|
||||
* the prerequisite is in the queue
|
||||
*/
|
||||
if (dto.construction is Building
|
||||
&& dto.rejectionReason?.type == RejectionReasonType.RequiresBuildingInThisCity
|
||||
&& constructionButtonDTOList.any {
|
||||
|
@ -337,12 +337,11 @@ object UnitActionsFromUniques {
|
||||
for (unique in unit.getMatchingUniques(UniqueType.CanTransform, stateForConditionals)) {
|
||||
val unitToTransformTo = civInfo.getEquivalentUnit(unique.params[0])
|
||||
|
||||
// Respect OnlyAvailable criteria
|
||||
if (unitToTransformTo.getMatchingUniques(
|
||||
UniqueType.OnlyAvailable,
|
||||
StateForConditionals.IgnoreConditionals
|
||||
)
|
||||
.any { !it.conditionalsApply(stateForConditionals) })
|
||||
continue
|
||||
UniqueType.OnlyAvailable, StateForConditionals.IgnoreConditionals
|
||||
).any { !it.conditionalsApply(stateForConditionals) }
|
||||
) continue
|
||||
|
||||
// Check _new_ resource requirements
|
||||
// Using Counter to aggregate is a bit exaggerated, but - respect the mad modder.
|
||||
|
Loading…
x
Reference in New Issue
Block a user