AI rationing of strategic resources; Hydro Plant re-enabled (#5401)

* AI evaluation of resources

* optimizations

* sell or disband when needed for space victory

* use for all constructions

* use in trade evaluations

* .requiresResource()
This commit is contained in:
SimonCeder
2021-10-06 16:11:02 +02:00
committed by GitHub
parent e4ff3d43d6
commit c00ce49c86
11 changed files with 191 additions and 19 deletions

View File

@ -913,7 +913,6 @@
"requiredBuilding": "Bank", "requiredBuilding": "Bank",
"requiredTech": "Electricity" "requiredTech": "Electricity"
}, },
/* This works and even has icon but AI cannot manage its Aluminum at this moment
{ {
"name": "Hydro Plant", "name": "Hydro Plant",
"requiredResource": "Aluminum", "requiredResource": "Aluminum",
@ -922,7 +921,6 @@
"uniques": ["Must be on [River]","[+1 Production] from [River] tiles [in this city]"], "uniques": ["Must be on [River]","[+1 Production] from [River] tiles [in this city]"],
"requiredTech": "Electricity" "requiredTech": "Electricity"
}, },
*/
// Modern Era // Modern Era

View File

@ -77,6 +77,9 @@ class GameInfo {
@Transient @Transient
var simulateUntilWin = false var simulateUntilWin = false
@Transient
var spaceResources = HashSet<String>()
//endregion //endregion
//region Pure functions //region Pure functions
@ -312,6 +315,9 @@ class GameInfo {
} }
} }
spaceResources.addAll(ruleSet.buildings.values.filter { it.hasUnique("Spaceship part") }
.flatMap { it.getResourceRequirements().keys } )
barbarians.setTransients(this) barbarians.setTransients(this)
} }

View File

