diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index f662bb2d61..c8ccf95696 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -13,6 +13,7 @@ import com.unciv.models.ruleset.Victory import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.LocalUniqueCache +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat @@ -247,7 +248,7 @@ object Automation { construction: INonPerpetualConstruction ): Boolean { return allowCreateImprovementBuildings(civInfo, city, construction) - && allowSpendingResource(civInfo, construction) + && allowSpendingResource(civInfo, construction, city) } @Suppress("MemberVisibilityCanBePrivate") @@ -268,7 +269,7 @@ object Automation { /** Determines whether the AI should be willing to spend strategic resources to build * [construction] for [civInfo], assumes that we are actually able to do so. */ - fun allowSpendingResource(civInfo: Civilization, construction: INonPerpetualConstruction): Boolean { + fun allowSpendingResource(civInfo: Civilization, construction: INonPerpetualConstruction, cityInfo: City? = null): Boolean { // City states do whatever they want if (civInfo.isCityState()) return true @@ -277,7 +278,9 @@ object Automation { if (construction.name in civInfo.gameInfo.spaceResources) return true - val requiredResources = construction.getResourceRequirementsPerTurn() + val requiredResources = if (construction is BaseUnit) + construction.getResourceRequirementsPerTurn(StateForConditionals(civInfo)) + else construction.getResourceRequirementsPerTurn(StateForConditionals(civInfo, cityInfo)) // Does it even require any resources? if (requiredResources.isEmpty()) return true @@ -295,9 +298,11 @@ object Automation { for (city in civInfo.cities) { val otherConstruction = city.cityConstructions.getCurrentConstruction() if (otherConstruction is Building) - futureForBuildings += otherConstruction.getResourceRequirementsPerTurn()[resource] + futureForBuildings += otherConstruction.getResourceRequirementsPerTurn( + StateForConditionals(civInfo, city))[resource] else - futureForUnits += otherConstruction.getResourceRequirementsPerTurn()[resource] + futureForUnits += otherConstruction.getResourceRequirementsPerTurn( + StateForConditionals(civInfo))[resource] } // Make sure we have some for space diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index f5e3a05066..a5b2dcaf7d 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -480,7 +480,7 @@ object NextTurnAutomation { continue val unitToDisband = civInfo.units.getCivUnits() - .filter { it.baseUnit.requiresResource(resource) } + .filter { it.requiresResource(resource) } .minByOrNull { it.getForceEvaluation() } unitToDisband?.disband() @@ -489,7 +489,7 @@ object NextTurnAutomation { continue val buildingToSell = civInfo.gameInfo.ruleset.buildings.values.filter { city.cityConstructions.isBuilt(it.name) - && it.requiresResource(resource) + && it.requiresResource(resource, StateForConditionals(civInfo, city)) && it.isSellable() && !civInfo.civConstructions.hasFreeBuilding(city, it) } .randomOrNull() diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index fafc48b8c1..76273191d9 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -14,6 +14,7 @@ import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.tile.Tile +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsPillage import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade @@ -130,7 +131,7 @@ object UnitAutomation { val upgradedUnit = unit.upgrade.getUnitToUpgradeTo() if (!upgradedUnit.isBuildable(unit.civ)) return false // for resource reasons, usually - if (upgradedUnit.getResourceRequirementsPerTurn().keys.any { !unit.baseUnit.requiresResource(it) }) { + if (upgradedUnit.getResourceRequirementsPerTurn(StateForConditionals(unit.civ, unit = unit)).keys.any { !unit.requiresResource(it) }) { // The upgrade requires new resource types, so check if we are willing to invest them if (!Automation.allowSpendingResource(unit.civ, upgradedUnit)) return false } diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index e66aa8761f..0c68bdbeff 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -120,7 +120,7 @@ object BattleDamage { private fun addResourceLackingMalus(combatant: MapUnitCombatant, modifiers: Counter) { val civInfo = combatant.getCivInfo() val civResources = civInfo.getCivResourcesByName() - for (resource in combatant.unit.baseUnit.getResourceRequirementsPerTurn().keys) + for (resource in combatant.unit.getResourceRequirementsPerTurn().keys) if (civResources[resource]!! < 0 && !civInfo.isBarbarian()) modifiers["Missing resource"] = BattleConstants.MISSING_RESOURCES_MALUS } diff --git a/core/src/com/unciv/logic/battle/Nuke.kt b/core/src/com/unciv/logic/battle/Nuke.kt index 6983082ab1..80bac816c7 100644 --- a/core/src/com/unciv/logic/battle/Nuke.kt +++ b/core/src/com/unciv/logic/battle/Nuke.kt @@ -204,7 +204,7 @@ object Nuke { var damageModifierFromMissingResource = 1f val civResources = attacker.getCivInfo().getCivResourcesByName() - for (resource in attacker.unit.baseUnit.getResourceRequirementsPerTurn().keys) { + for (resource in attacker.unit.getResourceRequirementsPerTurn().keys) { if (civResources[resource]!! < 0 && !attacker.getCivInfo().isBarbarian()) damageModifierFromMissingResource *= 0.5f // I could not find a source for this number, but this felt about right // - Original Civ5 does *not* reduce damage from missing resource, from source inspection diff --git a/core/src/com/unciv/logic/city/CityResources.kt b/core/src/com/unciv/logic/city/CityResources.kt index bdd832ab88..904fda0f55 100644 --- a/core/src/com/unciv/logic/city/CityResources.kt +++ b/core/src/com/unciv/logic/city/CityResources.kt @@ -78,7 +78,7 @@ object CityResources { for (building in city.cityConstructions.getBuiltBuildings()) { // Free buildings cost no resources if (building.name in freeBuildings) continue - cityResources.subtractResourceRequirements(building.getResourceRequirementsPerTurn(), city.getRuleset(), "Buildings") + cityResources.subtractResourceRequirements(building.getResourceRequirementsPerTurn(StateForConditionals(city.civ, city)), city.getRuleset(), "Buildings") } } diff --git a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt index 6a92bab2d0..26f6985892 100644 --- a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt @@ -87,7 +87,7 @@ class UnitManager(val civInfo:Civilization) { if (unique.conditionals.any { it.isOfType(UniqueType.TriggerUponGainingUnit) && unit.matchesFilter(unique.params[0]) }) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo, triggerNotificationText = triggerNotificationText) - if (unit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty()) + if (unit.getResourceRequirementsPerTurn().isNotEmpty()) civInfo.cache.updateCivResources() for (unique in civInfo.getMatchingUniques(UniqueType.LandUnitsCrossTerrainAfterUnitGained)) { @@ -126,7 +126,7 @@ class UnitManager(val civInfo:Civilization) { // Not relevant when updating Tile transients, since some info of the civ itself isn't yet available, // and in any case it'll be updated once civ info transients are civInfo.updateStatsForNextTurn() // unit upkeep - if (mapUnit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty()) + if (mapUnit.getResourceRequirementsPerTurn().isNotEmpty()) civInfo.cache.updateCivResources() } } @@ -139,7 +139,7 @@ class UnitManager(val civInfo:Civilization) { nextPotentiallyDueAt = 0 civInfo.updateStatsForNextTurn() // unit upkeep - if (mapUnit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty()) + if (mapUnit.getResourceRequirementsPerTurn().isNotEmpty()) civInfo.cache.updateCivResources() } diff --git a/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt b/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt index ba30a445f3..db71f18c40 100644 --- a/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt +++ b/core/src/com/unciv/logic/civilization/transients/CivInfoTransientCache.kt @@ -44,8 +44,8 @@ class CivInfoTransientCache(val civInfo: Civilization) { val ruleset = civInfo.gameInfo.ruleset for (resource in ruleset.tileResources.values.asSequence().filter { it.resourceType == ResourceType.Strategic }.map { it.name }) { - val applicableBuildings = ruleset.buildings.values.filter { it.requiresResource(resource) && civInfo.getEquivalentBuilding(it) == it } - val applicableUnits = ruleset.units.values.filter { it.requiresResource(resource) && civInfo.getEquivalentUnit(it) == it } + val applicableBuildings = ruleset.buildings.values.filter { it.requiresResource(resource, StateForConditionals.IgnoreConditionals) && civInfo.getEquivalentBuilding(it) == it } + val applicableUnits = ruleset.units.values.filter { it.requiresResource(resource, StateForConditionals.IgnoreConditionals) && civInfo.getEquivalentUnit(it) == it } val lastEraForBuilding = applicableBuildings.maxOfOrNull { ruleset.eras[ruleset.technologies[it.requiredTech]?.era()]?.eraNumber ?: 0 } val lastEraForUnit = applicableUnits.maxOfOrNull { ruleset.eras[ruleset.technologies[it.requiredTech]?.era()]?.eraNumber ?: 0 } @@ -316,7 +316,7 @@ class CivInfoTransientCache(val civInfo: Civilization) { for (unit in civInfo.units.getCivUnits()) newDetailedCivResources.subtractResourceRequirements( - unit.baseUnit.getResourceRequirementsPerTurn(), civInfo.gameInfo.ruleset, "Units") + unit.getResourceRequirementsPerTurn(), civInfo.gameInfo.ruleset, "Units") newDetailedCivResources.removeAll { it.resource.hasUnique(UniqueType.CityResource) } diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index b61fb0e153..e272763362 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -587,7 +587,7 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization { // And update civ stats, since the new unit changes both unit upkeep and resource consumption civInfo.updateStatsForNextTurn() - if (unit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty()) + if (unit.getResourceRequirementsPerTurn().isNotEmpty()) civInfo.cache.updateCivResources() return unit diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index 0d5f077b0a..73f6c6688c 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -12,6 +12,7 @@ import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.map.mapunit.movement.UnitMovement import com.unciv.logic.map.tile.Tile +import com.unciv.models.Counter import com.unciv.models.UnitActionType import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.TileImprovement @@ -259,6 +260,23 @@ class MapUnit : IsPartOfGameInfoSerialization { newUnit.updateVisibleTiles() } + /** Gets *per turn* resource requirements - does not include immediate costs for stockpiled resources. + * StateForConditionals is assumed to regarding this mapUnit*/ + fun getResourceRequirementsPerTurn(): Counter { + val resourceRequirements = Counter() + if (baseUnit.requiredResource != null) resourceRequirements[baseUnit.requiredResource!!] = 1 + for (unique in getMatchingUniques(UniqueType.ConsumesResources, StateForConditionals(civ, unit = this))) + resourceRequirements[unique.params[1]] += unique.params[0].toInt() + return resourceRequirements + } + + fun requiresResource(resource: String): Boolean { + if (getResourceRequirementsPerTurn().contains(resource)) return true + for (unique in getMatchingUniques(UniqueType.CostsResources, StateForConditionals(civ, unit = this))) { + if (unique.params[1] == resource) return true + } + return false + } fun getMaxMovement(): Int { var movement = diff --git a/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt b/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt index 093eab9dbf..062e8ff77b 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt @@ -62,7 +62,7 @@ class UnitUpgradeManager(val unit:MapUnit) { ): Boolean { if (unit.name == unitToUpgradeTo.name) return false - val rejectionReasons = unitToUpgradeTo.getRejectionReasons(unit.civ, additionalResources = unit.baseUnit.getResourceRequirementsPerTurn()) + val rejectionReasons = unitToUpgradeTo.getRejectionReasons(unit.civ, additionalResources = unit.getResourceRequirementsPerTurn()) var relevantRejectionReasons = rejectionReasons.filterNot { it.type == RejectionReasonType.Unbuildable } if (ignoreRequirements) diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index 7fe306712e..2b8700e920 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -11,6 +11,7 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.ui.components.extensions.toPercent import com.unciv.ui.screens.victoryscreen.RankingType @@ -127,9 +128,11 @@ class TradeEvaluation { val amountToBuyInOffer = min(amountWillingToBuy, offer.amount) val canUseForBuildings = civInfo.cities - .any { city -> city.cityConstructions.getBuildableBuildings().any { it.getResourceRequirementsPerTurn().containsKey(offer.name) } } + .any { city -> city.cityConstructions.getBuildableBuildings().any { + it.getResourceRequirementsPerTurn(StateForConditionals(civInfo, city)).containsKey(offer.name) } } val canUseForUnits = civInfo.cities - .any { city -> city.cityConstructions.getConstructableUnits().any { it.getResourceRequirementsPerTurn().containsKey(offer.name) } } + .any { city -> city.cityConstructions.getConstructableUnits().any { + it.getResourceRequirementsPerTurn(StateForConditionals(civInfo)).containsKey(offer.name) } } if (!canUseForBuildings && !canUseForUnits) return 0 return 50 * amountToBuyInOffer @@ -229,7 +232,7 @@ class TradeEvaluation { if (!civInfo.isAtWar()) return 50 * offer.amount val canUseForUnits = civInfo.gameInfo.ruleset.units.values - .any { it.getResourceRequirementsPerTurn().containsKey(offer.name) + .any { it.getResourceRequirementsPerTurn(StateForConditionals(civInfo)).containsKey(offer.name) && it.isBuildable(civInfo) } if (!canUseForUnits) return 50 * offer.amount diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index a223208e9c..c1d3f28b3e 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -86,7 +86,6 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList() tileBonusHashmap[stats]!!.add(unique.params[1]) } - unique.isOfType(UniqueType.ConsumesResources) -> Unit // skip these, else -> yield(unique.text) } for ((key, value) in tileBonusHashmap) @@ -116,7 +115,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { if (isWonder) translatedLines += "Wonder".tr() if (isNationalWonder) translatedLines += "National Wonder".tr() if (!isFree) { - for ((resourceName, amount) in getResourceRequirementsPerTurn()) { + for ((resourceName, amount) in getResourceRequirementsPerTurn(StateForConditionals(city.civ, city))) { val available = city.getResourceAmount(resourceName) val resource = city.getRuleset().tileResources[resourceName] ?: continue val consumesString = resourceName.getConsumesAmountString(amount, resource.isStockpiled()) @@ -247,15 +246,12 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { textList += FormattedLine("Requires [$requiredBuilding] to be built in the city", link="Building/$requiredBuilding") - val resourceRequirements = getResourceRequirementsPerTurn() - if (resourceRequirements.isNotEmpty()) { + if (requiredResource != null) { textList += FormattedLine() - for ((resourceName, amount) in resourceRequirements) { - val resource = ruleset.tileResources[resourceName] ?: continue - textList += FormattedLine( - resourceName.getConsumesAmountString(amount, resource.isStockpiled()), - link="Resources/$resourceName", color="#F42" ) - } + val resource = ruleset.tileResources[requiredResource] + textList += FormattedLine( + requiredResource!!.getConsumesAmountString(1, resource!!.isStockpiled()), + link="Resources/$requiredResource", color="#F42" ) } val stats = cloneStats() @@ -269,7 +265,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { } else if (uniques.isNotEmpty()) { for (unique in uniqueObjects) { if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - if (unique.type == UniqueType.ConsumesResources) continue // already shown from getResourceRequirements + if (unique.type == UniqueType.ConsumesResources) { + textList += FormattedLine(unique.text, link = "Resources/${unique.params[1]}", color = "#F42") + continue + } textList += FormattedLine(unique) } } @@ -620,7 +619,9 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { yield(RejectionReasonType.RequiresBuildingInThisCity.toInstance("Requires a [${civ.getEquivalentBuilding(requiredBuilding!!)}] in this city")) } - for ((resourceName, requiredAmount) in getResourceRequirementsPerTurn()) { + for ((resourceName, requiredAmount) in getResourceRequirementsPerTurn( + StateForConditionals(cityConstructions.city.civ, cityConstructions.city)) + ) { val availableAmount = cityConstructions.city.getResourceAmount(resourceName) if (availableAmount < requiredAmount) { yield(RejectionReasonType.ConsumesResources.toInstance(resourceName.getNeedMoreAmountString(requiredAmount - availableAmount))) @@ -704,20 +705,17 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable) - override fun getResourceRequirementsPerTurn(): Counter = resourceRequirementsInternal - - private val resourceRequirementsInternal: Counter by lazy { + override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?): Counter { val resourceRequirements = Counter() if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 - for (unique in uniqueObjects) - if (unique.isOfType(UniqueType.ConsumesResources)) - resourceRequirements[unique.params[1]] = unique.params[0].toInt() - resourceRequirements + for (unique in getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals)) + resourceRequirements[unique.params[1]] += unique.params[0].toInt() + return resourceRequirements } - override fun requiresResource(resource: String): Boolean { - if (resourceRequirementsInternal.contains(resource)) return true - for (unique in getMatchingUniques(UniqueType.CostsResources)) { + override fun requiresResource(resource: String, stateForConditionals: StateForConditionals?): Boolean { + if (getResourceRequirementsPerTurn(stateForConditionals).contains(resource)) return true + for (unique in getMatchingUniques(UniqueType.CostsResources, stateForConditionals)) { if (unique.params[1] == resource) return true } return false diff --git a/core/src/com/unciv/models/ruleset/IConstruction.kt b/core/src/com/unciv/models/ruleset/IConstruction.kt index 82cf6243c0..867fbeaa92 100644 --- a/core/src/com/unciv/models/ruleset/IConstruction.kt +++ b/core/src/com/unciv/models/ruleset/IConstruction.kt @@ -18,9 +18,10 @@ import kotlin.math.roundToInt interface IConstruction : INamed { fun isBuildable(cityConstructions: CityConstructions): Boolean fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean - /** Gets *per turn* resource requirements - does not include immediate costs for stockpiled resources */ - fun getResourceRequirementsPerTurn(): Counter - fun requiresResource(resource: String): Boolean + /** Gets *per turn* resource requirements - does not include immediate costs for stockpiled resources. + * Uses [stateForConditionals] to determine which civ or city this is built for*/ + fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals? = null): Counter + fun requiresResource(resource: String, stateForConditionals: StateForConditionals? = null): Boolean /** We can't call this getMatchingUniques because then it would conflict with IHasUniques */ fun getMatchingUniquesNotConflicting(uniqueType: UniqueType) = sequenceOf() } @@ -206,7 +207,6 @@ enum class RejectionReasonType(val shouldShow: Boolean, val errorMessage: String } } - open class PerpetualConstruction(override var name: String, val description: String) : IConstruction { @@ -232,9 +232,9 @@ open class PerpetualConstruction(override var name: String, val description: Str override fun isBuildable(cityConstructions: CityConstructions): Boolean = throw Exception("Impossible!") - override fun getResourceRequirementsPerTurn() = Counter.ZERO + override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?) = Counter.ZERO - override fun requiresResource(resource: String) = false + override fun requiresResource(resource: String, stateForConditionals: StateForConditionals?) = false } diff --git a/core/src/com/unciv/models/ruleset/tile/TileResource.kt b/core/src/com/unciv/models/ruleset/tile/TileResource.kt index f57bc5b3b5..b8261b2c0a 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileResource.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileResource.kt @@ -5,6 +5,7 @@ import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetStatsObject +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueType @@ -101,7 +102,8 @@ class TileResource : RulesetStatsObject() { } } - val buildingsThatConsumeThis = ruleset.buildings.values.filter { it.getResourceRequirementsPerTurn().containsKey(name) } + val buildingsThatConsumeThis = ruleset.buildings.values.filter { it.getResourceRequirementsPerTurn( + StateForConditionals.IgnoreConditionals).containsKey(name) } if (buildingsThatConsumeThis.isNotEmpty()) { textList += FormattedLine() textList += FormattedLine("{Buildings that consume this resource}:") @@ -110,7 +112,8 @@ class TileResource : RulesetStatsObject() { } } - val unitsThatConsumeThis = ruleset.units.values.filter { it.getResourceRequirementsPerTurn().containsKey(name) } + val unitsThatConsumeThis = ruleset.units.values.filter { it.getResourceRequirementsPerTurn( + StateForConditionals.IgnoreConditionals).containsKey(name) } if (unitsThatConsumeThis.isNotEmpty()) { textList += FormattedLine() textList += FormattedLine("{Units that consume this resource}: ") diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 9f0ecf4bb2..2ceba63022 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -40,7 +40,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { val type by lazy { ruleset.unitTypes[unitType]!! } override var requiredTech: String? = null - private var requiredResource: String? = null + var requiredResource: String? = null override fun getUniqueTarget() = UniqueTarget.Unit @@ -182,7 +182,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { if (!civ.isBarbarian()) { // Barbarians don't need resources val civResources = Counter(civ.getCivResourcesByName()) + additionalResources - for ((resource, requiredAmount) in getResourceRequirementsPerTurn()) { + for ((resource, requiredAmount) in getResourceRequirementsPerTurn(StateForConditionals(civ))) { val availableAmount = civResources[resource] if (availableAmount < requiredAmount) { val message = resource.getNeedMoreAmountString(requiredAmount - availableAmount) @@ -310,17 +310,21 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { fun movesLikeAirUnits() = type.getMovementType() == UnitMovementType.Air /** Returns resource requirements from both uniques and requiredResource field */ - override fun getResourceRequirementsPerTurn(): Counter = resourceRequirementsInternal - - private val resourceRequirementsInternal: Counter by lazy { + override fun getResourceRequirementsPerTurn(stateForConditionals: StateForConditionals?): Counter { val resourceRequirements = Counter() if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 - for (unique in getMatchingUniques(UniqueType.ConsumesResources)) - resourceRequirements[unique.params[1]] = unique.params[0].toInt() - resourceRequirements + for (unique in getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals)) + resourceRequirements[unique.params[1]] += unique.params[0].toInt() + return resourceRequirements } - override fun requiresResource(resource: String) = getResourceRequirementsPerTurn().containsKey(resource) + override fun requiresResource(resource: String, stateForConditionals: StateForConditionals?): Boolean { + if (getResourceRequirementsPerTurn(stateForConditionals).contains(resource)) return true + for (unique in getMatchingUniques(UniqueType.CostsResources, stateForConditionals)) { + if (unique.params[1] == resource) return true + } + return false + } fun isRanged() = rangedStrength > 0 fun isMelee() = !isRanged() && strength > 0 diff --git a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt index 4004d18d51..7af75fcaa6 100644 --- a/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt +++ b/core/src/com/unciv/models/ruleset/validation/RulesetValidator.kt @@ -433,7 +433,7 @@ class RulesetValidator(val ruleset: Ruleset) { for (specialistName in building.specialistSlots.keys) if (!ruleset.specialists.containsKey(specialistName)) lines += "${building.name} provides specialist $specialistName which does not exist!" - for (resource in building.getResourceRequirementsPerTurn().keys) + for (resource in building.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys) if (!ruleset.tileResources.containsKey(resource)) lines += "${building.name} requires resource $resource which does not exist!" if (building.replaces != null && !ruleset.buildings.containsKey(building.replaces!!)) @@ -649,7 +649,7 @@ class RulesetValidator(val ruleset: Ruleset) { ) } - for (resource in unit.getResourceRequirementsPerTurn().keys) + for (resource in unit.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys) if (!ruleset.tileResources.containsKey(resource)) lines += "${unit.name} requires resource $resource which does not exist!" if (unit.replaces != null && !ruleset.units.containsKey(unit.replaces!!)) diff --git a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt index 8bbf2678c2..f9f110c272 100644 --- a/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt +++ b/core/src/com/unciv/ui/objectdescriptions/BaseUnitDescriptions.kt @@ -7,6 +7,7 @@ import com.unciv.logic.city.City import com.unciv.models.metadata.GameSettings import com.unciv.models.ruleset.IRulesetObject import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueFlag import com.unciv.models.ruleset.unique.UniqueType @@ -43,7 +44,7 @@ object BaseUnitDescriptions { fun getDescription(baseUnit: BaseUnit, city: City): String { val lines = mutableListOf() val availableResources = city.civ.getCivResourcesByName() - for ((resourceName, amount) in baseUnit.getResourceRequirementsPerTurn()) { + for ((resourceName, amount) in baseUnit.getResourceRequirementsPerTurn(StateForConditionals(city.civ))) { val available = availableResources[resourceName] ?: 0 val resource = baseUnit.ruleset.tileResources[resourceName] ?: continue val consumesString = resourceName.getConsumesAmountString(amount, resource.isStockpiled()) @@ -60,7 +61,6 @@ object BaseUnitDescriptions { if (baseUnit.replacementTextForUniques != "") lines += baseUnit.replacementTextForUniques else for (unique in baseUnit.uniqueObjects.filterNot { it.type == UniqueType.Unbuildable - || it.type == UniqueType.ConsumesResources // already shown from getResourceRequirements || it.type?.flags?.contains(UniqueFlag.HiddenToUsers) == true }) lines += unique.text.tr() @@ -110,21 +110,20 @@ object BaseUnitDescriptions { textList += FormattedLine() for (unique in baseUnit.uniqueObjects.sortedBy { it.text }) { if (unique.hasFlag(UniqueFlag.HiddenToUsers)) continue - if (unique.type == UniqueType.ConsumesResources) continue // already shown from getResourceRequirements + if (unique.type == UniqueType.ConsumesResources) { + textList += FormattedLine(unique.text, link = "Resources/${unique.params[1]}", color = "#F42") + continue + } textList += FormattedLine(unique) } } - val resourceRequirements = baseUnit.getResourceRequirementsPerTurn() - if (resourceRequirements.isNotEmpty()) { + if (baseUnit.requiredResource != null) { textList += FormattedLine() - for ((resourceName, amount) in resourceRequirements) { - val resource = ruleset.tileResources[resourceName] ?: continue - textList += FormattedLine( - resourceName.getConsumesAmountString(amount, resource.isStockpiled()), - link = "Resource/$resource", color = "#F42" - ) - } + val resource = ruleset.tileResources[baseUnit.requiredResource] + textList += FormattedLine( + baseUnit.requiredResource!!.getConsumesAmountString(1, resource!!.isStockpiled()), + link="Resources/${baseUnit.requiredResource}", color="#F42") } if (baseUnit.uniqueTo != null) { @@ -293,8 +292,8 @@ object BaseUnitDescriptions { if (betterUnit.movement != originalUnit.movement) yield("${Fonts.movement} {[${betterUnit.movement}] vs [${originalUnit.movement}]}" to null) - for (resource in originalUnit.getResourceRequirementsPerTurn().keys) - if (!betterUnit.getResourceRequirementsPerTurn().containsKey(resource)) { + for (resource in originalUnit.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).keys) + if (!betterUnit.getResourceRequirementsPerTurn(StateForConditionals.IgnoreConditionals).containsKey(resource)) { yield("[$resource] not required" to "Resource/$resource") } // We return the unique text directly, so Nation.getUniqueUnitsText will not use the diff --git a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt index 73670aab83..53a028292a 100644 --- a/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt +++ b/core/src/com/unciv/ui/screens/cityscreen/CityConstructionsTable.kt @@ -19,6 +19,7 @@ import com.unciv.models.ruleset.INonPerpetualConstruction import com.unciv.models.ruleset.PerpetualConstruction import com.unciv.models.ruleset.RejectionReason import com.unciv.models.ruleset.RejectionReasonType +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.stats.Stat @@ -210,7 +211,9 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { val useStoredProduction = entry is Building || !cityConstructions.isBeingConstructedOrEnqueued(entry.name) val buttonText = cityConstructions.getTurnsToConstructionString(entry, useStoredProduction).trim() - val resourcesRequired = entry.getResourceRequirementsPerTurn() + val resourcesRequired = if (entry is BaseUnit) + entry.getResourceRequirementsPerTurn(StateForConditionals(city.civ)) + else entry.getResourceRequirementsPerTurn(StateForConditionals(city.civ, city)) val mostImportantRejection = entry.getRejectionReasons(cityConstructions) .filter { it.isImportantRejection() } @@ -325,7 +328,9 @@ class CityConstructionsTable(private val cityScreen: CityScreen) { if (constructionName in PerpetualConstruction.perpetualConstructionsMap) "\n∞" else cityConstructions.getTurnsToConstructionString(construction, isFirstConstructionOfItsKind) - val constructionResource = construction.getResourceRequirementsPerTurn() + val constructionResource = if (construction is BaseUnit) + construction.getResourceRequirementsPerTurn(StateForConditionals(city.civ, city)) + else construction.getResourceRequirementsPerTurn(StateForConditionals(city.civ)) for ((resourceName, amount) in constructionResource) { val resource = cityConstructions.city.getRuleset().tileResources[resourceName] ?: continue text += "\n" + resourceName.getConsumesAmountString(amount, resource.isStockpiled()).tr() diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt index 23a2c6d114..06f924bafa 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt @@ -298,9 +298,9 @@ object UnitActionsFromUniques { // Check _new_ resource requirements // Using Counter to aggregate is a bit exaggerated, but - respect the mad modder. val resourceRequirementsDelta = Counter() - for ((resource, amount) in unit.baseUnit().getResourceRequirementsPerTurn()) + for ((resource, amount) in unit.getResourceRequirementsPerTurn()) resourceRequirementsDelta.add(resource, -amount) - for ((resource, amount) in unitToTransformTo.getResourceRequirementsPerTurn()) + for ((resource, amount) in unitToTransformTo.getResourceRequirementsPerTurn(StateForConditionals(unit.civ, unit = unit))) resourceRequirementsDelta.add(resource, amount) val newResourceRequirementsString = resourceRequirementsDelta.entries .filter { it.value > 0 } diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsUpgrade.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsUpgrade.kt index b64d6e7bd2..ffb33e0b47 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsUpgrade.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsUpgrade.kt @@ -4,6 +4,7 @@ import com.unciv.logic.map.mapunit.MapUnit import com.unciv.models.Counter import com.unciv.models.UnitAction import com.unciv.models.UpgradeUnitAction +import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.translations.tr @@ -43,9 +44,9 @@ object UnitActionsUpgrade { // Check _new_ resource requirements (display only - yes even for free or special upgrades) // Using Counter to aggregate is a bit exaggerated, but - respect the mad modder. val resourceRequirementsDelta = Counter() - for ((resource, amount) in unit.baseUnit().getResourceRequirementsPerTurn()) + for ((resource, amount) in unit.getResourceRequirementsPerTurn()) resourceRequirementsDelta.add(resource, -amount) - for ((resource, amount) in upgradedUnit.getResourceRequirementsPerTurn()) + for ((resource, amount) in upgradedUnit.getResourceRequirementsPerTurn(StateForConditionals(unit.civ, unit = unit))) resourceRequirementsDelta.add(resource, amount) for ((resource, _) in resourceRequirementsDelta.filter { it.value < 0 }) // filter copies, so no CCM resourceRequirementsDelta[resource] = 0