Refactor more code, hopefully increasing maintainability (#5062)

* Fixed great person gift formula, confusing boolean, "great person" filter

* Refactored getRejectionReason to return a hashSet of reasons instead of a random one
This commit is contained in:
Xander Lenstra 2021-09-02 15:37:40 +02:00 committed by GitHub
parent 2e43637144
commit fcc335b78a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 408 additions and 225 deletions

View File

@ -1064,7 +1064,8 @@
"name": "Apollo Program", "name": "Apollo Program",
"cost": 750, "cost": 750,
"isNationalWonder": true, "isNationalWonder": true,
"uniques": ["Enables construction of Spaceship parts", "Triggers a global alert upon completion"], "uniques": ["Enables construction of Spaceship parts", "Triggers a global alert upon completion",
"Hidden when [Scientific] Victory is disabled"],
"requiredTech": "Rocketry" "requiredTech": "Rocketry"
}, },
@ -1091,7 +1092,7 @@
"name": "SS Cockpit", "name": "SS Cockpit",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"requiredTech": "Satellites", "requiredTech": "Satellites",
"uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased"] "uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased", "Hidden when [Scientific] Victory is disabled"]
}, },
{ {
"name": "Hubble Space Telescope", "name": "Hubble Space Telescope",
@ -1109,7 +1110,7 @@
"name": "SS Booster", "name": "SS Booster",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"requiredTech": "Advanced Ballistics", "requiredTech": "Advanced Ballistics",
"uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased"] "uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased", "Hidden when [Scientific] Victory is disabled"]
}, },
{ {
"name": "Spaceship Factory", "name": "Spaceship Factory",
@ -1136,13 +1137,13 @@
"name": "SS Engine", "name": "SS Engine",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"requiredTech": "Particle Physics", "requiredTech": "Particle Physics",
"uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased"] "uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased", "Hidden when [Scientific] Victory is disabled"]
}, },
{ {
"name": "SS Stasis Chamber", "name": "SS Stasis Chamber",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
"requiredTech": "Nanotechnology", "requiredTech": "Nanotechnology",
"uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased"] "uniques": ["Spaceship part", "Triggers a global alert upon completion", "Cannot be purchased", "Hidden when [Scientific] Victory is disabled"]
}, },
// All Eras // All Eras

View File