@ -1,9 +1,11 @@
package com.unciv.logic.automation package com.unciv.logic.automation
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.BFS import com.unciv.logic.map.BFS
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.VictoryType import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
@ -60,14 +62,11 @@ object Automation {
fun chooseMilitaryUnit(city: CityInfo): String? { fun chooseMilitaryUnit(city: CityInfo): String? {
var militaryUnits = var militaryUnits =
city.cityConstructions.getConstructableUnits().filter { !it.isCivilian() } city.cityConstructions.getConstructableUnits().filter { !it.isCivilian() }
.filter { allowSpendingResource(city.civInfo, it) }
if (militaryUnits.map { it.name } if (militaryUnits.map { it.name }
.contains(city.cityConstructions.currentConstructionFromQueue)) .contains(city.cityConstructions.currentConstructionFromQueue))
return city.cityConstructions.currentConstructionFromQueue return city.cityConstructions.currentConstructionFromQueue
// This is so that the AI doesn't use all its aluminum on units and have none left for spaceship parts
val aluminum = city.civInfo.getCivResourcesByName()["Aluminum"]
if (aluminum != null && aluminum < 2) // mods may have no aluminum
militaryUnits.filter { !it.getResourceRequirements().containsKey("Aluminum") }
val findWaterConnectedCitiesAndEnemies = val findWaterConnectedCitiesAndEnemies =
BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() } BFS(city.getCenterTile()) { it.isWater || it.isCityCenter() }
@ -100,6 +99,80 @@ object Automation {
return chosenUnit.name return chosenUnit.name
} }
/** Determines whether the AI should be willing to spend strategic resources to build
* [construction] in [city], assumes that we are actually able to do so. */
fun allowSpendingResource(civInfo: CivilizationInfo, construction: INonPerpetualConstruction): Boolean {
// City states do whatever they want
if (civInfo.isCityState())
return true
// Spaceships are always allowed
if (construction.hasUnique("Spaceship part"))
return true
val requiredResources = construction.getResourceRequirements()
// Does it even require any resources?
if (requiredResources.isEmpty())
return true
val civResources = civInfo.getCivResourcesByName()
// Rule of thumb: reserve 2-3 for spaceship, then reserve half each for buildings and units
// Assume that no buildings provide any resources
for ((resource, amount) in requiredResources) {
// Also count things under construction
var futureForUnits = 0
var futureForBuildings = 0
for (city in civInfo.cities) {
val otherConstruction = city.cityConstructions.getCurrentConstruction()
if (otherConstruction is Building)
futureForBuildings += otherConstruction.getResourceRequirements()[resource] ?: 0
else
futureForUnits += otherConstruction.getResourceRequirements()[resource] ?: 0
}
// Make sure we have some for space
if (resource in civInfo.gameInfo.spaceResources && civResources[resource]!! - amount - futureForBuildings - futureForUnits
< getReservedSpaceResourceAmount(civInfo)) {
return false
}
// Assume buildings remain useful
val neededForBuilding = civInfo.lastEraResourceUsedForBuilding[resource] != null
// Don't care about old units
val neededForUnits = civInfo.lastEraResourceUsedForUnit[resource] != null
&& civInfo.lastEraResourceUsedForUnit[resource]!! >= civInfo.getEraNumber()
// No need to save for both
if (!neededForBuilding || !neededForUnits) {
continue
}
val usedForUnits = civInfo.detailedCivResources.filter { it.resource.name == resource && it.origin == "Units" }.sumOf { -it.amount }
val usedForBuildings = civInfo.detailedCivResources.filter { it.resource.name == resource && it.origin == "Buildings" }.sumOf { -it.amount }
if (construction is Building) {
// Will more than half the total resources be used for buildings after this construction?
if (civResources[resource]!! + usedForUnits < usedForBuildings + amount + futureForBuildings) {
return false
}
} else {
// Will more than half the total resources be used for units after this construction?
if (civResources[resource]!! + usedForBuildings < usedForUnits + amount + futureForUnits) {
return false
}
}
}
return true
}
fun getReservedSpaceResourceAmount(civInfo: CivilizationInfo): Int {
return if (civInfo.nation.preferredVictoryType == VictoryType.Scientific) 3 else 2
}
fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel { fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel {
val powerLevelComparison = val powerLevelComparison =
assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat() assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat()

View File

@ -115,7 +115,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addWorkBoatChoice() { private fun addWorkBoatChoice() {
val buildableWorkboatUnits = cityInfo.cityConstructions.getConstructableUnits() val buildableWorkboatUnits = cityInfo.cityConstructions.getConstructableUnits()
.filter { it.uniques.contains(Constants.workBoatsUnique) } .filter { it.uniques.contains(Constants.workBoatsUnique)
&& Automation.allowSpendingResource(civInfo, it) }
val canBuildWorkboat = buildableWorkboatUnits.any() val canBuildWorkboat = buildableWorkboatUnits.any()
&& !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(Constants.workBoatsUnique) == true } && !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(Constants.workBoatsUnique) == true }
if (!canBuildWorkboat) return if (!canBuildWorkboat) return
@ -140,7 +141,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
val workerEquivalents = civInfo.gameInfo.ruleSet.units.values val workerEquivalents = civInfo.gameInfo.ruleSet.units.values
.filter { it.uniques.any { .filter { it.uniques.any {
unique -> unique.equalsPlaceholderText(Constants.canBuildImprovements) unique -> unique.equalsPlaceholderText(Constants.canBuildImprovements)
} && it.isBuildable(cityConstructions) } } && it.isBuildable(cityConstructions)
&& Automation.allowSpendingResource(civInfo, it) }
if (workerEquivalents.isEmpty()) return // for mods with no worker units if (workerEquivalents.isEmpty()) return // for mods with no worker units
if (civInfo.getIdleUnits().any { it.isAutomated() && it.hasUniqueToBuildImprovements }) if (civInfo.getIdleUnits().any { it.isAutomated() && it.hasUniqueToBuildImprovements })
return // If we have automated workers who have no work to do then it's silly to construct new workers. return // If we have automated workers who have no work to do then it's silly to construct new workers.
@ -155,7 +157,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addCultureBuildingChoice() { private fun addCultureBuildingChoice() {
val cultureBuilding = buildableNotWonders val cultureBuilding = buildableNotWonders
.filter { it.isStatRelated(Stat.Culture) }.minByOrNull { it.cost } .filter { it.isStatRelated(Stat.Culture)
&& Automation.allowSpendingResource(civInfo, it) }.minByOrNull { it.cost }
if (cultureBuilding != null) { if (cultureBuilding != null) {
var modifier = 0.5f var modifier = 0.5f
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
@ -175,7 +178,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
} }
private fun addOtherBuildingChoice() { private fun addOtherBuildingChoice() {
val otherBuilding = buildableNotWonders.minByOrNull { it.cost } val otherBuilding = buildableNotWonders
.filter { Automation.allowSpendingResource(civInfo, it) }.minByOrNull { it.cost }
if (otherBuilding != null) { if (otherBuilding != null) {
val modifier = 0.6f val modifier = 0.6f
addChoice(relativeCostEffectiveness, otherBuilding.name, modifier) addChoice(relativeCostEffectiveness, otherBuilding.name, modifier)
@ -211,6 +215,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
if (!buildableWonders.any()) return if (!buildableWonders.any()) return
val highestPriorityWonder = buildableWonders val highestPriorityWonder = buildableWonders
.filter { Automation.allowSpendingResource(civInfo, it) }
.maxByOrNull { getWonderPriority(it) }!! .maxByOrNull { getWonderPriority(it) }!!
val citiesBuildingWonders = civInfo.cities val citiesBuildingWonders = civInfo.cities
.count { it.cityConstructions.isBuildingWonder() } .count { it.cityConstructions.isBuildingWonder() }
@ -222,7 +227,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addUnitTrainingBuildingChoice() { private fun addUnitTrainingBuildingChoice() {
val unitTrainingBuilding = buildableNotWonders.asSequence() val unitTrainingBuilding = buildableNotWonders.asSequence()
.filter { it.hasUnique("New [] units start with [] Experience []") }.minByOrNull { it.cost } .filter { it.hasUnique("New [] units start with [] Experience []")
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost }
if (unitTrainingBuilding != null && (preferredVictoryType != VictoryType.Cultural || isAtWar)) { if (unitTrainingBuilding != null && (preferredVictoryType != VictoryType.Cultural || isAtWar)) {
var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
if (isAtWar) modifier *= 2 if (isAtWar) modifier *= 2
@ -234,7 +240,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addDefenceBuildingChoice() { private fun addDefenceBuildingChoice() {
val defensiveBuilding = buildableNotWonders.asSequence() val defensiveBuilding = buildableNotWonders.asSequence()
.filter { it.cityStrength > 0 }.minByOrNull { it.cost } .filter { it.cityStrength > 0
&& Automation.allowSpendingResource(civInfo, it)}.minByOrNull { it.cost }
if (defensiveBuilding != null && (isAtWar || preferredVictoryType != VictoryType.Cultural)) { if (defensiveBuilding != null && (isAtWar || preferredVictoryType != VictoryType.Cultural)) {
var modifier = 0.2f var modifier = 0.2f
if (isAtWar) modifier = 0.5f if (isAtWar) modifier = 0.5f
@ -250,8 +257,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addHappinessBuildingChoice() { private fun addHappinessBuildingChoice() {
val happinessBuilding = buildableNotWonders.asSequence() val happinessBuilding = buildableNotWonders.asSequence()
.filter { it.isStatRelated(Stat.Happiness) .filter { (it.isStatRelated(Stat.Happiness)
|| it.uniques.contains("Remove extra unhappiness from annexed cities") } || it.uniques.contains("Remove extra unhappiness from annexed cities"))
&& Automation.allowSpendingResource(civInfo, it)}
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (happinessBuilding != null) { if (happinessBuilding != null) {
var modifier = 1f var modifier = 1f
@ -265,7 +273,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addScienceBuildingChoice() { private fun addScienceBuildingChoice() {
if (allTechsAreResearched) return if (allTechsAreResearched) return
val scienceBuilding = buildableNotWonders.asSequence() val scienceBuilding = buildableNotWonders.asSequence()
.filter { it.isStatRelated(Stat.Science) } .filter { it.isStatRelated(Stat.Science)
&& Automation.allowSpendingResource(civInfo, it)}
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (scienceBuilding != null) { if (scienceBuilding != null) {
var modifier = 1.1f var modifier = 1.1f
@ -276,7 +285,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
} }
private fun addGoldBuildingChoice() { private fun addGoldBuildingChoice() {
val goldBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Gold) } val goldBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Gold)
&& Automation.allowSpendingResource(civInfo, it)}
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (goldBuilding != null) { if (goldBuilding != null) {
val modifier = if (civInfo.statsForNextTurn.gold < 0) 3f else 1.2f val modifier = if (civInfo.statsForNextTurn.gold < 0) 3f else 1.2f
@ -286,7 +296,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addProductionBuildingChoice() { private fun addProductionBuildingChoice() {
val productionBuilding = buildableNotWonders.asSequence() val productionBuilding = buildableNotWonders.asSequence()
.filter { it.isStatRelated(Stat.Production) } .filter { it.isStatRelated(Stat.Production) && Automation.allowSpendingResource(civInfo, it) }
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (productionBuilding != null) { if (productionBuilding != null) {
addChoice(relativeCostEffectiveness, productionBuilding.name, 1.5f) addChoice(relativeCostEffectiveness, productionBuilding.name, 1.5f)
@ -294,8 +304,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
} }
private fun addFoodBuildingChoice() { private fun addFoodBuildingChoice() {
val foodBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Food) val foodBuilding = buildableNotWonders.asSequence().filter { (it.isStatRelated(Stat.Food)
|| it.uniqueObjects.any { it.placeholderText=="[]% of food is carried over after population increases" }} || it.uniqueObjects.any { it.placeholderText=="[]% of food is carried over after population increases" })
&& Automation.allowSpendingResource(civInfo, it) }
.minByOrNull { it.cost } .minByOrNull { it.cost }
if (foodBuilding != null) { if (foodBuilding != null) {
var modifier = 1f var modifier = 1f

View File

@ -46,6 +46,7 @@ object NextTurnAutomation {
exchangeLuxuries(civInfo) exchangeLuxuries(civInfo)
issueRequests(civInfo) issueRequests(civInfo)
adoptPolicy(civInfo) // todo can take a second - why? adoptPolicy(civInfo) // todo can take a second - why?
freeUpSpaceResources(civInfo)
} else { } else {
civInfo.getFreeTechForCityState() civInfo.getFreeTechForCityState()
civInfo.updateDiplomaticRelationshipForCityState() civInfo.updateDiplomaticRelationshipForCityState()
@ -300,6 +301,39 @@ object NextTurnAutomation {
} }
} }
/** If we are able to build a spaceship but have already spent our resources, try disbanding
* a unit and selling a building to make room. Can happen due to trades etc */
private fun freeUpSpaceResources(civInfo: CivilizationInfo) {
// Can't build spaceships
if (!civInfo.hasUnique("Enables construction of Spaceship parts"))
return
for (resource in civInfo.gameInfo.spaceResources) {
// Have enough resources already
if (civInfo.getCivResourcesByName()[resource]!! >= Automation.getReservedSpaceResourceAmount(civInfo))
continue
val unitToDisband = civInfo.getCivUnits()
.filter { it.baseUnit.requiresResource(resource) }
.minByOrNull { it.getForceEvaluation() }
if (unitToDisband != null) {
unitToDisband.disband()
}
for (city in civInfo.cities) {
if (city.hasSoldBuildingThisTurn)
continue
val buildingToSell = civInfo.gameInfo.ruleSet.buildings.values.filter {
it.name in city.cityConstructions.builtBuildings
&& it.requiresResource(resource) }.randomOrNull()
if (buildingToSell != null) {
city.sellBuilding(buildingToSell.name)
break
}
}
}
}
private fun chooseReligiousBeliefs(civInfo: CivilizationInfo) { private fun chooseReligiousBeliefs(civInfo: CivilizationInfo) {
choosePantheon(civInfo) choosePantheon(civInfo)
foundReligion(civInfo) foundReligion(civInfo)

View File

@ -78,6 +78,11 @@ object UnitAutomation {
val upgradedUnit = unit.getUnitToUpgradeTo() val upgradedUnit = unit.getUnitToUpgradeTo()
if (!upgradedUnit.isBuildable(unit.civInfo)) return false // for resource reasons, usually if (!upgradedUnit.isBuildable(unit.civInfo)) return false // for resource reasons, usually
if (upgradedUnit.getResourceRequirements().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.civInfo, upgradedUnit)) return false
}
val upgradeAction = UnitActions.getUpgradeAction(unit) val upgradeAction = UnitActions.getUpgradeAction(unit)
?: return false ?: return false

View File

@ -13,6 +13,7 @@ interface IConstruction : INamed {
fun isBuildable(cityConstructions: CityConstructions): Boolean fun isBuildable(cityConstructions: CityConstructions): Boolean
fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean fun shouldBeDisplayed(cityConstructions: CityConstructions): Boolean
fun getResourceRequirements(): HashMap<String,Int> fun getResourceRequirements(): HashMap<String,Int>
fun requiresResource(resource: String): Boolean
} }
interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
@ -208,4 +209,6 @@ open class PerpetualConstruction(override var name: String, val description: Str
override fun getResourceRequirements(): HashMap<String, Int> = hashMapOf() override fun getResourceRequirements(): HashMap<String, Int> = hashMapOf()
override fun requiresResource(resource: String) = false
} }

View File

@ -106,6 +106,12 @@ class CivilizationInfo {
@Transient @Transient
var nonStandardTerrainDamage = false var nonStandardTerrainDamage = false
@Transient
var lastEraResourceUsedForBuilding = HashMap<String, Int>()
@Transient
val lastEraResourceUsedForUnit = HashMap<String, Int>()
var playerType = PlayerType.AI var playerType = PlayerType.AI
/** Used in online multiplayer for human players */ /** Used in online multiplayer for human players */
@ -681,6 +687,20 @@ class CivilizationInfo {
// Cache whether this civ gets nonstandard terrain damage for performance reasons. // Cache whether this civ gets nonstandard terrain damage for performance reasons.
nonStandardTerrainDamage = getMatchingUniques("Units ending their turn on [] tiles take [] damage") nonStandardTerrainDamage = getMatchingUniques("Units ending their turn on [] tiles take [] damage")
.any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() } .any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() }
// Cache the last era each resource is used for buildings or units respectively for AI building evaluation
for (resource in gameInfo.ruleSet.tileResources.values.filter { it.resourceType == ResourceType.Strategic }.map { it.name }) {
val applicableBuildings = gameInfo.ruleSet.buildings.values.filter { getEquivalentBuilding(it) == it && it.requiresResource(resource) }
val applicableUnits = gameInfo.ruleSet.units.values.filter { getEquivalentUnit(it) == it && it.requiresResource(resource) }
val lastEraForBuilding = applicableBuildings.map { gameInfo.ruleSet.eras[gameInfo.ruleSet.technologies[it.requiredTech]?.era()]?.eraNumber ?: 0 }.maxOrNull()
val lastEraForUnit = applicableUnits.map { gameInfo.ruleSet.eras[gameInfo.ruleSet.technologies[it.requiredTech]?.era()]?.eraNumber ?: 0 }.maxOrNull()
if (lastEraForBuilding != null)
lastEraResourceUsedForBuilding[resource] = lastEraForBuilding
if (lastEraForUnit != null)
lastEraResourceUsedForUnit[resource] = lastEraForUnit
}
} }
fun updateSightAndResources() { fun updateSightAndResources() {

View File

@ -191,6 +191,11 @@ class TradeEvaluation {
else 500 // you want to take away our last lux of this type?! else 500 // you want to take away our last lux of this type?!
} }
TradeType.Strategic_Resource -> { TradeType.Strategic_Resource -> {
if (civInfo.gameInfo.spaceResources.contains(offer.name) &&
(civInfo.hasUnique("Enables construction of Spaceship parts") ||
tradePartner.hasUnique("Enables construction of Spaceship parts")))
return 10000 // We'd rather win the game, thanks
if (!civInfo.isAtWar()) return 50 * offer.amount if (!civInfo.isAtWar()) return 50 * offer.amount
val canUseForUnits = civInfo.gameInfo.ruleSet.units.values val canUseForUnits = civInfo.gameInfo.ruleSet.units.values

View File

@ -690,6 +690,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
if (get(stat) > 0) return true if (get(stat) > 0) return true
if (getStatPercentageBonuses(null)[stat] > 0) return true if (getStatPercentageBonuses(null)[stat] > 0) return true
if (uniqueObjects.any { it.placeholderText == "[] per [] population []" && it.stats[stat] > 0 }) return true if (uniqueObjects.any { it.placeholderText == "[] per [] population []" && it.stats[stat] > 0 }) return true
if (uniqueObjects.any { it.placeholderText == "[] from [] tiles []" && it.stats[stat] > 0 }) return true
return false return false
} }
@ -710,4 +711,12 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
resourceRequirements[unique.params[1]] = unique.params[0].toInt() resourceRequirements[unique.params[1]] = unique.params[0].toInt()
return resourceRequirements return resourceRequirements
} }
override fun requiresResource(resource: String): Boolean {
if (requiredResource == resource) return true
for (unique in getMatchingUniques(UniqueType.ConsumesResources)) {
if (unique.params[1] == resource) return true
}
return false
}
} }

View File

@ -540,6 +540,14 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
return resourceRequirements return resourceRequirements
} }
override fun requiresResource(resource: String): Boolean {
if (requiredResource == resource) return true
for (unique in getMatchingUniques(UniqueType.ConsumesResources)) {
if (unique.params[1] == resource) return true
}
return false
}
fun isRanged() = rangedStrength > 0 fun isRanged() = rangedStrength > 0
fun isMelee() = !isRanged() && strength > 0 fun isMelee() = !isRanged() && strength > 0
fun isMilitary() = isRanged() || isMelee() fun isMilitary() = isRanged() || isMelee()