mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-29 06:51:30 -04:00
Resource stockpiles! (#9147)
* Resource stockpiles! * toString extension including sign (+/-) * Trigger uniques to provide/consume stockpiled resources * Fixed build * Display 'per turn' for stockpiled resources that are consumed per turn * "Costs [amount] [resource]" works! * Stockpile unique costs are displayed in construction button * Added unique to prevert certain resources from being traded
This commit is contained in:
parent
0c60f87b27
commit
adb51d9264
@ -963,6 +963,8 @@ You may choose a free Policy =
|
||||
You may choose [amount] free Policies =
|
||||
You gain the [policy] Policy =
|
||||
You enter a Golden Age =
|
||||
You have gained [amount] [resourceName] =
|
||||
You have lost [amount] [resourceName] =
|
||||
|
||||
## Trigger causes
|
||||
|
||||
|
@ -591,7 +591,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
|
||||
spaceResources.clear()
|
||||
spaceResources.addAll(ruleset.buildings.values.filter { it.hasUnique(UniqueType.SpaceshipPart) }
|
||||
.flatMap { it.getResourceRequirements().keys })
|
||||
.flatMap { it.getResourceRequirementsPerTurn().keys })
|
||||
spaceResources.addAll(ruleset.victories.values.flatMap { it.requiredSpaceshipParts })
|
||||
|
||||
barbarians.setTransients(this)
|
||||
|
@ -263,7 +263,7 @@ object Automation {
|
||||
if (construction.name in civInfo.gameInfo.spaceResources)
|
||||
return true
|
||||
|
||||
val requiredResources = construction.getResourceRequirements()
|
||||
val requiredResources = construction.getResourceRequirementsPerTurn()
|
||||
// Does it even require any resources?
|
||||
if (requiredResources.isEmpty())
|
||||
return true
|
||||
@ -281,9 +281,9 @@ object Automation {
|
||||
for (city in civInfo.cities) {
|
||||
val otherConstruction = city.cityConstructions.getCurrentConstruction()
|
||||
if (otherConstruction is Building)
|
||||
futureForBuildings += otherConstruction.getResourceRequirements()[resource] ?: 0
|
||||
futureForBuildings += otherConstruction.getResourceRequirementsPerTurn()[resource] ?: 0
|
||||
else
|
||||
futureForUnits += otherConstruction.getResourceRequirements()[resource] ?: 0
|
||||
futureForUnits += otherConstruction.getResourceRequirementsPerTurn()[resource] ?: 0
|
||||
}
|
||||
|
||||
// Make sure we have some for space
|
||||
|
@ -121,7 +121,7 @@ object UnitAutomation {
|
||||
val upgradedUnit = unit.upgrade.getUnitToUpgradeTo()
|
||||
if (!upgradedUnit.isBuildable(unit.civ)) return false // for resource reasons, usually
|
||||
|
||||
if (upgradedUnit.getResourceRequirements().keys.any { !unit.baseUnit.requiresResource(it) }) {
|
||||
if (upgradedUnit.getResourceRequirementsPerTurn().keys.any { !unit.baseUnit.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
|
||||
}
|
||||
|
@ -818,7 +818,7 @@ object Battle {
|
||||
|
||||
var damageModifierFromMissingResource = 1f
|
||||
val civResources = attacker.getCivInfo().getCivResourcesByName()
|
||||
for (resource in attacker.unit.baseUnit.getResourceRequirements().keys) {
|
||||
for (resource in attacker.unit.baseUnit.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
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ object BattleDamage {
|
||||
}
|
||||
|
||||
val civResources = civInfo.getCivResourcesByName()
|
||||
for (resource in combatant.unit.baseUnit.getResourceRequirements().keys)
|
||||
for (resource in combatant.unit.baseUnit.getResourceRequirementsPerTurn().keys)
|
||||
if (civResources[resource]!! < 0 && !civInfo.isBarbarian())
|
||||
modifiers["Missing resource"] = -25 //todo ModConstants
|
||||
|
||||
|
@ -222,7 +222,7 @@ class City : IsPartOfGameInfoSerialization {
|
||||
for (building in cityConstructions.getBuiltBuildings()) {
|
||||
// Free buildings cost no resources
|
||||
if (building.name in freeBuildings) continue
|
||||
cityResources.subtractResourceRequirements(building.getResourceRequirements(), getRuleset(), "Buildings")
|
||||
cityResources.subtractResourceRequirements(building.getResourceRequirementsPerTurn(), getRuleset(), "Buildings")
|
||||
}
|
||||
|
||||
for (unique in getLocalMatchingUniques(UniqueType.ProvidesResources, StateForConditionals(civ, this))) { // E.G "Provides [1] [Iron]"
|
||||
|
@ -349,7 +349,19 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
constructionQueue.clear()
|
||||
|
||||
for (constructionName in queueSnapshot) {
|
||||
if (getConstruction(constructionName).isBuildable(this))
|
||||
val construction = getConstruction(constructionName)
|
||||
// First construction will be built next turn, we need to make sure it has the correct resources
|
||||
if (constructionQueue.isEmpty() && getWorkDone(constructionName) == 0) {
|
||||
val costUniques = construction.getMatchingUniquesNotConflicting(UniqueType.CostsResources)
|
||||
val civResources = city.civ.getCivResourcesByName()
|
||||
|
||||
if (costUniques.any {
|
||||
val resourceName = it.params[1]
|
||||
civResources[resourceName] == null
|
||||
|| it.params[0].toInt() > civResources[resourceName]!! })
|
||||
continue // Removes this construction from the queue
|
||||
}
|
||||
if (construction.isBuildable(this))
|
||||
constructionQueue.add(constructionName)
|
||||
}
|
||||
}
|
||||
@ -392,6 +404,14 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
private fun constructionBegun(construction: IConstruction) {
|
||||
val costUniques = construction.getMatchingUniquesNotConflicting(UniqueType.CostsResources)
|
||||
|
||||
for (unique in costUniques){
|
||||
val amount = unique.params[0].toInt()
|
||||
val resourceName = unique.params[1]
|
||||
city.civ.resourceStockpiles.add(resourceName, -amount)
|
||||
}
|
||||
|
||||
if (construction !is Building) return
|
||||
if (!construction.hasUnique(UniqueType.TriggersAlertOnStart)) return
|
||||
val buildingIcon = "BuildingIcons/${construction.name}"
|
||||
|
@ -31,6 +31,7 @@ import com.unciv.logic.civilization.transients.CivInfoTransientCache
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.logic.trade.TradeRequest
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.ruleset.Building
|
||||
import com.unciv.models.ruleset.Policy
|
||||
import com.unciv.models.ruleset.Victory
|
||||
@ -111,7 +112,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
var detailedCivResources = ResourceSupplyList()
|
||||
|
||||
@Transient
|
||||
var summarizedCivResources = ResourceSupplyList()
|
||||
var summarizedCivResourceSupply = ResourceSupplyList()
|
||||
|
||||
@Transient
|
||||
val cityStateFunctions = CityStateFunctions(this)
|
||||
@ -172,6 +173,8 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
/** See DiplomacyManager.flagsCountdown for why this does not map Enums to ints */
|
||||
var flagsCountdown = HashMap<String, Int>()
|
||||
|
||||
var resourceStockpiles = Counter<String>()
|
||||
|
||||
/** Arraylist instead of HashMap as the same unique might appear multiple times
|
||||
* We don't use pairs, as these cannot be serialized due to having no no-arg constructor
|
||||
* We ALSO can't use a class inheriting from ArrayList<TemporaryUnique>() because ANNOYINGLY that doesn't pass deserialization
|
||||
@ -288,6 +291,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
toReturn.attacksSinceTurnStart = attacksSinceTurnStart.copy()
|
||||
toReturn.hasMovedAutomatedUnits = hasMovedAutomatedUnits
|
||||
toReturn.statsHistory = statsHistory.clone()
|
||||
toReturn.resourceStockpiles = resourceStockpiles.clone()
|
||||
return toReturn
|
||||
}
|
||||
|
||||
@ -376,12 +380,18 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
|
||||
fun getHappiness() = stats.happiness
|
||||
|
||||
fun getCivResources(): ResourceSupplyList = summarizedCivResources
|
||||
/** Note that for stockpiled resources, this gives by how much it grows per turn, not current amount */
|
||||
fun getCivResourceSupply(): ResourceSupplyList = summarizedCivResourceSupply
|
||||
|
||||
// Preserves some origins for resources so we can separate them for trades
|
||||
/** Preserves some origins for resources so we can separate them for trades
|
||||
* Stockpiled uniques cannot be traded currently
|
||||
*/
|
||||
fun getCivResourcesWithOriginsForTrade(): ResourceSupplyList {
|
||||
val newResourceSupplyList = ResourceSupplyList(keepZeroAmounts = true)
|
||||
|
||||
for (resourceSupply in detailedCivResources) {
|
||||
if (resourceSupply.resource.isStockpiled()) continue
|
||||
if (resourceSupply.resource.hasUnique(UniqueType.CannotBeTraded)) continue
|
||||
// If we got it from another trade or from a CS, preserve the origin
|
||||
if (resourceSupply.isCityStateOrTradeOrigin()) {
|
||||
newResourceSupplyList.add(resourceSupply.copy())
|
||||
@ -398,12 +408,16 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
|
||||
/**
|
||||
* Returns a dictionary of ALL resource names, and the amount that the civ has of each
|
||||
* Stockpiled resources return the stockpiled amount
|
||||
*/
|
||||
fun getCivResourcesByName(): HashMap<String, Int> {
|
||||
val hashMap = HashMap<String, Int>(gameInfo.ruleset.tileResources.size)
|
||||
for (resource in gameInfo.ruleset.tileResources.keys) hashMap[resource] = 0
|
||||
for (entry in getCivResources())
|
||||
hashMap[entry.resource.name] = entry.amount
|
||||
for (entry in getCivResourceSupply())
|
||||
if (!entry.resource.isStockpiled())
|
||||
hashMap[entry.resource.name] = entry.amount
|
||||
for ((key, value) in resourceStockpiles)
|
||||
hashMap[key] = value
|
||||
return hashMap
|
||||
}
|
||||
|
||||
@ -442,7 +456,7 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
yieldAll(religionManager.religion!!.getFounderUniques()
|
||||
.filter { it.isOfType(uniqueType) && it.conditionalsApply(stateForConditionals) })
|
||||
|
||||
yieldAll(getCivResources().asSequence()
|
||||
yieldAll(getCivResourceSupply().asSequence()
|
||||
.filter { it.amount > 0 }
|
||||
.flatMap { it.resource.getMatchingUniques(uniqueType, stateForConditionals) }
|
||||
)
|
||||
|
@ -404,6 +404,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
val isResourceFilter: (TradeOffer) -> Boolean = {
|
||||
(it.type == TradeType.Strategic_Resource || it.type == TradeType.Luxury_Resource)
|
||||
&& resourcesMap.containsKey(it.name)
|
||||
&& !resourcesMap[it.name]!!.isStockpiled()
|
||||
}
|
||||
for (trade in trades) {
|
||||
for (offer in trade.ourOffers.filter(isResourceFilter))
|
||||
@ -436,8 +437,8 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization {
|
||||
|
||||
// Every cancelled trade can change this - if 1 resource is missing,
|
||||
// don't cancel all trades of that resource, only cancel one (the first one, as it happens, since they're added chronologically)
|
||||
val negativeCivResources = civInfo.getCivResources()
|
||||
.filter { it.amount < 0 }.map { it.resource.name }
|
||||
val negativeCivResources = civInfo.getCivResourceSupply()
|
||||
.filter { it.amount < 0 && !it.resource.isStockpiled() }.map { it.resource.name }
|
||||
|
||||
for (offer in trade.ourOffers) {
|
||||
if (offer.type in listOf(TradeType.Luxury_Resource, TradeType.Strategic_Resource)
|
||||
|
@ -36,6 +36,11 @@ class TurnManager(val civInfo: Civilization) {
|
||||
if (civInfo.cities.isNotEmpty() && civInfo.gameInfo.ruleset.technologies.isNotEmpty())
|
||||
civInfo.tech.updateResearchProgress()
|
||||
|
||||
|
||||
civInfo.cache.updateCivResources() // If you offered a trade last turn, this turn it will have been accepted/declined
|
||||
for (stockpiledResource in civInfo.getCivResourceSupply().filter { it.resource.isStockpiled() })
|
||||
civInfo.resourceStockpiles.add(stockpiledResource.resource.name, stockpiledResource.amount)
|
||||
|
||||
civInfo.civConstructions.startTurn()
|
||||
civInfo.attacksSinceTurnStart.clear()
|
||||
civInfo.updateStatsForNextTurn() // for things that change when turn passes e.g. golden age, city state influence
|
||||
@ -70,8 +75,6 @@ class TurnManager(val civInfo: Civilization) {
|
||||
unit.doAction()
|
||||
} else civInfo.hasMovedAutomatedUnits = false
|
||||
|
||||
civInfo.cache.updateCivResources() // If you offered a trade last turn, this turn it will have been accepted/declined
|
||||
|
||||
for (tradeRequest in civInfo.tradeRequests.toList()) { // remove trade requests where one of the sides can no longer supply
|
||||
val offeringCiv = civInfo.gameInfo.getCivilization(tradeRequest.requestingCiv)
|
||||
if (offeringCiv.isDefeated() || !TradeEvaluation().isTradeValid(tradeRequest.trade, civInfo, offeringCiv)) {
|
||||
|
@ -98,7 +98,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.getResourceRequirements().isNotEmpty())
|
||||
if (mapUnit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty())
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,7 @@ class UnitManager(val civInfo:Civilization) {
|
||||
nextPotentiallyDueAt = 0
|
||||
|
||||
civInfo.updateStatsForNextTurn() // unit upkeep
|
||||
if (mapUnit.baseUnit.getResourceRequirements().isNotEmpty())
|
||||
if (mapUnit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty())
|
||||
civInfo.cache.updateCivResources()
|
||||
}
|
||||
|
||||
|
@ -230,10 +230,10 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) {
|
||||
for (unique in civInfo.getMatchingUniques(UniqueType.BonusHappinessFromLuxury))
|
||||
happinessPerUniqueLuxury += unique.params[0].toInt()
|
||||
|
||||
val ownedLuxuries = civInfo.getCivResources().map { it.resource }
|
||||
val ownedLuxuries = civInfo.getCivResourceSupply().map { it.resource }
|
||||
.filter { it.resourceType == ResourceType.Luxury }
|
||||
|
||||
val relevantLuxuries = civInfo.getCivResources().asSequence()
|
||||
val relevantLuxuries = civInfo.getCivResourceSupply().asSequence()
|
||||
.map { it.resource }
|
||||
.count { it.resourceType == ResourceType.Luxury
|
||||
&& it.getMatchingUniques(UniqueType.ObsoleteWith)
|
||||
@ -245,7 +245,7 @@ class CivInfoStatsForNextTurn(val civInfo: Civilization) {
|
||||
|
||||
val luxuriesProvidedByCityStates = civInfo.getKnownCivs().asSequence()
|
||||
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
|
||||
.flatMap { it.getCivResources().map { res -> res.resource } }
|
||||
.flatMap { it.getCivResourceSupply().map { res -> res.resource } }
|
||||
.distinct()
|
||||
.count { it.resourceType === ResourceType.Luxury && ownedLuxuries.contains(it) }
|
||||
|
||||
|
@ -305,13 +305,13 @@ class CivInfoTransientCache(val civInfo: Civilization) {
|
||||
|
||||
for (unit in civInfo.units.getCivUnits())
|
||||
newDetailedCivResources.subtractResourceRequirements(
|
||||
unit.baseUnit.getResourceRequirements(), civInfo.gameInfo.ruleset, "Units")
|
||||
unit.baseUnit.getResourceRequirementsPerTurn(), civInfo.gameInfo.ruleset, "Units")
|
||||
|
||||
// Check if anything has actually changed so we don't update stats for no reason - this uses List equality which means it checks the elements
|
||||
if (civInfo.detailedCivResources == newDetailedCivResources) return
|
||||
|
||||
civInfo.detailedCivResources = newDetailedCivResources
|
||||
civInfo.summarizedCivResources = newDetailedCivResources.sumByResource("All")
|
||||
civInfo.summarizedCivResourceSupply = newDetailedCivResources.sumByResource("All")
|
||||
|
||||
civInfo.updateStatsForNextTurn() // More or less resources = more or less happiness, with potential domino effects
|
||||
}
|
||||
|
@ -538,7 +538,7 @@ class TileMap : IsPartOfGameInfoSerialization {
|
||||
// And update civ stats, since the new unit changes both unit upkeep and resource consumption
|
||||
civInfo.updateStatsForNextTurn()
|
||||
|
||||
if (unit.baseUnit.getResourceRequirements().isNotEmpty())
|
||||
if (unit.baseUnit.getResourceRequirementsPerTurn().isNotEmpty())
|
||||
civInfo.cache.updateCivResources()
|
||||
|
||||
return unit
|
||||
|
@ -36,19 +36,20 @@ class TileInfoImprovementFunctions(val tile: Tile) {
|
||||
yield(ImprovementBuildingProblem.NotJustOutsideBorders)
|
||||
}
|
||||
|
||||
if (improvement.getMatchingUniques(UniqueType.OnlyAvailableWhen, StateForConditionals.IgnoreConditionals).any {
|
||||
!it.conditionalsApply(stateForConditionals)
|
||||
})
|
||||
if (improvement.getMatchingUniques(UniqueType.OnlyAvailableWhen, StateForConditionals.IgnoreConditionals)
|
||||
.any { !it.conditionalsApply(stateForConditionals) })
|
||||
yield(ImprovementBuildingProblem.UnmetConditional)
|
||||
|
||||
if (improvement.getMatchingUniques(UniqueType.ObsoleteWith, stateForConditionals).any {
|
||||
civInfo.tech.isResearched(it.params[0])
|
||||
})
|
||||
if (improvement.getMatchingUniques(UniqueType.ObsoleteWith, stateForConditionals)
|
||||
.any { civInfo.tech.isResearched(it.params[0]) })
|
||||
yield(ImprovementBuildingProblem.Obsolete)
|
||||
|
||||
if (improvement.getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals).any {
|
||||
civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt()
|
||||
})
|
||||
if (improvement.getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals)
|
||||
.any { civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() })
|
||||
yield(ImprovementBuildingProblem.MissingResources)
|
||||
|
||||
if (improvement.getMatchingUniques(UniqueType.CostsResources)
|
||||
.any { civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() })
|
||||
yield(ImprovementBuildingProblem.MissingResources)
|
||||
|
||||
val knownFeatureRemovals = tile.ruleset.tileImprovements.values
|
||||
|
@ -118,9 +118,9 @@ class TradeEvaluation {
|
||||
val amountToBuyInOffer = min(amountWillingToBuy, offer.amount)
|
||||
|
||||
val canUseForBuildings = civInfo.cities
|
||||
.any { city -> city.cityConstructions.getBuildableBuildings().any { it.getResourceRequirements().containsKey(offer.name) } }
|
||||
.any { city -> city.cityConstructions.getBuildableBuildings().any { it.getResourceRequirementsPerTurn().containsKey(offer.name) } }
|
||||
val canUseForUnits = civInfo.cities
|
||||
.any { city -> city.cityConstructions.getConstructableUnits().any { it.getResourceRequirements().containsKey(offer.name) } }
|
||||
.any { city -> city.cityConstructions.getConstructableUnits().any { it.getResourceRequirementsPerTurn().containsKey(offer.name) } }
|
||||
if (!canUseForBuildings && !canUseForUnits) return 0
|
||||
|
||||
return 50 * amountToBuyInOffer
|
||||
@ -217,7 +217,7 @@ class TradeEvaluation {
|
||||
if (!civInfo.isAtWar()) return 50 * offer.amount
|
||||
|
||||
val canUseForUnits = civInfo.gameInfo.ruleset.units.values
|
||||
.any { it.getResourceRequirements().containsKey(offer.name)
|
||||
.any { it.getResourceRequirementsPerTurn().containsKey(offer.name)
|
||||
&& it.isBuildable(civInfo) }
|
||||
if (!canUseForUnits) return 50 * offer.amount
|
||||
|
||||
|
@ -122,12 +122,14 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
if (isNationalWonder) lines += "National Wonder"
|
||||
if (!isFree) {
|
||||
val availableResources = if (!showAdditionalInfo) emptyMap()
|
||||
else city.civ.getCivResources().associate { it.resource.name to it.amount }
|
||||
for ((resource, amount) in getResourceRequirements()) {
|
||||
val available = availableResources[resource] ?: 0
|
||||
lines += if (showAdditionalInfo)
|
||||
"{${resource.getConsumesAmountString(amount)}} ({[$available] available})"
|
||||
else resource.getConsumesAmountString(amount)
|
||||
else city.civ.getCivResourcesByName()
|
||||
for ((resourceName, amount) in getResourceRequirementsPerTurn()) {
|
||||
val available = availableResources[resourceName] ?: 0
|
||||
val resource = city.getRuleset().tileResources[resourceName] ?: continue
|
||||
val consumesString = resourceName.getConsumesAmountString(amount, resource.isStockpiled())
|
||||
|
||||
lines += if (showAdditionalInfo) "$consumesString ({[$available] available})"
|
||||
else consumesString
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,14 +256,14 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
textList += FormattedLine("Requires [$requiredBuilding] to be built in the city",
|
||||
link="Building/$requiredBuilding")
|
||||
|
||||
val resourceRequirements = getResourceRequirements()
|
||||
val resourceRequirements = getResourceRequirementsPerTurn()
|
||||
if (resourceRequirements.isNotEmpty()) {
|
||||
textList += FormattedLine()
|
||||
for ((resource, amount) in resourceRequirements) {
|
||||
for ((resourceName, amount) in resourceRequirements) {
|
||||
val resource = ruleset.tileResources[resourceName] ?: continue
|
||||
textList += FormattedLine(
|
||||
// the 1 variant should deprecate some time
|
||||
resource.getConsumesAmountString(amount),
|
||||
link="Resources/$resource", color="#F42" )
|
||||
resourceName.getConsumesAmountString(amount, resource.isStockpiled()),
|
||||
link="Resources/$resourceName", color="#F42" )
|
||||
}
|
||||
}
|
||||
|
||||
@ -615,7 +617,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
yield(RejectionReasonType.RequiresBuildingInThisCity.toInstance("Requires a [${civ.getEquivalentBuilding(requiredBuilding!!)}] in this city"))
|
||||
}
|
||||
|
||||
for ((resource, requiredAmount) in getResourceRequirements()) {
|
||||
for ((resource, requiredAmount) in getResourceRequirementsPerTurn()) {
|
||||
val availableAmount = civ.getCivResourcesByName()[resource]!!
|
||||
if (availableAmount < requiredAmount) {
|
||||
yield(RejectionReasonType.ConsumesResources.toInstance(resource.getNeedMoreAmountString(requiredAmount - availableAmount)))
|
||||
@ -740,7 +742,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
|
||||
fun isSellable() = !isAnyWonder() && !hasUnique(UniqueType.Unsellable)
|
||||
|
||||
override fun getResourceRequirements(): HashMap<String, Int> = resourceRequirementsInternal
|
||||
override fun getResourceRequirementsPerTurn(): HashMap<String, Int> = resourceRequirementsInternal
|
||||
|
||||
private val resourceRequirementsInternal: HashMap<String, Int> by lazy {
|
||||
val resourceRequirements = HashMap<String, Int>()
|
||||
@ -756,6 +758,9 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
for (unique in getMatchingUniques(UniqueType.ConsumesResources)) {
|
||||
if (unique.params[1] == resource) return true
|
||||
}
|
||||
for (unique in getMatchingUniques(UniqueType.CostsResources)) {
|
||||
if (unique.params[1] == resource) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package com.unciv.logic.city
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.ruleset.unique.IHasUniques
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.stats.INamed
|
||||
import com.unciv.models.stats.Stat
|
||||
@ -14,8 +15,11 @@ import kotlin.math.roundToInt
|
||||
interface IConstruction : INamed {
|
||||
fun isBuildable(cityConstructions: CityConstructions): Boolean
|
||||
fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean
|
||||
fun getResourceRequirements(): HashMap<String,Int>
|
||||
/** Gets *per turn* resource requirements - does not include immediate costs for stockpiled resources */
|
||||
fun getResourceRequirementsPerTurn(): HashMap<String,Int>
|
||||
fun requiresResource(resource: String): Boolean
|
||||
/** We can't call this getMatchingUniques because then it would conflict with IHasUniques */
|
||||
fun getMatchingUniquesNotConflicting(uniqueType: UniqueType) = sequenceOf<Unique>()
|
||||
}
|
||||
|
||||
interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
|
||||
@ -82,6 +86,9 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
|
||||
fun getCostForConstructionsIncreasingInPrice(baseCost: Int, increaseCost: Int, previouslyBought: Int): Int {
|
||||
return (baseCost + increaseCost / 2f * ( previouslyBought * previouslyBought + previouslyBought )).toInt()
|
||||
}
|
||||
|
||||
override fun getMatchingUniquesNotConflicting(uniqueType: UniqueType): Sequence<Unique> =
|
||||
getMatchingUniques(uniqueType)
|
||||
}
|
||||
|
||||
|
||||
@ -210,7 +217,7 @@ open class PerpetualConstruction(override var name: String, val description: Str
|
||||
override fun isBuildable(cityConstructions: CityConstructions): Boolean =
|
||||
throw Exception("Impossible!")
|
||||
|
||||
override fun getResourceRequirements(): HashMap<String, Int> = hashMapOf()
|
||||
override fun getResourceRequirementsPerTurn(): HashMap<String, Int> = hashMapOf()
|
||||
|
||||
override fun requiresResource(resource: String) = false
|
||||
|
||||
|
@ -157,7 +157,7 @@ class RulesetValidator(val ruleset: Ruleset) {
|
||||
)
|
||||
}
|
||||
|
||||
for (resource in unit.getResourceRequirements().keys)
|
||||
for (resource in unit.getResourceRequirementsPerTurn().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!!))
|
||||
@ -189,7 +189,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.getResourceRequirements().keys)
|
||||
for (resource in building.getResourceRequirementsPerTurn().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!!))
|
||||
|
@ -236,8 +236,8 @@ class Nation : RulesetObject() {
|
||||
yield(FormattedLine("${Fonts.range} " + "[${unit.range}] vs [${originalUnit.range}]".tr(), indent=1))
|
||||
if (unit.movement != originalUnit.movement)
|
||||
yield(FormattedLine("${Fonts.movement} " + "[${unit.movement}] vs [${originalUnit.movement}]".tr(), indent=1))
|
||||
for (resource in originalUnit.getResourceRequirements().keys)
|
||||
if (!unit.getResourceRequirements().containsKey(resource)) {
|
||||
for (resource in originalUnit.getResourceRequirementsPerTurn().keys)
|
||||
if (!unit.getResourceRequirementsPerTurn().containsKey(resource)) {
|
||||
yield(FormattedLine("[$resource] not required", link="Resource/$resource", indent=1))
|
||||
}
|
||||
// This does not use the auto-linking FormattedLine(Unique) for two reasons:
|
||||
|
@ -62,7 +62,7 @@ class ResourceSupplyList(
|
||||
add(resourceSupply)
|
||||
}
|
||||
|
||||
/** Add entries from a requirements list (as produced by [IConstruction.getResourceRequirements]), expressing requirement as negative supply. */
|
||||
/** Add entries from a requirements list (as produced by [IConstruction.getResourceRequirementsPerTurn]), expressing requirement as negative supply. */
|
||||
fun subtractResourceRequirements(resourceRequirements: HashMap<String, Int>, ruleset: Ruleset, origin: String) {
|
||||
for ((resourceName, amount) in resourceRequirements) {
|
||||
val resource = ruleset.tileResources[resourceName] ?: continue
|
||||
|
@ -15,6 +15,7 @@ class TileResource : RulesetStatsObject() {
|
||||
var resourceType: ResourceType = ResourceType.Bonus
|
||||
var terrainsCanBeFoundOn: List<String> = listOf()
|
||||
var improvement: String? = null
|
||||
/** stats that this resource adds to a tile */
|
||||
var improvementStats: Stats? = null
|
||||
var revealedBy: String? = null
|
||||
var improvedBy: List<String> = listOf()
|
||||
@ -91,7 +92,7 @@ class TileResource : RulesetStatsObject() {
|
||||
}
|
||||
}
|
||||
|
||||
val buildingsThatConsumeThis = ruleset.buildings.values.filter { it.getResourceRequirements().containsKey(name) }
|
||||
val buildingsThatConsumeThis = ruleset.buildings.values.filter { it.getResourceRequirementsPerTurn().containsKey(name) }
|
||||
if (buildingsThatConsumeThis.isNotEmpty()) {
|
||||
textList += FormattedLine()
|
||||
textList += FormattedLine("{Buildings that consume this resource}:")
|
||||
@ -100,7 +101,7 @@ class TileResource : RulesetStatsObject() {
|
||||
}
|
||||
}
|
||||
|
||||
val unitsThatConsumeThis = ruleset.units.values.filter { it.getResourceRequirements().containsKey(name) }
|
||||
val unitsThatConsumeThis = ruleset.units.values.filter { it.getResourceRequirementsPerTurn().containsKey(name) }
|
||||
if (unitsThatConsumeThis.isNotEmpty()) {
|
||||
textList += FormattedLine()
|
||||
textList += FormattedLine("{Units that consume this resource}: ")
|
||||
@ -135,6 +136,8 @@ class TileResource : RulesetStatsObject() {
|
||||
}
|
||||
}
|
||||
|
||||
fun isStockpiled() = hasUnique(UniqueType.Stockpiled)
|
||||
|
||||
class DepositAmount {
|
||||
var sparse: Int = 1
|
||||
var default: Int = 2
|
||||
|
@ -330,6 +330,38 @@ object UniqueTriggerActivation {
|
||||
return true
|
||||
}
|
||||
|
||||
UniqueType.OneTimeProvideResources -> {
|
||||
val amount = unique.params[0].toInt()
|
||||
val resourceName = unique.params[1]
|
||||
val resource = ruleSet.tileResources[resourceName] ?: return false
|
||||
if (!resource.isStockpiled()) return false
|
||||
|
||||
civInfo.resourceStockpiles.add(resourceName, amount)
|
||||
|
||||
val notificationText = getNotificationText(notification, triggerNotificationText,
|
||||
"You have gained [$amount] [$resourceName]")
|
||||
?: return true
|
||||
|
||||
civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName")
|
||||
return true
|
||||
}
|
||||
|
||||
UniqueType.OneTimeConsumeResources -> {
|
||||
val amount = unique.params[0].toInt()
|
||||
val resourceName = unique.params[1]
|
||||
val resource = ruleSet.tileResources[resourceName] ?: return false
|
||||
if (!resource.isStockpiled()) return false
|
||||
|
||||
civInfo.resourceStockpiles.add(resourceName, amount)
|
||||
|
||||
val notificationText = getNotificationText(notification, triggerNotificationText,
|
||||
"You have lost [$amount] [$resourceName]")
|
||||
?: return true
|
||||
|
||||
civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName")
|
||||
return true
|
||||
}
|
||||
|
||||
UniqueType.OneTimeRevealEntireMap -> {
|
||||
if (notification != null) {
|
||||
civInfo.addNotification(notification, LocationAction(tile?.position), NotificationCategory.General, NotificationIcon.Scout)
|
||||
@ -366,6 +398,21 @@ object UniqueTriggerActivation {
|
||||
return promotedUnitLocations.isNotEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* The mechanics for granting great people are wonky, but basically the following happens:
|
||||
* Based on the game speed, a timer with some amount of turns is set, 40 on regular speed
|
||||
* Every turn, 1 is subtracted from this timer, as long as you have at least 1 city state ally
|
||||
* So no, the number of city-state allies does not matter for this. You have a global timer for all of them combined.
|
||||
* If the timer reaches the amount of city-state allies you have (or 10, whichever is lower), it is reset.
|
||||
* You will then receive a random great person from a random city-state you are allied to
|
||||
* The very first time after acquiring this policy, the timer is set to half of its normal value
|
||||
* This is the basics, and apart from this, there is some randomness in the exact turn count, but I don't know how much
|
||||
* There is surprisingly little information findable online about this policy, and the civ 5 source files are
|
||||
* also quite though to search through, so this might all be incorrect.
|
||||
* For now this mechanic seems decent enough that this is fine.
|
||||
* Note that the way this is implemented now, this unique does NOT stack
|
||||
* I could parametrize the [Allied], but eh.
|
||||
*/
|
||||
UniqueType.CityStateCanGiftGreatPeople -> {
|
||||
civInfo.addFlag(
|
||||
CivFlags.CityStateGreatPersonGift.name,
|
||||
@ -376,21 +423,6 @@ object UniqueTriggerActivation {
|
||||
}
|
||||
return true
|
||||
}
|
||||
// The mechanics for granting great people are wonky, but basically the following happens:
|
||||
// Based on the game speed, a timer with some amount of turns is set, 40 on regular speed
|
||||
// Every turn, 1 is subtracted from this timer, as long as you have at least 1 city state ally
|
||||
// So no, the number of city-state allies does not matter for this. You have a global timer for all of them combined.
|
||||
// If the timer reaches the amount of city-state allies you have (or 10, whichever is lower), it is reset.
|
||||
// You will then receive a random great person from a random city-state you are allied to
|
||||
// The very first time after acquiring this policy, the timer is set to half of its normal value
|
||||
// This is the basics, and apart from this, there is some randomness in the exact turn count, but I don't know how much
|
||||
|
||||
// There is surprisingly little information findable online about this policy, and the civ 5 source files are
|
||||
// also quite though to search through, so this might all be incorrect.
|
||||
// For now this mechanic seems decent enough that this is fine.
|
||||
|
||||
// Note that the way this is implemented now, this unique does NOT stack
|
||||
// I could parametrize the [Allied], but eh.
|
||||
|
||||
UniqueType.OneTimeGainStat -> {
|
||||
val stat = Stat.safeValueOf(unique.params[1]) ?: return false
|
||||
@ -414,6 +446,7 @@ object UniqueTriggerActivation {
|
||||
civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon)
|
||||
return true
|
||||
}
|
||||
|
||||
UniqueType.OneTimeGainStatRange -> {
|
||||
val stat = Stat.safeValueOf(unique.params[2]) ?: return false
|
||||
|
||||
|
@ -155,6 +155,9 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
||||
ConsumesResources("Consumes [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Building, UniqueTarget.Unit),
|
||||
ProvidesResources("Provides [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Global),
|
||||
|
||||
/** For stockpiled resources */
|
||||
CostsResources("Costs [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Building, UniqueTarget.Unit),
|
||||
|
||||
GrowthPercentBonus("[relativeAmount]% growth [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
CarryOverFood("[relativeAmount]% Food is carried over after population increases [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
|
||||
@ -579,6 +582,9 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
||||
/////// Resource uniques
|
||||
ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
|
||||
CityStateOnlyResource("Can only be created by Mercantile City-States", UniqueTarget.Resource),
|
||||
Stockpiled("Stockpiled", UniqueTarget.Resource),
|
||||
CannotBeTraded("Cannot be traded", UniqueTarget.Resource),
|
||||
NotShownOnWorldScreen("Not shown on world screen", UniqueTarget.Resource, flags = UniqueFlag.setOfHiddenToUsers),
|
||||
|
||||
ResourceWeighting("Generated with weight [amount]", UniqueTarget.Resource, flags = UniqueFlag.setOfHiddenToUsers),
|
||||
MinorDepositWeighting("Minor deposits generated with weight [amount]", UniqueTarget.Resource, flags = UniqueFlag.setOfHiddenToUsers),
|
||||
@ -724,7 +730,12 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
||||
OneTimeFreeBelief("Gain a free [beliefType] belief", UniqueTarget.Triggerable),
|
||||
OneTimeTriggerVoting("Triggers voting for the Diplomatic Victory", UniqueTarget.Triggerable), // used in Building
|
||||
|
||||
OneTimeGainStat("Gain [amount] [stat]", UniqueTarget.Triggerable),
|
||||
/** For stockpiled resources */
|
||||
OneTimeConsumeResources("Instantly consumes [amount] [resource]", UniqueTarget.Triggerable),
|
||||
/** For stockpiled resources */
|
||||
OneTimeProvideResources("Instantly provides [amount] [resource]", UniqueTarget.Triggerable),
|
||||
|
||||
OneTimeGainStat("Gain [amount] [stat/resource]", UniqueTarget.Triggerable),
|
||||
OneTimeGainStatRange("Gain [amount]-[amount] [stat]", UniqueTarget.Triggerable),
|
||||
OneTimeGainPantheon("Gain enough Faith for a Pantheon", UniqueTarget.Triggerable),
|
||||
OneTimeGainProphet("Gain enough Faith for [amount]% of a Great Prophet", UniqueTarget.Triggerable),
|
||||
|
@ -175,7 +175,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
}
|
||||
|
||||
if (!civ.isBarbarian()) { // Barbarians don't need resources
|
||||
for ((resource, requiredAmount) in getResourceRequirements()) {
|
||||
for ((resource, requiredAmount) in getResourceRequirementsPerTurn()) {
|
||||
val availableAmount = civ.getCivResourcesByName()[resource]!!
|
||||
if (availableAmount < requiredAmount) {
|
||||
result.add(
|
||||
@ -317,7 +317,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
fun movesLikeAirUnits() = type.getMovementType() == UnitMovementType.Air
|
||||
|
||||
/** Returns resource requirements from both uniques and requiredResource field */
|
||||
override fun getResourceRequirements(): HashMap<String, Int> = resourceRequirementsInternal
|
||||
override fun getResourceRequirementsPerTurn(): HashMap<String, Int> = resourceRequirementsInternal
|
||||
|
||||
private val resourceRequirementsInternal: HashMap<String, Int> by lazy {
|
||||
val resourceRequirements = HashMap<String, Int>()
|
||||
@ -327,7 +327,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
resourceRequirements
|
||||
}
|
||||
|
||||
override fun requiresResource(resource: String) = getResourceRequirements().containsKey(resource)
|
||||
override fun requiresResource(resource: String) = getResourceRequirementsPerTurn().containsKey(resource)
|
||||
|
||||
fun isRanged() = rangedStrength > 0
|
||||
fun isMelee() = !isRanged() && strength > 0
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.ui.components.extensions
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.Fonts
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Duration
|
||||
import java.time.temporal.ChronoUnit
|
||||
@ -17,7 +18,11 @@ fun Int.toPercent() = toFloat().toPercent()
|
||||
fun Float.toPercent() = 1 + this/100
|
||||
|
||||
/** Convert a [resource name][this] into "Consumes [amount] $resource" string (untranslated) */
|
||||
fun String.getConsumesAmountString(amount: Int) = "Consumes [$amount] [$this]"
|
||||
fun String.getConsumesAmountString(amount: Int, isStockpiled:Boolean): String {
|
||||
val uniqueString = "{Consumes [$amount] [$this]}"
|
||||
if (!isStockpiled) return uniqueString
|
||||
else return "$uniqueString /${Fonts.turn}"
|
||||
}
|
||||
|
||||
/** Convert a [resource name][this] into "Need [amount] more $resource" string (untranslated) */
|
||||
fun String.getNeedMoreAmountString(amount: Int) = "Need [$amount] more [$this]"
|
||||
@ -34,7 +39,7 @@ fun Duration.format(): String {
|
||||
if (firstPartAlreadyAdded) {
|
||||
sb.append(", ")
|
||||
}
|
||||
sb.append("[${part}] $unit")
|
||||
sb.append("[$part] $unit")
|
||||
firstPartAlreadyAdded = true
|
||||
}
|
||||
return sb.toString()
|
||||
|
@ -9,10 +9,10 @@ import com.unciv.models.ruleset.unit.UnitMovementType
|
||||
import com.unciv.models.ruleset.unit.UnitType
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||
import com.unciv.ui.components.Fonts
|
||||
import com.unciv.ui.components.extensions.getConsumesAmountString
|
||||
import com.unciv.ui.components.extensions.toPercent
|
||||
import com.unciv.ui.screens.civilopediascreen.FormattedLine
|
||||
import kotlin.math.pow
|
||||
|
||||
object BaseUnitDescriptions {
|
||||
@ -35,10 +35,12 @@ object BaseUnitDescriptions {
|
||||
* @param city Supplies civInfo to show available resources after resource requirements */
|
||||
fun getDescription(baseUnit: BaseUnit, city: City): String {
|
||||
val lines = mutableListOf<String>()
|
||||
val availableResources = city.civ.getCivResources().associate { it.resource.name to it.amount }
|
||||
for ((resource, amount) in baseUnit.getResourceRequirements()) {
|
||||
val available = availableResources[resource] ?: 0
|
||||
lines += "{${resource.getConsumesAmountString(amount)}} ({[$available] available})".tr()
|
||||
val availableResources = city.civ.getCivResourcesByName()
|
||||
for ((resourceName, amount) in baseUnit.getResourceRequirementsPerTurn()) {
|
||||
val available = availableResources[resourceName] ?: 0
|
||||
val resource = baseUnit.ruleset.tileResources[resourceName] ?: continue
|
||||
val consumesString = resourceName.getConsumesAmountString(amount, resource.isStockpiled())
|
||||
lines += "$consumesString ({[$available] available})".tr()
|
||||
}
|
||||
var strengthLine = ""
|
||||
if (baseUnit.strength != 0) {
|
||||
@ -90,7 +92,7 @@ object BaseUnitDescriptions {
|
||||
val buyCost = (30.0 * baseUnit.cost.toFloat().pow(0.75f) * baseUnit.hurryCostModifier.toPercent()).toInt() / 10 * 10
|
||||
stats += "$buyCost${Fonts.gold}"
|
||||
}
|
||||
textList += FormattedLine(stats.joinToString(", ", "{Cost}: "))
|
||||
textList += FormattedLine(stats.joinToString("/", "{Cost}: "))
|
||||
}
|
||||
|
||||
if (baseUnit.replacementTextForUniques.isNotEmpty()) {
|
||||
@ -105,12 +107,13 @@ object BaseUnitDescriptions {
|
||||
}
|
||||
}
|
||||
|
||||
val resourceRequirements = baseUnit.getResourceRequirements()
|
||||
val resourceRequirements = baseUnit.getResourceRequirementsPerTurn()
|
||||
if (resourceRequirements.isNotEmpty()) {
|
||||
textList += FormattedLine()
|
||||
for ((resource, amount) in resourceRequirements) {
|
||||
for ((resourceName, amount) in resourceRequirements) {
|
||||
val resource = ruleset.tileResources[resourceName] ?: continue
|
||||
textList += FormattedLine(
|
||||
resource.getConsumesAmountString(amount),
|
||||
resourceName.getConsumesAmountString(amount, resource.isStockpiled()),
|
||||
link = "Resource/$resource", color = "#F42"
|
||||
)
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
|
||||
val useStoredProduction = entry is Building || !cityConstructions.isBeingConstructedOrEnqueued(entry.name)
|
||||
val buttonText = cityConstructions.getTurnsToConstructionString(entry.name, useStoredProduction).trim()
|
||||
val resourcesRequired = entry.getResourceRequirements()
|
||||
val resourcesRequired = entry.getResourceRequirementsPerTurn()
|
||||
val mostImportantRejection =
|
||||
entry.getRejectionReasons(cityConstructions)
|
||||
.filter { it.isImportantRejection() }
|
||||
@ -317,9 +317,11 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
if (constructionName in PerpetualConstruction.perpetualConstructionsMap) "\n∞"
|
||||
else cityConstructions.getTurnsToConstructionString(constructionName, isFirstConstructionOfItsKind)
|
||||
|
||||
val constructionResource = cityConstructions.getConstruction(constructionName).getResourceRequirements()
|
||||
for ((resource, amount) in constructionResource)
|
||||
text += "\n" + resource.getConsumesAmountString(amount).tr()
|
||||
val constructionResource = cityConstructions.getConstruction(constructionName).getResourceRequirementsPerTurn()
|
||||
for ((resourceName, amount) in constructionResource) {
|
||||
val resource = cityConstructions.city.getRuleset().tileResources[resourceName] ?: continue
|
||||
text += "\n" + resourceName.getConsumesAmountString(amount, resource.isStockpiled()).tr()
|
||||
}
|
||||
|
||||
table.defaults().pad(2f).minWidth(40f)
|
||||
if (isFirstConstructionOfItsKind) table.add(getProgressBar(constructionName)).minWidth(5f)
|
||||
@ -396,6 +398,12 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
resourceTable.add(ImageGetter.getResourcePortrait(resource, 15f)).padBottom(1f)
|
||||
}
|
||||
}
|
||||
for (unique in constructionButtonDTO.construction.getMatchingUniquesNotConflicting(UniqueType.CostsResources)){
|
||||
val color = if (constructionButtonDTO.rejectionReason?.type == RejectionReasonType.ConsumesResources)
|
||||
Color.RED else Color.WHITE
|
||||
resourceTable.add(unique.params[0].toLabel(fontColor = color)).expandX().left().padLeft(5f)
|
||||
resourceTable.add(ImageGetter.getResourcePortrait(unique.params[1], 15f)).padBottom(1f)
|
||||
}
|
||||
constructionTable.add(resourceTable).expandX().left()
|
||||
|
||||
pickConstructionButton.add(constructionTable).expandX().left()
|
||||
|
@ -11,10 +11,6 @@ import com.unciv.models.ruleset.tile.ResourceSupplyList
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.TileResource
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaCategories
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.addSeparatorVertical
|
||||
@ -22,6 +18,10 @@ import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.pad
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaCategories
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
|
||||
|
||||
class ResourcesOverviewTab(
|
||||
@ -73,10 +73,16 @@ class ResourcesOverviewTab(
|
||||
private val extraOrigins: List<ExtraInfoOrigin> = extraDrilldown.asSequence()
|
||||
.mapNotNull { ExtraInfoOrigin.safeValueOf(it.origin) }.distinct().toList()
|
||||
|
||||
private fun ResourceSupplyList.getLabel(resource: TileResource, origin: String): Label? =
|
||||
get(resource, origin)?.amount?.toLabel()
|
||||
private fun ResourceSupplyList.getTotalLabel(resource: TileResource): Label =
|
||||
filter { it.resource == resource }.sumOf { it.amount }.toLabel()
|
||||
private fun ResourceSupplyList.getLabel(resource: TileResource, origin: String): Label? {
|
||||
val amount = get(resource, origin)?.amount ?: return null
|
||||
return if (resource.isStockpiled() && amount > 0) "+$amount".toLabel()
|
||||
else amount.toLabel()
|
||||
}
|
||||
private fun ResourceSupplyList.getTotalLabel(resource: TileResource): Label {
|
||||
val total = filter { it.resource == resource }.sumOf { it.amount }
|
||||
return if (resource.isStockpiled() && total > 0) "+$total".toLabel()
|
||||
else total.toLabel()
|
||||
}
|
||||
private fun getResourceImage(name: String) =
|
||||
ImageGetter.getResourcePortrait(name, iconSize).apply {
|
||||
onClick {
|
||||
|
@ -12,6 +12,7 @@ import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.TileResource
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.Fonts
|
||||
@ -24,6 +25,7 @@ import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.setFontColor
|
||||
import com.unciv.ui.components.extensions.setFontSize
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toStringSigned
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.popups
|
||||
@ -347,14 +349,23 @@ class WorldScreenTopBar(val worldScreen: WorldScreen) : Table() {
|
||||
turnsLabel.setText(Fonts.turn + "" + civInfo.gameInfo.turns + " | " + yearText)
|
||||
resourcesWrapper.clearChildren()
|
||||
var firstPadLeft = 20f // We want a distance from the turns entry to the first resource, but only if any resource is displayed
|
||||
val civResources = civInfo.getCivResources()
|
||||
val civResources = civInfo.getCivResourcesByName()
|
||||
val civResourceSupply = civInfo.getCivResourceSupply()
|
||||
for ((resource, label, icon) in resourceActors) {
|
||||
if (resource.revealedBy != null && !civInfo.tech.isResearched(resource.revealedBy!!))
|
||||
continue
|
||||
if (resource.hasUnique(UniqueType.NotShownOnWorldScreen)) continue
|
||||
|
||||
resourcesWrapper.add(icon).padLeft(firstPadLeft).padRight(0f)
|
||||
firstPadLeft = 5f
|
||||
val amount = civResources.get(resource, "All")?.amount ?: 0
|
||||
label.setText(amount)
|
||||
val amount = civResources[resource.name] ?: 0
|
||||
if (!resource.isStockpiled())
|
||||
label.setText(amount)
|
||||
else {
|
||||
val perTurn = civResourceSupply.firstOrNull { it.resource == resource }?.amount ?: 0
|
||||
if (perTurn == 0) label.setText(amount)
|
||||
else label.setText("$amount (${perTurn.toStringSigned()})")
|
||||
}
|
||||
resourcesWrapper.add(label).padTop(8f) // digits don't have descenders, so push them down a little
|
||||
}
|
||||
|
||||
|
@ -336,9 +336,9 @@ object UnitActions {
|
||||
// Check _new_ resource requirements
|
||||
// Using Counter to aggregate is a bit exaggerated, but - respect the mad modder.
|
||||
val resourceRequirementsDelta = Counter<String>()
|
||||
for ((resource, amount) in unit.baseUnit().getResourceRequirements())
|
||||
for ((resource, amount) in unit.baseUnit().getResourceRequirementsPerTurn())
|
||||
resourceRequirementsDelta.add(resource, -amount)
|
||||
for ((resource, amount) in upgradedUnit.getResourceRequirements())
|
||||
for ((resource, amount) in upgradedUnit.getResourceRequirementsPerTurn())
|
||||
resourceRequirementsDelta.add(resource, amount)
|
||||
val newResourceRequirementsString = resourceRequirementsDelta.entries
|
||||
.filter { it.value > 0 }
|
||||
|
@ -42,9 +42,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<String>()
|
||||
for ((resource, amount) in unit.baseUnit().getResourceRequirements())
|
||||
for ((resource, amount) in unit.baseUnit().getResourceRequirementsPerTurn())
|
||||
resourceRequirementsDelta.add(resource, -amount)
|
||||
for ((resource, amount) in upgradedUnit.getResourceRequirements())
|
||||
for ((resource, amount) in upgradedUnit.getResourceRequirementsPerTurn())
|
||||
resourceRequirementsDelta.add(resource, amount)
|
||||
val newResourceRequirementsString = resourceRequirementsDelta.entries
|
||||
.filter { it.value > 0 }
|
||||
|
@ -74,7 +74,17 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
??? example "Triggers voting for the Diplomatic Victory"
|
||||
Applicable to: Triggerable
|
||||
|
||||
??? example "Gain [amount] [stat]"
|
||||
??? example "Instantly consumes [amount] [resource]"
|
||||
Example: "Instantly consumes [3] [Iron]"
|
||||
|
||||
Applicable to: Triggerable
|
||||
|
||||
??? example "Instantly provides [amount] [resource]"
|
||||
Example: "Instantly provides [3] [Iron]"
|
||||
|
||||
Applicable to: Triggerable
|
||||
|
||||
??? example "Gain [amount] [stat/resource]"
|
||||
Example: "Gain [3] [Culture]"
|
||||
|
||||
Applicable to: Triggerable
|
||||
@ -914,6 +924,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
|
||||
Applicable to: Building, Unit, Improvement
|
||||
|
||||
??? example "Costs [amount] [resource]"
|
||||
Example: "Costs [3] [Iron]"
|
||||
|
||||
Applicable to: Building, Unit, Improvement
|
||||
|
||||
??? example "Unbuildable"
|
||||
Applicable to: Building, Unit, Improvement
|
||||
|
||||
@ -1601,6 +1616,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
??? example "Can only be created by Mercantile City-States"
|
||||
Applicable to: Resource
|
||||
|
||||
??? example "Stockpiled"
|
||||
Applicable to: Resource
|
||||
|
||||
??? example "Not shown on world screen"
|
||||
Applicable to: Resource
|
||||
|
||||
??? example "Generated with weight [amount]"
|
||||
Example: "Generated with weight [3]"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user