@ -375,14 +375,10 @@ class CityConstructions {
// Perpetual constructions should always still be valid (I hope) // Perpetual constructions should always still be valid (I hope)
if (construction is PerpetualConstruction) continue if (construction is PerpetualConstruction) continue
val rejectionReason = val rejectionReasons =
(construction as INonPerpetualConstruction).getRejectionReason(this) (construction as INonPerpetualConstruction).getRejectionReasons(this)
if (rejectionReason.endsWith("lready built") if (rejectionReasons.hasAReasonToBeRemovedFromQueue()) {
|| rejectionReason.startsWith("Cannot be built with")
|| rejectionReason.startsWith("Don't need to build any more")
|| rejectionReason.startsWith("Obsolete")
) {
if (construction is Building) { if (construction is Building) {
// Production put into wonders gets refunded // Production put into wonders gets refunded
if (construction.isWonder && getWorkDone(constructionName) != 0) { if (construction.isWonder && getWorkDone(constructionName) != 0) {
@ -392,7 +388,7 @@ class CityConstructions {
} }
} else if (construction is BaseUnit) { } else if (construction is BaseUnit) {
// Production put into upgradable units gets put into upgraded version // Production put into upgradable units gets put into upgraded version
if (rejectionReason.startsWith("Obsolete") && construction.upgradesTo != null) { if (rejectionReasons.all { it == RejectionReason.Obsoleted } && construction.upgradesTo != null) {
// I'd love to use the '+=' operator but since 'inProgressConstructions[...]' can be null, kotlin doesn't allow me to // I'd love to use the '+=' operator but since 'inProgressConstructions[...]' can be null, kotlin doesn't allow me to
if (!inProgressConstructions.contains(construction.upgradesTo)) { if (!inProgressConstructions.contains(construction.upgradesTo)) {
inProgressConstructions[construction.upgradesTo!!] = getWorkDone(constructionName) inProgressConstructions[construction.upgradesTo!!] = getWorkDone(constructionName)

View File

@ -21,7 +21,7 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
fun getProductionCost(civInfo: CivilizationInfo): Int fun getProductionCost(civInfo: CivilizationInfo): Int
fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int? fun getStatBuyCost(cityInfo: CityInfo, stat: Stat): Int?
fun getRejectionReason(cityConstructions: CityConstructions): String fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons
fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious. fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat? = null): Boolean // Yes I'm hilarious.
fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> { fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> {
@ -31,26 +31,25 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
return uniqueObjects.any { it.placeholderText == uniqueTemplate } return uniqueObjects.any { it.placeholderText == uniqueTemplate }
} }
fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean = false): Boolean { fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean {
if (stat in listOf(Stat.Production, Stat.Happiness)) return false if (stat in listOf(Stat.Production, Stat.Happiness)) return false
if ("Cannot be purchased" in uniques) return false if ("Cannot be purchased" in uniques) return false
if (stat == Stat.Gold) return !uniques.contains("Unbuildable") if (stat == Stat.Gold) return !uniques.contains("Unbuildable")
// Can be purchased with [Stat] [cityFilter] // Can be purchased with [Stat] [cityFilter]
if (getMatchingUniques("Can be purchased with [] []") if (getMatchingUniques("Can be purchased with [] []")
.any { it.params[0] == stat.name && (ignoreCityRequirements || cityInfo.matchesFilter(it.params[1])) } .any { it.params[0] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[1])) }
) return true ) return true
// Can be purchased for [amount] [Stat] [cityFilter] // Can be purchased for [amount] [Stat] [cityFilter]
if (getMatchingUniques("Can be purchased for [] [] []") if (getMatchingUniques("Can be purchased for [] [] []")
.any { it.params[1] == stat.name && ( ignoreCityRequirements || cityInfo.matchesFilter(it.params[2])) } .any { it.params[1] == stat.name && (cityInfo != null && cityInfo.matchesFilter(it.params[2])) }
) return true ) return true
return false return false
} }
/** Checks if the construction should be purchasable, not whether it can be bought with a stat at all */ /** Checks if the construction should be purchasable, not whether it can be bought with a stat at all */
fun isPurchasable(cityConstructions: CityConstructions): Boolean { fun isPurchasable(cityConstructions: CityConstructions): Boolean {
val rejectionReason = getRejectionReason(cityConstructions) val rejectionReasons = getRejectionReasons(cityConstructions)
return rejectionReason == "" return rejectionReasons.all { it == RejectionReason.Unbuildable }
|| rejectionReason == "Can only be purchased"
} }
fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean { fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean {
@ -81,6 +80,100 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
class RejectionReasons(): HashSet<RejectionReason>() {
private val techPolicyEraWonderRequirements = hashSetOf(
RejectionReason.Obsoleted,
RejectionReason.RequiresTech,
RejectionReason.RequiresPolicy,
RejectionReason.MorePolicyBranches,
RejectionReason.RequiresBuildingInSomeCity
)
fun filterTechPolicyEraWonderRequirements(): HashSet<RejectionReason> {
return filterNot { it in techPolicyEraWonderRequirements }.toHashSet()
}
private val reasonsToDefinitivelyRemoveFromQueue = hashSetOf(
RejectionReason.Obsoleted,
RejectionReason.WonderAlreadyBuilt,
RejectionReason.NationalWonderAlreadyBuilt,
RejectionReason.CannotBeBuiltWith,
RejectionReason.ReachedBuildCap
)
fun hasAReasonToBeRemovedFromQueue(): Boolean {
return any { it in reasonsToDefinitivelyRemoveFromQueue }
}
private val orderOfErrorMessages = listOf(
RejectionReason.WonderBeingBuiltElsewhere,
RejectionReason.NationalWonderBeingBuiltElsewhere,
RejectionReason.RequiresBuildingInAllCities,
RejectionReason.RequiresBuildingInThisCity,
RejectionReason.RequiresBuildingInSomeCity,
RejectionReason.PopulationRequirement,
RejectionReason.ConsumesResources,
RejectionReason.CanOnlyBePurchased
)
fun getMostImportantRejectionReason(): String? {
return orderOfErrorMessages.firstOrNull { it in this }?.errorMessage
}
}
enum class RejectionReason(val shouldShow: Boolean, var errorMessage: String) {
AlreadyBuilt(false, "Building already built in this city"),
Unbuildable(false, "Unbuildable"),
CanOnlyBePurchased(true, "Can only be purchased"),
ShouldNotBeDisplayed(false, "Should not be displayed"),
DisabledBySetting(false, "Disabled by setting"),
HiddenWithoutVictory(false, "Hidden because a victory type has been disabled"),
MustBeOnTile(false, "Must be on a specific tile"),
MustNotBeOnTile(false, "Must not be on a specific tile"),
MustBeNextToTile(false, "Must be next to a specific tile"),
MustNotBeNextToTile(false, "Must not be next to a specific tile"),
MustOwnTile(false, "Must own a specific tile closeby"),
WaterUnitsInCoastalCities(false, "May only built water units in coastal cities"),
CanOnlyBeBuiltInSpecificCities(false, "Can only be built in specific cities"),
UniqueToOtherNation(false, "Unique to another nation"),
ReplacedByOurUnique(false, "Our unique replaces this"),
Obsoleted(false, "Obsolete"),
RequiresTech(false, "Required tech not researched"),
RequiresPolicy(false, "Requires a specific policy!"),
UnlockedWithEra(false, "Unlocked when reacing a specific era"),
MorePolicyBranches(false, "Hidden until more policy branches are fully adopted"),
RequiresNearbyResource(false, "Requires a certain resource being exploited nearby"),
InvalidRequiredBuilding(false, "Required building does not exist in ruleSet!"),
CannotBeBuiltWith(false, "Cannot be built at the same time as another building already built"),
RequiresBuildingInThisCity(true, "Requires a specific building in this city!"),
RequiresBuildingInAllCities(true, "Requires a specific building in all cities!"),
RequiresBuildingInSomeCity(true, "Requires a specific building anywhere in your empire!"),
WonderAlreadyBuilt(false, "Wonder already built"),
NationalWonderAlreadyBuilt(false, "National Wonder already built"),
WonderBeingBuiltElsewhere(true, "Wonder is being built elsewhere"),
NationalWonderBeingBuiltElsewhere(true, "National Wonder is being built elsewhere"),
CityStateWonder(false, "No Wonders for city-states"),
CityStateNationalWonder(false, "No National Wonders for city-states"),
WonderDisabledEra(false, "This Wonder is disabled when starting in this era"),
ReachedBuildCap(false, "Don't need to build any more of these!"),
ConsumesResources(true, "Consumes resources which you are lacking"),
PopulationRequirement(true, "Requires more population"),
NoSettlerForOneCityPlayers(false, "No settlers for city-states or one-city challangers");
}
open class PerpetualConstruction(override var name: String, val description: String) : IConstruction { open class PerpetualConstruction(override var name: String, val description: String) : IConstruction {
override fun shouldBeDisplayed(cityConstructions: CityConstructions) = isBuildable(cityConstructions) override fun shouldBeDisplayed(cityConstructions: CityConstructions) = isBuildable(cityConstructions)

View File

@ -927,10 +927,7 @@ class CivilizationInfo {
addNotification( "[${givingCityState.civName}] gave us a [${giftedUnit.name}] as a gift!", locations, givingCityState.civName, giftedUnit.name) addNotification( "[${givingCityState.civName}] gave us a [${giftedUnit.name}] as a gift!", locations, givingCityState.civName, giftedUnit.name)
} }
fun turnsForGreatPersonFromCityState(): Int = ((40 + -2 + Random().nextInt(5)) * gameInfo.gameParameters.gameSpeed.modifier).toInt() fun turnsForGreatPersonFromCityState(): Int = ((37 + Random().nextInt(7)) * gameInfo.gameParameters.gameSpeed.modifier).toInt()
// There seems to be some randomness in the amount of turns between receiving each great person,
// but I have no idea what the actual lower and upper bound are, so this is just an approximation
fun getAllyCiv() = allyCivName fun getAllyCiv() = allyCivName

View File

@ -2,11 +2,10 @@ package com.unciv.models.ruleset
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.*
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.stats.NamedStats import com.unciv.models.stats.NamedStats
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
@ -228,7 +227,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
if (cost > 0) { if (cost > 0) {
val stats = mutableListOf("$cost${Fonts.production}") val stats = mutableListOf("$cost${Fonts.production}")
if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true)) { if (canBePurchasedWithStat(null, Stat.Gold)) {
stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}" stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}"
} }
textList += FormattedLine(stats.joinToString(", ", "{Cost}: ")) textList += FormattedLine(stats.joinToString(", ", "{Cost}: "))
@ -352,13 +351,13 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
} }
override fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean): Boolean { override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean {
if (stat == Stat.Gold && isAnyWonder()) return false if (stat == Stat.Gold && isAnyWonder()) return false
// May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter] // May buy [buildingFilter] buildings for [amount] [Stat] [cityFilter]
if (!ignoreCityRequirements && cityInfo.getMatchingUniques("May buy [] buildings for [] [] []") if (cityInfo != null && cityInfo.getMatchingUniques("May buy [] buildings for [] [] []")
.any { it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) } .any { it.params[2] == stat.name && matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) }
) return true ) return true
return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) return super.canBePurchasedWithStat(cityInfo, stat)
} }
override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? {
@ -408,192 +407,264 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean {
if (cityConstructions.isBeingConstructedOrEnqueued(name)) if (cityConstructions.isBeingConstructedOrEnqueued(name))
return false return false
val rejectionReason = getRejectionReason(cityConstructions) val rejectionReasons = getRejectionReasons(cityConstructions)
return rejectionReason == "" return rejectionReasons.none { !it.shouldShow }
|| rejectionReason.startsWith("Requires") || (
|| rejectionReason.startsWith("Consumes") canBePurchasedWithAnyStat(cityConstructions.cityInfo)
|| rejectionReason.endsWith("Wonder is being built elsewhere") && rejectionReasons.all { it == RejectionReason.Unbuildable }
|| rejectionReason == "Can only be purchased" )
} }
override fun getRejectionReason(construction: CityConstructions): String { override fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons {
if (construction.isBuilt(name)) return "Already built" val rejectionReasons = RejectionReasons()
// for buildings that are created as side effects of other things, and not directly built val cityCenter = cityConstructions.cityInfo.getCenterTile()
// unless they can be bought with faith val civInfo = cityConstructions.cityInfo.civInfo
if (uniques.contains("Unbuildable")) { val ruleSet = civInfo.gameInfo.ruleSet
if (canBePurchasedWithAnyStat(construction.cityInfo))
return "Can only be purchased"
return "Unbuildable"
}
val cityCenter = construction.cityInfo.getCenterTile() if (cityConstructions.isBuilt(name))
val civInfo = construction.cityInfo.civInfo rejectionReasons.add(RejectionReason.AlreadyBuilt)
// for buildings that are created as side effects of other things, and not directly built,
// or for buildings that can only be bought
if (uniques.contains("Unbuildable"))
rejectionReasons.add(RejectionReason.Unbuildable)
// This overrides the others for (unique in uniqueObjects) {
if (uniqueObjects when (unique.placeholderText) {
.any { // Deprecated since 3.16.11, replace with "Not displayed [...] construction without []"
it.placeholderText == "Not displayed as an available construction unless [] is built" "Not displayed as an available construction unless [] is built" ->
&& !construction.containsBuildingOrEquivalent(it.params[0]) if (!cityConstructions.containsBuildingOrEquivalent(unique.params[0]))
} rejectionReasons.add(RejectionReason.ShouldNotBeDisplayed)
) return "Should not be displayed"
for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) {
val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.tileResources && !construction.cityInfo.civInfo.hasResource(filter)
|| filter in civInfo.gameInfo.ruleSet.buildings && !construction.containsBuildingOrEquivalent(filter))
return "Should not be displayed"
}
for (unique in uniqueObjects) when (unique.placeholderText) {
"Enables nuclear weapon" -> if(!construction.cityInfo.civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled) return "Disabled by setting"
"Must be on []" -> if (!cityCenter.matchesTerrainFilter(unique.params[0], civInfo)) return unique.text
"Must not be on []" -> if (cityCenter.matchesTerrainFilter(unique.params[0], civInfo)) return unique.text
"Must be next to []" -> if (!(unique.params[0] == "Fresh water" && cityCenter.isAdjacentToRiver()) // Fresh water is special, in that rivers are not tiles themselves but also fit the filter.
&& cityCenter.getTilesInDistance(1).none { it.matchesFilter(unique.params[0], civInfo) }) return unique.text
"Must not be next to []" -> if (cityCenter.getTilesInDistance(1).any { it.matchesFilter(unique.params[0], civInfo) }) return unique.text
"Must have an owned [] within [] tiles" -> if (cityCenter.getTilesInDistance(unique.params[1].toInt()).none {
it.matchesFilter(unique.params[0], civInfo) && it.getOwner() == construction.cityInfo.civInfo
}) return unique.text
// Deprecated since 3.16.11
"Can only be built in annexed cities" -> if (construction.cityInfo.isPuppet
|| construction.cityInfo.civInfo.civName == construction.cityInfo.foundingCiv) return unique.text
// //
"Can only be built []" -> if (!construction.cityInfo.matchesFilter(unique.params[0])) return unique.text
"Obsolete with []" -> if (civInfo.tech.isResearched(unique.params[0])) return unique.text "Not displayed as an available construction without []" ->
Constants.hiddenWithoutReligionUnique -> if (!civInfo.gameInfo.hasReligionEnabled()) return unique.text if (unique.params[0] in ruleSet.tileResources && !civInfo.hasResource(unique.params[0])
|| unique.params[0] in ruleSet.buildings && !cityConstructions.containsBuildingOrEquivalent(unique.params[0])
|| unique.params[0] in ruleSet.technologies && !civInfo.tech.isResearched(unique.params[0])
|| unique.params[0] in ruleSet.policies && !civInfo.policies.isAdopted(unique.params[0])
)
rejectionReasons.add(RejectionReason.ShouldNotBeDisplayed)
"Enables nuclear weapon" -> if (!cityConstructions.cityInfo.civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled)
rejectionReasons.add(RejectionReason.DisabledBySetting)
"Must be on []" ->
if (!cityCenter.matchesTerrainFilter(unique.params[0], civInfo))
rejectionReasons.add(RejectionReason.MustBeOnTile.apply { errorMessage = unique.text })
"Must not be on []" ->
if (cityCenter.matchesTerrainFilter(unique.params[0], civInfo))
rejectionReasons.add(RejectionReason.MustNotBeOnTile.apply { errorMessage = unique.text })
"Must be next to []" ->
if (// Fresh water is special, in that rivers are not tiles themselves but also fit the filter.
!(unique.params[0] == "Fresh water" && cityCenter.isAdjacentToRiver())
&& cityCenter.getTilesInDistance(1).none { it.matchesFilter(unique.params[0], civInfo) }
)
rejectionReasons.add(RejectionReason.MustBeNextToTile.apply { errorMessage = unique.text })
"Must not be next to []" ->
if (cityCenter.getTilesInDistance(1).any { it.matchesFilter(unique.params[0], civInfo) })
rejectionReasons.add(RejectionReason.MustNotBeNextToTile.apply { errorMessage = unique.text })
"Must have an owned [] within [] tiles" ->
if (cityCenter.getTilesInDistance(unique.params[1].toInt())
.none { it.matchesFilter(unique.params[0], civInfo) && it.getOwner() == cityConstructions.cityInfo.civInfo }
)
rejectionReasons.add(RejectionReason.MustOwnTile.apply { errorMessage = unique.text })
// Deprecated since 3.16.11
"Can only be built in annexed cities" ->
if (
cityConstructions.cityInfo.isPuppet
|| cityConstructions.cityInfo.civInfo.civName == cityConstructions.cityInfo.foundingCiv
)
rejectionReasons.add(RejectionReason.CanOnlyBeBuiltInSpecificCities.apply { errorMessage = unique.text })
//
"Can only be built []" ->
if (!cityConstructions.cityInfo.matchesFilter(unique.params[0]))
rejectionReasons.add(RejectionReason.CanOnlyBeBuiltInSpecificCities.apply { errorMessage = unique.text })
"Obsolete with []" ->
if (civInfo.tech.isResearched(unique.params[0]))
rejectionReasons.add(RejectionReason.Obsoleted.apply { errorMessage = unique.text })
Constants.hiddenWithoutReligionUnique ->
if (!civInfo.gameInfo.hasReligionEnabled())
rejectionReasons.add(RejectionReason.DisabledBySetting)
}
} }
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo" if (uniqueTo != null && uniqueTo != civInfo.civName)
if (civInfo.gameInfo.ruleSet.buildings.values.any { it.uniqueTo == civInfo.civName && it.replaces == name }) rejectionReasons.add(RejectionReason.UniqueToOtherNation.apply { errorMessage = "Unique to $uniqueTo"})
return "Our unique building replaces this"
if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" }) if (civInfo.gameInfo.ruleSet.buildings.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
if (civInfo.tech.researchedTechnologies.none { it.era() == unique.params[0] || it.name == unique.params[0] } rejectionReasons.add(RejectionReason.ReplacedByOurUnique)
&& !civInfo.policies.isAdopted(unique.params[0]))
return unique.text if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!))
rejectionReasons.add(RejectionReason.RequiresTech.apply { "$requiredTech not researched!"})
for (unique in uniqueObjects) {
if (unique.placeholderText != "Unlocked with []" && unique.placeholderText != "Requires []") continue
val filter = unique.params[0]
when {
ruleSet.technologies.contains(filter) ->
if (!civInfo.tech.isResearched(filter))
rejectionReasons.add(RejectionReason.RequiresTech.apply { errorMessage = unique.text })
ruleSet.policies.contains(filter) ->
if (!civInfo.policies.isAdopted(filter))
rejectionReasons.add(RejectionReason.RequiresPolicy.apply { errorMessage = unique.text })
// ToDo: Fix this when eras.json is required
ruleSet.getEraNumber(filter) != -1 ->
if (civInfo.getEraNumber() < ruleSet.getEraNumber(filter))
rejectionReasons.add(RejectionReason.UnlockedWithEra.apply { errorMessage = unique.text })
ruleSet.buildings.contains(filter) ->
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) })
rejectionReasons.add(RejectionReason.RequiresBuildingInSomeCity.apply { errorMessage = unique.text })
}
}
// Regular wonders // Regular wonders
if (isWonder) { if (isWonder) {
if (civInfo.gameInfo.getCities().any { it.cityConstructions.isBuilt(name) }) if (civInfo.gameInfo.getCities().any { it.cityConstructions.isBuilt(name) })
return "Wonder is already built" rejectionReasons.add(RejectionReason.WonderAlreadyBuilt)
if (civInfo.cities.any { it != construction.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) }) if (civInfo.cities.any { it != cityConstructions.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) })
return "Wonder is being built elsewhere" rejectionReasons.add(RejectionReason.WonderBeingBuiltElsewhere)
if (civInfo.isCityState()) if (civInfo.isCityState())
return "No world wonders for city-states" rejectionReasons.add(RejectionReason.CityStateWonder)
val ruleSet = civInfo.gameInfo.ruleSet
val startingEra = civInfo.gameInfo.gameParameters.startingEra val startingEra = civInfo.gameInfo.gameParameters.startingEra
if (startingEra in ruleSet.eras && name in ruleSet.eras[startingEra]!!.startingObsoleteWonders) if (startingEra in ruleSet.eras && name in ruleSet.eras[startingEra]!!.startingObsoleteWonders)
return "Wonder is disabled when starting in this era" rejectionReasons.add(RejectionReason.WonderDisabledEra)
} }
// National wonders // National wonders
if (isNationalWonder) { if (isNationalWonder) {
if (civInfo.cities.any { it.cityConstructions.isBuilt(name) }) if (civInfo.cities.any { it.cityConstructions.isBuilt(name) })
return "National Wonder is already built" rejectionReasons.add(RejectionReason.NationalWonderAlreadyBuilt)
if (requiredBuildingInAllCities != null && civInfo.gameInfo.ruleSet.buildings[requiredBuildingInAllCities!!] == null)
return "Required building in all cities does not exist in the ruleset!" if (requiredBuildingInAllCities != null && civInfo.gameInfo.ruleSet.buildings[requiredBuildingInAllCities!!] == null) {
rejectionReasons.add(RejectionReason.InvalidRequiredBuilding)
} else {
if (requiredBuildingInAllCities != null if (requiredBuildingInAllCities != null
&& civInfo.cities.any { && civInfo.cities.any {
!it.isPuppet && !it.cityConstructions !it.isPuppet && !it.cityConstructions
.containsBuildingOrEquivalent(requiredBuildingInAllCities!!) .containsBuildingOrEquivalent(requiredBuildingInAllCities!!)
}) }
return "Requires a [${civInfo.getEquivalentBuilding(requiredBuildingInAllCities!!)}] in all cities" ) {
if (civInfo.cities.any { it != construction.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) }) rejectionReasons.add(RejectionReason.RequiresBuildingInAllCities
return "National Wonder is being built elsewhere" .apply { errorMessage = "Requires a [${civInfo.getEquivalentBuilding(requiredBuildingInAllCities!!)}] in all cities"})
}
if (civInfo.cities.any { it != cityConstructions.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) })
rejectionReasons.add(RejectionReason.NationalWonderBeingBuiltElsewhere)
if (civInfo.isCityState()) if (civInfo.isCityState())
return "No national wonders for city-states" rejectionReasons.add(RejectionReason.CityStateNationalWonder)
}
} }
if ("Spaceship part" in uniques) { if ("Spaceship part" in uniques) {
if (!civInfo.hasUnique("Enables construction of Spaceship parts")) return "Apollo project not built!" if (!civInfo.hasUnique("Enables construction of Spaceship parts"))
if (civInfo.victoryManager.unconstructedSpaceshipParts()[name] == 0) return "Don't need to build any more of these!" rejectionReasons.add(
RejectionReason.RequiresBuildingInSomeCity.apply { errorMessage = "Apollo project not built!" }
)
if (civInfo.victoryManager.unconstructedSpaceshipParts()[name] == 0)
rejectionReasons.add(RejectionReason.ReachedBuildCap)
} }
for (unique in uniqueObjects) when (unique.placeholderText) { for (unique in uniqueObjects) when (unique.placeholderText) {
"Requires []" -> {
val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.buildings) {
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built
} else if (!civInfo.policies.isAdopted(filter)) return "Policy is not adopted" // this reason should not be displayed
}
"Requires a [] in this city" -> { "Requires a [] in this city" -> {
val filter = unique.params[0] val filter = unique.params[0]
if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) && !cityConstructions.containsBuildingOrEquivalent(filter))
&& !construction.containsBuildingOrEquivalent(filter)) rejectionReasons.add(
return "Requires a [${civInfo.getEquivalentBuilding(filter)}] in this city" // replace with civ-specific building for user // replace with civ-specific building for user
RejectionReason.RequiresBuildingInThisCity.apply { errorMessage = "Requires a [${civInfo.getEquivalentBuilding(filter)}] in this city" }
)
} }
"Requires a [] in all cities" -> { "Requires a [] in all cities" -> {
val filter = unique.params[0] val filter = unique.params[0]
if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter)
&& civInfo.cities.any { !it.isPuppet && !it.cityConstructions.containsBuildingOrEquivalent(unique.params[0]) }) && civInfo.cities.any {
return "Requires a [${civInfo.getEquivalentBuilding(unique.params[0])}] in all cities" // replace with civ-specific building for user !it.isPuppet && !it.cityConstructions.containsBuildingOrEquivalent(unique.params[0])
} }
) {
rejectionReasons.add(
// replace with civ-specific building for user
RejectionReason.RequiresBuildingInAllCities.apply {
errorMessage = "Requires a [${civInfo.getEquivalentBuilding(unique.params[0])}] in all cities"
}
)
}
}
"Hidden until [] social policy branches have been completed" -> { "Hidden until [] social policy branches have been completed" -> {
if (construction.cityInfo.civInfo.getCompletedPolicyBranchesCount() < unique.params[0].toInt()) { if (cityConstructions.cityInfo.civInfo.getCompletedPolicyBranchesCount() < unique.params[0].toInt())
return "Should not be displayed" rejectionReasons.add(RejectionReason.MorePolicyBranches.apply { errorMessage = unique.text })
}
} }
"Hidden when [] Victory is disabled" -> { "Hidden when [] Victory is disabled" -> {
if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0]))) { if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0])))
return unique.text rejectionReasons.add(RejectionReason.HiddenWithoutVictory.apply { errorMessage = unique.text })
}
} }
// Deprecated since 3.15.14 // Deprecated since 3.15.14
"Hidden when cultural victory is disabled" -> { "Hidden when cultural victory is disabled" -> {
if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.Cultural)) { if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.Cultural))
return unique.text rejectionReasons.add(RejectionReason.HiddenWithoutVictory.apply { errorMessage = unique.text })
}
} }
// //
} }
if (requiredBuilding != null && !construction.containsBuildingOrEquivalent(requiredBuilding!!)) { if (requiredBuilding != null && !cityConstructions.containsBuildingOrEquivalent(requiredBuilding!!)) {
if (!civInfo.gameInfo.ruleSet.buildings.containsKey(requiredBuilding!!)) if (!civInfo.gameInfo.ruleSet.buildings.containsKey(requiredBuilding!!)) {
return "Requires a [${requiredBuilding}] in this city, which doesn't seem to exist in this ruleset!" rejectionReasons.add(
return "Requires a [${civInfo.getEquivalentBuilding(requiredBuilding!!)}] in this city" RejectionReason.InvalidRequiredBuilding
.apply { errorMessage = "Requires a [${requiredBuilding}] in this city, which doesn't seem to exist in this ruleset!" }
)
} else {
rejectionReasons.add(
RejectionReason.RequiresBuildingInThisCity.apply { errorMessage = "Requires a [${civInfo.getEquivalentBuilding(requiredBuilding!!)}] in this city"}
)
}
} }
// cannotBeBuiltWith is Deprecated as of 3.15.19 // cannotBeBuiltWith is Deprecated as of 3.15.19
val cannotBeBuiltWith = uniqueObjects val cannotBeBuiltWith = uniqueObjects
.firstOrNull { it.placeholderText == "Cannot be built with []" } .firstOrNull { it.placeholderText == "Cannot be built with []" }
?.params?.get(0) ?.params?.get(0)
?: this.cannotBeBuiltWith ?: this.cannotBeBuiltWith
if (cannotBeBuiltWith != null && construction.isBuilt(cannotBeBuiltWith)) if (cannotBeBuiltWith != null && cityConstructions.isBuilt(cannotBeBuiltWith))
return "Cannot be built with [$cannotBeBuiltWith]" rejectionReasons.add(RejectionReason.CannotBeBuiltWith.apply { errorMessage = "Cannot be built with [$cannotBeBuiltWith]" })
for ((resource, amount) in getResourceRequirements()) for ((resource, amount) in getResourceRequirements())
if (civInfo.getCivResourcesByName()[resource]!! < amount) { if (civInfo.getCivResourcesByName()[resource]!! < amount) {
return if (amount == 1) "Consumes 1 [$resource]" // Again, to preserve existing translations rejectionReasons.add(RejectionReason.ConsumesResources.apply {
else "Consumes [$amount] [$resource]" errorMessage = "Consumes [$amount] [$resource]"
})
} }
if (requiredNearbyImprovedResources != null) { if (requiredNearbyImprovedResources != null) {
val containsResourceWithImprovement = construction.cityInfo.getWorkableTiles() val containsResourceWithImprovement = cityConstructions.cityInfo.getWorkableTiles()
.any { .any {
it.resource != null it.resource != null
&& requiredNearbyImprovedResources!!.contains(it.resource!!) && requiredNearbyImprovedResources!!.contains(it.resource!!)
&& it.getOwner() == civInfo && it.getOwner() == civInfo
&& (it.getTileResource().improvement == it.improvement || it.getTileImprovement()?.isGreatImprovement() == true || it.isCityCenter()) && (it.getTileResource().improvement == it.improvement || it.isCityCenter()
|| (it.getTileImprovement()?.isGreatImprovement() == true && it.getTileResource().resourceType == ResourceType.Strategic)
)
} }
if (!containsResourceWithImprovement) return "Nearby $requiredNearbyImprovedResources required" if (!containsResourceWithImprovement)
rejectionReasons.add(RejectionReason.RequiresNearbyResource.apply { errorMessage = "Nearby $requiredNearbyImprovedResources required" })
} }
return rejectionReasons
if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.Scientific)
&& "Enables construction of Spaceship parts" in uniques)
return "Can't construct spaceship parts if scientific victory is not enabled!"
return ""
} }
override fun isBuildable(cityConstructions: CityConstructions): Boolean = override fun isBuildable(cityConstructions: CityConstructions): Boolean =
getRejectionReason(cityConstructions) == "" getRejectionReasons(cityConstructions).isEmpty()
override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean {
val civInfo = cityConstructions.cityInfo.civInfo val civInfo = cityConstructions.cityInfo.civInfo

View File

@ -2,9 +2,7 @@ package com.unciv.models.ruleset.unit
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.*
import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
@ -126,7 +124,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
if (cost > 0) { if (cost > 0) {
stats.clear() stats.clear()
stats += "$cost${Fonts.production}" stats += "$cost${Fonts.production}"
if (canBePurchasedWithStat(CityInfo(), Stat.Gold, true)) if (canBePurchasedWithStat(null, Stat.Gold))
stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}" stats += "${getBaseGoldCost(UncivGame.Current.gameInfo.currentPlayerCiv).toInt() / 10 * 10}${Fonts.gold}"
textList += FormattedLine(stats.joinToString(", ", "{Cost}: ")) textList += FormattedLine(stats.joinToString(", ", "{Cost}: "))
} }
@ -216,13 +214,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
return productionCost.toInt() return productionCost.toInt()
} }
override fun canBePurchasedWithStat( override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean {
cityInfo: CityInfo,
stat: Stat,
ignoreCityRequirements: Boolean
): Boolean {
// May buy [unitFilter] units for [amount] [Stat] starting from the [eraName] at an increasing price ([amount]) // 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 ([])") if (cityInfo != null && cityInfo.civInfo.getMatchingUniques("May buy [] units for [] [] [] starting from the [] at an increasing price ([])")
.any { .any {
matchesFilter(it.params[0]) matchesFilter(it.params[0])
&& cityInfo.matchesFilter(it.params[3]) && cityInfo.matchesFilter(it.params[3])
@ -231,7 +225,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
} }
) return true ) return true
return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) return super.canBePurchasedWithStat(cityInfo, stat)
} }
private fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int { private fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int {
@ -285,84 +279,103 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
fun getDisbandGold(civInfo: CivilizationInfo) = getBaseGoldCost(civInfo).toInt() / 20 fun getDisbandGold(civInfo: CivilizationInfo) = getBaseGoldCost(civInfo).toInt() / 20
override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean {
val rejectionReason = getRejectionReason(cityConstructions) val rejectionReasons = getRejectionReasons(cityConstructions)
return rejectionReason == "" return rejectionReasons.none { !it.shouldShow }
|| rejectionReason.startsWith("Requires") || (
|| rejectionReason.startsWith("Consumes") canBePurchasedWithAnyStat(cityConstructions.cityInfo)
|| rejectionReason == "Can only be purchased" && rejectionReasons.all { it == RejectionReason.Unbuildable }
)
} }
override fun getRejectionReason(cityConstructions: CityConstructions): String { override fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons {
val rejectionReasons = RejectionReasons()
if (isWaterUnit() && !cityConstructions.cityInfo.isCoastal()) if (isWaterUnit() && !cityConstructions.cityInfo.isCoastal())
return "Can only build water units in coastal cities" rejectionReasons.add(RejectionReason.WaterUnitsInCoastalCities)
val civInfo = cityConstructions.cityInfo.civInfo val civInfo = cityConstructions.cityInfo.civInfo
for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) { for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) {
val filter = unique.params[0] val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.tileResources && !civInfo.hasResource(filter) if (filter in civInfo.gameInfo.ruleSet.tileResources && !civInfo.hasResource(filter)
|| filter in civInfo.gameInfo.ruleSet.buildings && !cityConstructions.containsBuildingOrEquivalent(filter)) || filter in civInfo.gameInfo.ruleSet.buildings && !cityConstructions.containsBuildingOrEquivalent(filter))
return "Should not be displayed" rejectionReasons.add(RejectionReason.ShouldNotBeDisplayed)
} }
val civRejectionReason = getRejectionReason(civInfo) val civRejectionReasons = getRejectionReasons(civInfo)
if (civRejectionReason != "") { if (civRejectionReasons.isNotEmpty()) {
if (civRejectionReason == "Unbuildable" && canBePurchasedWithAnyStat(cityConstructions.cityInfo)) rejectionReasons.addAll(civRejectionReasons)
return "Can only be purchased"
return civRejectionReason
} }
for (unique in uniqueObjects.filter { it.placeholderText == "Requires at least [] population" }) for (unique in uniqueObjects.filter { it.placeholderText == "Requires at least [] population" })
if (unique.params[0].toInt() > cityConstructions.cityInfo.population.population) if (unique.params[0].toInt() > cityConstructions.cityInfo.population.population)
return unique.text rejectionReasons.add(RejectionReason.PopulationRequirement)
return "" return rejectionReasons
} }
/** @param ignoreTechPolicyRequirements: its `true` value is used when upgrading via ancient ruins, /** @param ignoreTechPolicyRequirements: its `true` value is used when upgrading via ancient ruins,
* as there we don't care whether we have the required tech, policy or building for the unit, * as there we don't care whether we have the required tech, policy or building for the unit,
* but do still care whether we have the resources required for the unit * but do still care whether we have the resources required for the unit
*/ */
fun getRejectionReason(civInfo: CivilizationInfo, ignoreTechPolicyRequirements: Boolean = false): String { fun getRejectionReasons(civInfo: CivilizationInfo): RejectionReasons {
if (uniques.contains("Unbuildable")) return "Unbuildable" val rejectionReasons = RejectionReasons()
if (!ignoreTechPolicyRequirements && requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched" val ruleSet = civInfo.gameInfo.ruleSet
if (!ignoreTechPolicyRequirements && obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"
if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
return "Our unique unit replaces this"
if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled && isNuclearWeapon()
) return "Disabled by setting"
for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" }) if (uniques.contains("Unbuildable"))
// ToDo: Clean this up when eras.json is required rejectionReasons.add(RejectionReason.Unbuildable)
if ((civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]) != -1 && civInfo.getEraNumber() >= civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]))
|| civInfo.hasTechOrPolicy(unique.params[0])
) return unique.text
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) { if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!))
rejectionReasons.add(RejectionReason.RequiresTech.apply { this.errorMessage = "$requiredTech not researched" })
if (obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!))
rejectionReasons.add(RejectionReason.Obsoleted.apply { this.errorMessage = "Obsolete by $obsoleteTech" })
if (uniqueTo != null && uniqueTo != civInfo.civName)
rejectionReasons.add(RejectionReason.UniqueToOtherNation.apply { this.errorMessage = "Unique to $uniqueTo" })
if (ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name })
rejectionReasons.add(RejectionReason.ReplacedByOurUnique.apply { this.errorMessage = "Our unique unit replaces this" })
if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled && isNuclearWeapon())
rejectionReasons.add(RejectionReason.DisabledBySetting)
for (unique in uniqueObjects) {
if (unique.placeholderText != "Unlocked with []" && unique.placeholderText != "Requires []") continue
val filter = unique.params[0] val filter = unique.params[0]
if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) { when {
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built ruleSet.technologies.contains(filter) ->
} else if (!ignoreTechPolicyRequirements && !civInfo.policies.isAdopted(filter)) return "Policy is not adopted" if (!civInfo.tech.isResearched(filter))
rejectionReasons.add(RejectionReason.RequiresTech.apply { errorMessage = unique.text })
ruleSet.policies.contains(filter) ->
if (!civInfo.policies.isAdopted(filter))
rejectionReasons.add(RejectionReason.RequiresPolicy.apply { errorMessage = unique.text })
// ToDo: Fix this when eras.json is required
ruleSet.getEraNumber(filter) != -1 ->
if (civInfo.getEraNumber() < ruleSet.getEraNumber(filter))
rejectionReasons.add(RejectionReason.UnlockedWithEra.apply { errorMessage = unique.text })
ruleSet.buildings.contains(filter) ->
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) })
rejectionReasons.add(RejectionReason.RequiresBuildingInSomeCity.apply { errorMessage = unique.text })
}
} }
for ((resource, amount) in getResourceRequirements()) for ((resource, amount) in getResourceRequirements())
if (civInfo.getCivResourcesByName()[resource]!! < amount) { if (civInfo.getCivResourcesByName()[resource]!! < amount) {
return if (amount == 1) "Consumes 1 [$resource]" // Again, to preserve existing translations rejectionReasons.add(RejectionReason.ConsumesResources.apply {
else "Consumes [$amount] [$resource]" errorMessage = "Consumes [$amount] [$resource]"
})
} }
if (uniques.contains(Constants.settlerUnique) && civInfo.isCityState()) return "No settler for city-states" if (uniques.contains(Constants.settlerUnique) &&
if (uniques.contains(Constants.settlerUnique) && civInfo.isOneCityChallenger()) return "No settler for players in One City Challenge" (civInfo.isCityState() || civInfo.isOneCityChallenger())
return "" )
rejectionReasons.add(RejectionReason.NoSettlerForOneCityPlayers)
return rejectionReasons
} }
fun isBuildable(civInfo: CivilizationInfo) = getRejectionReason(civInfo) == "" fun isBuildable(civInfo: CivilizationInfo) = getRejectionReasons(civInfo).isEmpty()
override fun isBuildable(cityConstructions: CityConstructions): Boolean { override fun isBuildable(cityConstructions: CityConstructions): Boolean {
return getRejectionReason(cityConstructions) == "" return getRejectionReasons(cityConstructions).isEmpty()
} }
/** Preemptively as in: buildable without actually having the tech and/or policy required for it. fun isBuildableIgnoringTechs(civInfo: CivilizationInfo): Boolean {
* Still checks for resource use and other things val rejectionReasons = getRejectionReasons(civInfo)
*/ return rejectionReasons.filterTechPolicyEraWonderRequirements().isEmpty()
fun isBuildableIgnoringTechs(civInfo: CivilizationInfo) = }
getRejectionReason(civInfo, true) == ""
override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean {
val civInfo = cityConstructions.cityInfo.civInfo val civInfo = cityConstructions.cityInfo.civInfo
@ -466,8 +479,10 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
"non-air" -> !movesLikeAirUnits() "non-air" -> !movesLikeAirUnits()
"Nuclear Weapon" -> isNuclearWeapon() "Nuclear Weapon" -> isNuclearWeapon()
"Great Person", "Great" -> isGreatPerson()
// Deprecated as of 3.15.2 // Deprecated as of 3.15.2
"military water" -> isMilitary() && isWaterUnit() "military water" -> isMilitary() && isWaterUnit()
//
else -> { else -> {
if (getType().matchesFilter(filter)) return true if (getType().matchesFilter(filter)) return true
if ( if (

View File

@ -165,19 +165,30 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
val useStoredProduction = entry is Building || !cityConstructions.isBeingConstructedOrEnqueued(entry.name) val useStoredProduction = entry is Building || !cityConstructions.isBeingConstructedOrEnqueued(entry.name)
var buttonText = entry.name.tr() + cityConstructions.getTurnsToConstructionString(entry.name, useStoredProduction) var buttonText = entry.name.tr() + cityConstructions.getTurnsToConstructionString(entry.name, useStoredProduction)
for ((resource, amount) in entry.getResourceRequirements()) { for ((resource, amount) in entry.getResourceRequirements()) {
buttonText += "\n" + (if (amount == 1) "Consumes 1 [$resource]" buttonText += "\n" + (
else "Consumes [$amount] [$resource]").tr() if (amount == 1) "Consumes 1 [$resource]"
else "Consumes [$amount] [$resource]"
).tr()
} }
constructionButtonDTOList.add(ConstructionButtonDTO(entry, buttonText, constructionButtonDTOList.add(
entry.getRejectionReason(cityConstructions))) ConstructionButtonDTO(
entry,
buttonText,
entry.getRejectionReasons(cityConstructions).getMostImportantRejectionReason()
)
)
} }
for (specialConstruction in PerpetualConstruction.perpetualConstructionsMap.values for (specialConstruction in PerpetualConstruction.perpetualConstructionsMap.values
.filter { it.shouldBeDisplayed(cityConstructions) }) { .filter { it.shouldBeDisplayed(cityConstructions) }
constructionButtonDTOList.add(ConstructionButtonDTO(specialConstruction, ) {
"Produce [${specialConstruction.name}]".tr() constructionButtonDTOList.add(
+ specialConstruction.getProductionTooltip(city))) ConstructionButtonDTO(
specialConstruction,
"Produce [${specialConstruction.name}]".tr() + specialConstruction.getProductionTooltip(city)
)
)
} }
return constructionButtonDTOList return constructionButtonDTOList
@ -297,7 +308,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.WHITE) Color.BROWN.cpy().lerp(Color.WHITE, 0.5f), Color.WHITE)
} }
private class ConstructionButtonDTO(val construction: IConstruction, val buttonText: String, val rejectionReason: String = "") private class ConstructionButtonDTO(val construction: IConstruction, val buttonText: String, val rejectionReason: String? = null)
private fun getConstructionButton(constructionButtonDTO: ConstructionButtonDTO): Table { private fun getConstructionButton(constructionButtonDTO: ConstructionButtonDTO): Table {
val construction = constructionButtonDTO.construction val construction = constructionButtonDTO.construction
@ -325,7 +336,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
pickConstructionButton.row() pickConstructionButton.row()
// no rejection reason means we can build it! // no rejection reason means we can build it!
if (constructionButtonDTO.rejectionReason != "") { if (constructionButtonDTO.rejectionReason != null) {
pickConstructionButton.color = Color.GRAY pickConstructionButton.color = Color.GRAY
pickConstructionButton.add(constructionButtonDTO.rejectionReason.toLabel(Color.RED).apply { wrap = true }) pickConstructionButton.add(constructionButtonDTO.rejectionReason.toLabel(Color.RED).apply { wrap = true })
.colspan(pickConstructionButton.columns).fillX().left().padTop(2f) .colspan(pickConstructionButton.columns).fillX().left().padTop(2f)

View File

@ -769,8 +769,7 @@ object UnitActions {
val giftAction = { val giftAction = {
if (recipient.isCityState()) { if (recipient.isCityState()) {
for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) { for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) {
if ((unit.isGreatPerson() && unique.params[1] == "Great Person") if (unit.matchesFilter(unique.params[1])
|| unit.matchesFilter(unique.params[1])
) { ) {
recipient.getDiplomacyManager(unit.civInfo).addInfluence(unique.params[0].toFloat() - 5f) recipient.getDiplomacyManager(unit.civInfo).addInfluence(unique.params[0].toFloat() - 5f)
break break