From fcc335b78a5ae359f60bb8f2b50953f824e0cd46 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:37:40 +0200 Subject: [PATCH] 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 --- .../jsons/Civ V - Vanilla/Buildings.json | 11 +- .../com/unciv/logic/city/CityConstructions.kt | 12 +- .../src/com/unciv/logic/city/IConstruction.kt | 107 +++++- .../logic/civilization/CivilizationInfo.kt | 5 +- core/src/com/unciv/models/ruleset/Building.kt | 329 +++++++++++------- .../com/unciv/models/ruleset/unit/BaseUnit.kt | 135 +++---- .../ui/cityscreen/CityConstructionsTable.kt | 31 +- .../unciv/ui/worldscreen/unit/UnitActions.kt | 3 +- 8 files changed, 408 insertions(+), 225 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index 353985865f..76abf2c424 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -1064,7 +1064,8 @@ "name": "Apollo Program", "cost": 750, "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" }, @@ -1091,7 +1092,7 @@ "name": "SS Cockpit", "requiredResource": "Aluminum", "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", @@ -1109,7 +1110,7 @@ "name": "SS Booster", "requiredResource": "Aluminum", "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", @@ -1136,13 +1137,13 @@ "name": "SS Engine", "requiredResource": "Aluminum", "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", "requiredResource": "Aluminum", "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 diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index deecd3a9bc..e9b5a55920 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -375,14 +375,10 @@ class CityConstructions { // Perpetual constructions should always still be valid (I hope) if (construction is PerpetualConstruction) continue - val rejectionReason = - (construction as INonPerpetualConstruction).getRejectionReason(this) + val rejectionReasons = + (construction as INonPerpetualConstruction).getRejectionReasons(this) - if (rejectionReason.endsWith("lready built") - || rejectionReason.startsWith("Cannot be built with") - || rejectionReason.startsWith("Don't need to build any more") - || rejectionReason.startsWith("Obsolete") - ) { + if (rejectionReasons.hasAReasonToBeRemovedFromQueue()) { if (construction is Building) { // Production put into wonders gets refunded if (construction.isWonder && getWorkDone(constructionName) != 0) { @@ -392,7 +388,7 @@ class CityConstructions { } } else if (construction is BaseUnit) { // 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 if (!inProgressConstructions.contains(construction.upgradesTo)) { inProgressConstructions[construction.upgradesTo!!] = getWorkDone(constructionName) diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index f6bfd365c7..2230cc8127 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -21,7 +21,7 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { fun getProductionCost(civInfo: CivilizationInfo): 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 getMatchingUniques(uniqueTemplate: String): Sequence { @@ -31,26 +31,25 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { 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 ("Cannot be purchased" in uniques) return false if (stat == Stat.Gold) return !uniques.contains("Unbuildable") // Can be purchased with [Stat] [cityFilter] 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 // Can be purchased for [amount] [Stat] [cityFilter] 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 false } /** Checks if the construction should be purchasable, not whether it can be bought with a stat at all */ fun isPurchasable(cityConstructions: CityConstructions): Boolean { - val rejectionReason = getRejectionReason(cityConstructions) - return rejectionReason == "" - || rejectionReason == "Can only be purchased" + val rejectionReasons = getRejectionReasons(cityConstructions) + return rejectionReasons.all { it == RejectionReason.Unbuildable } } fun canBePurchasedWithAnyStat(cityInfo: CityInfo): Boolean { @@ -81,6 +80,100 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { + +class RejectionReasons(): HashSet() { + private val techPolicyEraWonderRequirements = hashSetOf( + RejectionReason.Obsoleted, + RejectionReason.RequiresTech, + RejectionReason.RequiresPolicy, + RejectionReason.MorePolicyBranches, + RejectionReason.RequiresBuildingInSomeCity + ) + fun filterTechPolicyEraWonderRequirements(): HashSet { + 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 { override fun shouldBeDisplayed(cityConstructions: CityConstructions) = isBuildable(cityConstructions) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 8a62b8b672..e8d7bdd8a2 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -927,10 +927,7 @@ class CivilizationInfo { 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() - // 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 turnsForGreatPersonFromCityState(): Int = ((37 + Random().nextInt(7)) * gameInfo.gameParameters.gameSpeed.modifier).toInt() fun getAllyCiv() = allyCivName diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 4de0658603..b7f354cbfb 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -2,11 +2,10 @@ package com.unciv.models.ruleset import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.city.CityConstructions -import com.unciv.logic.city.CityInfo -import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.* import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.Counter +import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.stats.NamedStats import com.unciv.models.stats.Stat @@ -228,7 +227,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { if (cost > 0) { 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}" } 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 // 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]) } ) return true - return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) + return super.canBePurchasedWithStat(cityInfo, stat) } override fun getBaseBuyCost(cityInfo: CityInfo, stat: Stat): Int? { @@ -408,192 +407,264 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { if (cityConstructions.isBeingConstructedOrEnqueued(name)) return false - val rejectionReason = getRejectionReason(cityConstructions) - return rejectionReason == "" - || rejectionReason.startsWith("Requires") - || rejectionReason.startsWith("Consumes") - || rejectionReason.endsWith("Wonder is being built elsewhere") - || rejectionReason == "Can only be purchased" + val rejectionReasons = getRejectionReasons(cityConstructions) + return rejectionReasons.none { !it.shouldShow } + || ( + canBePurchasedWithAnyStat(cityConstructions.cityInfo) + && rejectionReasons.all { it == RejectionReason.Unbuildable } + ) } - override fun getRejectionReason(construction: CityConstructions): String { - if (construction.isBuilt(name)) return "Already built" - // for buildings that are created as side effects of other things, and not directly built - // unless they can be bought with faith - if (uniques.contains("Unbuildable")) { - if (canBePurchasedWithAnyStat(construction.cityInfo)) - return "Can only be purchased" - return "Unbuildable" - } + override fun getRejectionReasons(cityConstructions: CityConstructions): RejectionReasons { + val rejectionReasons = RejectionReasons() + val cityCenter = cityConstructions.cityInfo.getCenterTile() + val civInfo = cityConstructions.cityInfo.civInfo + val ruleSet = civInfo.gameInfo.ruleSet + + if (cityConstructions.isBuilt(name)) + 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) - val cityCenter = construction.cityInfo.getCenterTile() - val civInfo = construction.cityInfo.civInfo - - // This overrides the others - if (uniqueObjects - .any { - it.placeholderText == "Not displayed as an available construction unless [] is built" - && !construction.containsBuildingOrEquivalent(it.params[0]) + for (unique in uniqueObjects) { + when (unique.placeholderText) { + // Deprecated since 3.16.11, replace with "Not displayed [...] construction without []" + "Not displayed as an available construction unless [] is built" -> + if (!cityConstructions.containsBuildingOrEquivalent(unique.params[0])) + rejectionReasons.add(RejectionReason.ShouldNotBeDisplayed) + // + + "Not displayed as an available construction without []" -> + 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) } - ) 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 - Constants.hiddenWithoutReligionUnique -> if (!civInfo.gameInfo.hasReligionEnabled()) return unique.text - } - - if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo" + if (uniqueTo != null && uniqueTo != civInfo.civName) + rejectionReasons.add(RejectionReason.UniqueToOtherNation.apply { errorMessage = "Unique to $uniqueTo"}) + if (civInfo.gameInfo.ruleSet.buildings.values.any { it.uniqueTo == civInfo.civName && it.replaces == name }) - return "Our unique building replaces this" - if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched" + rejectionReasons.add(RejectionReason.ReplacedByOurUnique) + + if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) + rejectionReasons.add(RejectionReason.RequiresTech.apply { "$requiredTech not researched!"}) - for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" }) - if (civInfo.tech.researchedTechnologies.none { it.era() == unique.params[0] || it.name == unique.params[0] } - && !civInfo.policies.isAdopted(unique.params[0])) - return unique.text + 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 if (isWonder) { 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) }) - return "Wonder is being built elsewhere" + if (civInfo.cities.any { it != cityConstructions.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) }) + rejectionReasons.add(RejectionReason.WonderBeingBuiltElsewhere) 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 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 if (isNationalWonder) { if (civInfo.cities.any { it.cityConstructions.isBuilt(name) }) - return "National Wonder is already built" - if (requiredBuildingInAllCities != null && civInfo.gameInfo.ruleSet.buildings[requiredBuildingInAllCities!!] == null) - return "Required building in all cities does not exist in the ruleset!" - if (requiredBuildingInAllCities != null + rejectionReasons.add(RejectionReason.NationalWonderAlreadyBuilt) + + if (requiredBuildingInAllCities != null && civInfo.gameInfo.ruleSet.buildings[requiredBuildingInAllCities!!] == null) { + rejectionReasons.add(RejectionReason.InvalidRequiredBuilding) + } else { + if (requiredBuildingInAllCities != null && civInfo.cities.any { !it.isPuppet && !it.cityConstructions - .containsBuildingOrEquivalent(requiredBuildingInAllCities!!) - }) - return "Requires a [${civInfo.getEquivalentBuilding(requiredBuildingInAllCities!!)}] in all cities" - if (civInfo.cities.any { it != construction.cityInfo && it.cityConstructions.isBeingConstructedOrEnqueued(name) }) - return "National Wonder is being built elsewhere" - if (civInfo.isCityState()) - return "No national wonders for city-states" + .containsBuildingOrEquivalent(requiredBuildingInAllCities!!) + } + ) { + rejectionReasons.add(RejectionReason.RequiresBuildingInAllCities + .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()) + rejectionReasons.add(RejectionReason.CityStateNationalWonder) + } } if ("Spaceship part" in uniques) { - if (!civInfo.hasUnique("Enables construction of Spaceship parts")) return "Apollo project not built!" - if (civInfo.victoryManager.unconstructedSpaceshipParts()[name] == 0) return "Don't need to build any more of these!" + if (!civInfo.hasUnique("Enables construction of Spaceship parts")) + 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) { - "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" -> { val filter = unique.params[0] - if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) - && !construction.containsBuildingOrEquivalent(filter)) - return "Requires a [${civInfo.getEquivalentBuilding(filter)}] in this city" // replace with civ-specific building for user + if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) && !cityConstructions.containsBuildingOrEquivalent(filter)) + rejectionReasons.add( + // replace with civ-specific building for user + RejectionReason.RequiresBuildingInThisCity.apply { errorMessage = "Requires a [${civInfo.getEquivalentBuilding(filter)}] in this city" } + ) } "Requires a [] in all cities" -> { val filter = unique.params[0] - if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) - && civInfo.cities.any { !it.isPuppet && !it.cityConstructions.containsBuildingOrEquivalent(unique.params[0]) }) - return "Requires a [${civInfo.getEquivalentBuilding(unique.params[0])}] in all cities" // replace with civ-specific building for user - } - "Hidden until [] social policy branches have been completed" -> { - if (construction.cityInfo.civInfo.getCompletedPolicyBranchesCount() < unique.params[0].toInt()) { - return "Should not be displayed" + if (civInfo.gameInfo.ruleSet.buildings.containsKey(filter) + && civInfo.cities.any { + !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" -> { + if (cityConstructions.cityInfo.civInfo.getCompletedPolicyBranchesCount() < unique.params[0].toInt()) + rejectionReasons.add(RejectionReason.MorePolicyBranches.apply { errorMessage = unique.text }) + } "Hidden when [] Victory is disabled" -> { - if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0]))) { - return unique.text - } + if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.valueOf(unique.params[0]))) + rejectionReasons.add(RejectionReason.HiddenWithoutVictory.apply { errorMessage = unique.text }) } // Deprecated since 3.15.14 "Hidden when cultural victory is disabled" -> { - if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.Cultural)) { - return unique.text - } + if (!civInfo.gameInfo.gameParameters.victoryTypes.contains(VictoryType.Cultural)) + rejectionReasons.add(RejectionReason.HiddenWithoutVictory.apply { errorMessage = unique.text }) } // } - if (requiredBuilding != null && !construction.containsBuildingOrEquivalent(requiredBuilding!!)) { - if (!civInfo.gameInfo.ruleSet.buildings.containsKey(requiredBuilding!!)) - return "Requires a [${requiredBuilding}] in this city, which doesn't seem to exist in this ruleset!" - return "Requires a [${civInfo.getEquivalentBuilding(requiredBuilding!!)}] in this city" + if (requiredBuilding != null && !cityConstructions.containsBuildingOrEquivalent(requiredBuilding!!)) { + if (!civInfo.gameInfo.ruleSet.buildings.containsKey(requiredBuilding!!)) { + rejectionReasons.add( + 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 val cannotBeBuiltWith = uniqueObjects .firstOrNull { it.placeholderText == "Cannot be built with []" } ?.params?.get(0) ?: this.cannotBeBuiltWith - if (cannotBeBuiltWith != null && construction.isBuilt(cannotBeBuiltWith)) - return "Cannot be built with [$cannotBeBuiltWith]" + if (cannotBeBuiltWith != null && cityConstructions.isBuilt(cannotBeBuiltWith)) + rejectionReasons.add(RejectionReason.CannotBeBuiltWith.apply { errorMessage = "Cannot be built with [$cannotBeBuiltWith]" }) for ((resource, amount) in getResourceRequirements()) if (civInfo.getCivResourcesByName()[resource]!! < amount) { - return if (amount == 1) "Consumes 1 [$resource]" // Again, to preserve existing translations - else "Consumes [$amount] [$resource]" + rejectionReasons.add(RejectionReason.ConsumesResources.apply { + errorMessage = "Consumes [$amount] [$resource]" + }) } if (requiredNearbyImprovedResources != null) { - val containsResourceWithImprovement = construction.cityInfo.getWorkableTiles() - .any { - it.resource != null - && requiredNearbyImprovedResources!!.contains(it.resource!!) - && it.getOwner() == civInfo - && (it.getTileResource().improvement == it.improvement || it.getTileImprovement()?.isGreatImprovement() == true || it.isCityCenter()) - } - if (!containsResourceWithImprovement) return "Nearby $requiredNearbyImprovedResources required" + val containsResourceWithImprovement = cityConstructions.cityInfo.getWorkableTiles() + .any { + it.resource != null + && requiredNearbyImprovedResources!!.contains(it.resource!!) + && it.getOwner() == civInfo + && (it.getTileResource().improvement == it.improvement || it.isCityCenter() + || (it.getTileImprovement()?.isGreatImprovement() == true && it.getTileResource().resourceType == ResourceType.Strategic) + ) + } + if (!containsResourceWithImprovement) + rejectionReasons.add(RejectionReason.RequiresNearbyResource.apply { errorMessage = "Nearby $requiredNearbyImprovedResources required" }) } - - - 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 "" + + return rejectionReasons } override fun isBuildable(cityConstructions: CityConstructions): Boolean = - getRejectionReason(cityConstructions) == "" + getRejectionReasons(cityConstructions).isEmpty() override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { val civInfo = cityConstructions.cityInfo.civInfo diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 0981521814..558bb2d433 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -2,9 +2,7 @@ package com.unciv.models.ruleset.unit import com.unciv.Constants import com.unciv.UncivGame -import com.unciv.logic.city.CityConstructions -import com.unciv.logic.city.CityInfo -import com.unciv.logic.city.INonPerpetualConstruction +import com.unciv.logic.city.* import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.models.ruleset.Ruleset @@ -126,7 +124,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { if (cost > 0) { stats.clear() 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}" textList += FormattedLine(stats.joinToString(", ", "{Cost}: ")) } @@ -216,13 +214,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { return productionCost.toInt() } - override fun canBePurchasedWithStat( - cityInfo: CityInfo, - stat: Stat, - ignoreCityRequirements: Boolean - ): Boolean { + override fun canBePurchasedWithStat(cityInfo: CityInfo?, stat: Stat): Boolean { // 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 { matchesFilter(it.params[0]) && cityInfo.matchesFilter(it.params[3]) @@ -231,7 +225,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { } ) return true - return super.canBePurchasedWithStat(cityInfo, stat, ignoreCityRequirements) + return super.canBePurchasedWithStat(cityInfo, stat) } 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 override fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean { - val rejectionReason = getRejectionReason(cityConstructions) - return rejectionReason == "" - || rejectionReason.startsWith("Requires") - || rejectionReason.startsWith("Consumes") - || rejectionReason == "Can only be purchased" + val rejectionReasons = getRejectionReasons(cityConstructions) + return rejectionReasons.none { !it.shouldShow } + || ( + canBePurchasedWithAnyStat(cityConstructions.cityInfo) + && 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()) - return "Can only build water units in coastal cities" + rejectionReasons.add(RejectionReason.WaterUnitsInCoastalCities) val civInfo = cityConstructions.cityInfo.civInfo 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 && !civInfo.hasResource(filter) || filter in civInfo.gameInfo.ruleSet.buildings && !cityConstructions.containsBuildingOrEquivalent(filter)) - return "Should not be displayed" + rejectionReasons.add(RejectionReason.ShouldNotBeDisplayed) } - val civRejectionReason = getRejectionReason(civInfo) - if (civRejectionReason != "") { - if (civRejectionReason == "Unbuildable" && canBePurchasedWithAnyStat(cityConstructions.cityInfo)) - return "Can only be purchased" - return civRejectionReason + val civRejectionReasons = getRejectionReasons(civInfo) + if (civRejectionReasons.isNotEmpty()) { + rejectionReasons.addAll(civRejectionReasons) } for (unique in uniqueObjects.filter { it.placeholderText == "Requires at least [] population" }) if (unique.params[0].toInt() > cityConstructions.cityInfo.population.population) - return unique.text - return "" + rejectionReasons.add(RejectionReason.PopulationRequirement) + return rejectionReasons } /** @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, * but do still care whether we have the resources required for the unit */ - fun getRejectionReason(civInfo: CivilizationInfo, ignoreTechPolicyRequirements: Boolean = false): String { - if (uniques.contains("Unbuildable")) return "Unbuildable" - if (!ignoreTechPolicyRequirements && requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched" - 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" + fun getRejectionReasons(civInfo: CivilizationInfo): RejectionReasons { + val rejectionReasons = RejectionReasons() + val ruleSet = civInfo.gameInfo.ruleSet + + if (uniques.contains("Unbuildable")) + rejectionReasons.add(RejectionReason.Unbuildable) + + 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.filter { it.placeholderText == "Unlocked with []" }) - // ToDo: Clean this up when eras.json is required - 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 []" }) { + for (unique in uniqueObjects) { + if (unique.placeholderText != "Unlocked with []" && unique.placeholderText != "Requires []") continue val filter = unique.params[0] - if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) { - if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built - } else if (!ignoreTechPolicyRequirements && !civInfo.policies.isAdopted(filter)) return "Policy is not adopted" + 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 }) + } } for ((resource, amount) in getResourceRequirements()) if (civInfo.getCivResourcesByName()[resource]!! < amount) { - return if (amount == 1) "Consumes 1 [$resource]" // Again, to preserve existing translations - else "Consumes [$amount] [$resource]" + rejectionReasons.add(RejectionReason.ConsumesResources.apply { + errorMessage = "Consumes [$amount] [$resource]" + }) } - - if (uniques.contains(Constants.settlerUnique) && civInfo.isCityState()) return "No settler for city-states" - if (uniques.contains(Constants.settlerUnique) && civInfo.isOneCityChallenger()) return "No settler for players in One City Challenge" - return "" + + if (uniques.contains(Constants.settlerUnique) && + (civInfo.isCityState() || civInfo.isOneCityChallenger()) + ) + 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 { - return getRejectionReason(cityConstructions) == "" + return getRejectionReasons(cityConstructions).isEmpty() + } + + fun isBuildableIgnoringTechs(civInfo: CivilizationInfo): Boolean { + val rejectionReasons = getRejectionReasons(civInfo) + return rejectionReasons.filterTechPolicyEraWonderRequirements().isEmpty() } - - /** Preemptively as in: buildable without actually having the tech and/or policy required for it. - * Still checks for resource use and other things - */ - fun isBuildableIgnoringTechs(civInfo: CivilizationInfo) = - getRejectionReason(civInfo, true) == "" override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean { val civInfo = cityConstructions.cityInfo.civInfo @@ -466,8 +479,10 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { "non-air" -> !movesLikeAirUnits() "Nuclear Weapon" -> isNuclearWeapon() + "Great Person", "Great" -> isGreatPerson() // Deprecated as of 3.15.2 - "military water" -> isMilitary() && isWaterUnit() + "military water" -> isMilitary() && isWaterUnit() + // else -> { if (getType().matchesFilter(filter)) return true if ( diff --git a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt index fd2a5b3153..8756435af5 100644 --- a/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityConstructionsTable.kt @@ -165,19 +165,30 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { val useStoredProduction = entry is Building || !cityConstructions.isBeingConstructedOrEnqueued(entry.name) var buttonText = entry.name.tr() + cityConstructions.getTurnsToConstructionString(entry.name, useStoredProduction) for ((resource, amount) in entry.getResourceRequirements()) { - buttonText += "\n" + (if (amount == 1) "Consumes 1 [$resource]" - else "Consumes [$amount] [$resource]").tr() + buttonText += "\n" + ( + if (amount == 1) "Consumes 1 [$resource]" + else "Consumes [$amount] [$resource]" + ).tr() } - constructionButtonDTOList.add(ConstructionButtonDTO(entry, buttonText, - entry.getRejectionReason(cityConstructions))) + constructionButtonDTOList.add( + ConstructionButtonDTO( + entry, + buttonText, + entry.getRejectionReasons(cityConstructions).getMostImportantRejectionReason() + ) + ) } for (specialConstruction in PerpetualConstruction.perpetualConstructionsMap.values - .filter { it.shouldBeDisplayed(cityConstructions) }) { - constructionButtonDTOList.add(ConstructionButtonDTO(specialConstruction, - "Produce [${specialConstruction.name}]".tr() - + specialConstruction.getProductionTooltip(city))) + .filter { it.shouldBeDisplayed(cityConstructions) } + ) { + constructionButtonDTOList.add( + ConstructionButtonDTO( + specialConstruction, + "Produce [${specialConstruction.name}]".tr() + specialConstruction.getProductionTooltip(city) + ) + ) } return constructionButtonDTOList @@ -297,7 +308,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { 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 { val construction = constructionButtonDTO.construction @@ -325,7 +336,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { pickConstructionButton.row() // no rejection reason means we can build it! - if (constructionButtonDTO.rejectionReason != "") { + if (constructionButtonDTO.rejectionReason != null) { pickConstructionButton.color = Color.GRAY pickConstructionButton.add(constructionButtonDTO.rejectionReason.toLabel(Color.RED).apply { wrap = true }) .colspan(pickConstructionButton.columns).fillX().left().padTop(2f) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 14d913f446..74729a2a79 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -769,8 +769,7 @@ object UnitActions { val giftAction = { if (recipient.isCityState()) { for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) { - if ((unit.isGreatPerson() && unique.params[1] == "Great Person") - || unit.matchesFilter(unique.params[1]) + if (unit.matchesFilter(unique.params[1]) ) { recipient.getDiplomacyManager(unit.civInfo).addInfluence(unique.params[0].toFloat() - 5f) break