mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 18:28:42 +07:00
Construction automation optimization (#7504)
This commit is contained in:

committed by
GitHub

parent
0e0e245365
commit
772e7faf54
@ -137,7 +137,7 @@ object Automation {
|
||||
val currentChoice = city.cityConstructions.getCurrentConstruction()
|
||||
if (currentChoice is BaseUnit && !currentChoice.isCivilian()) return city.cityConstructions.currentConstructionFromQueue
|
||||
|
||||
var militaryUnits = city.getRuleset().units.values.asSequence()
|
||||
var militaryUnits = city.getRuleset().units.values
|
||||
.filter { !it.isCivilian() }
|
||||
.filter { allowSpendingResource(city.civInfo, it) }
|
||||
|
||||
@ -154,14 +154,14 @@ object Automation {
|
||||
val carryingOnlyUnits = militaryUnits.filter {
|
||||
it.hasUnique(UniqueType.CarryAirUnits)
|
||||
&& it.hasUnique(UniqueType.CannotAttack)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
for (unit in carryingOnlyUnits)
|
||||
if (providesUnneededCarryingSlots(unit, city.civInfo))
|
||||
militaryUnits = militaryUnits.filterNot { it == unit }
|
||||
|
||||
// Only now do we filter out the constructable units because that's a heavier check
|
||||
militaryUnits = militaryUnits.filter { it.isBuildable(city.cityConstructions) }.toList().asSequence() // gather once because we have a .any afterwards
|
||||
militaryUnits = militaryUnits.filter { it.isBuildable(city.cityConstructions) } // gather once because we have a .any afterwards
|
||||
|
||||
val chosenUnit: BaseUnit
|
||||
if (!city.civInfo.isAtWar()
|
||||
@ -183,7 +183,7 @@ object Automation {
|
||||
}
|
||||
// Check the maximum force evaluation for the shortlist so we can prune useless ones (ie scouts)
|
||||
val bestForce = bestUnitsForType.maxOf { it.getForceEvaluation() }
|
||||
chosenUnit = bestUnitsForType.filter { it.uniqueTo != null || it.getForceEvaluation() > bestForce / 3 }.toList().random()
|
||||
chosenUnit = bestUnitsForType.filter { it.uniqueTo != null || it.getForceEvaluation() > bestForce / 3 }.random()
|
||||
}
|
||||
return chosenUnit.name
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
package com.unciv.logic.automation
|
||||
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.city.CityConstructions
|
||||
import com.unciv.logic.city.INonPerpetualConstruction
|
||||
import com.unciv.logic.city.PerpetualConstruction
|
||||
import com.unciv.logic.civilization.CityAction
|
||||
import com.unciv.logic.civilization.NotificationIcon
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.logic.map.BFS
|
||||
import com.unciv.models.ruleset.Building
|
||||
import com.unciv.models.ruleset.MilestoneType
|
||||
@ -22,13 +21,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
private val cityInfo = cityConstructions.cityInfo
|
||||
private val civInfo = cityInfo.civInfo
|
||||
|
||||
private val buildableBuildings = cityConstructions.getBuildableBuildings().toList()
|
||||
private val buildableNotWonders = buildableBuildings
|
||||
.filterNot { it.isAnyWonder() }
|
||||
private val buildableWonders = buildableBuildings
|
||||
.filter { it.isAnyWonder() }
|
||||
private val buildings = cityInfo.getRuleset().buildings.values
|
||||
private val nonWonders = buildings.filterNot { it.isAnyWonder() }
|
||||
private val wonders = buildings.filter { it.isAnyWonder() }
|
||||
|
||||
private val buildableUnits = cityConstructions.getConstructableUnits()
|
||||
private val units = cityInfo.getRuleset().units.values
|
||||
|
||||
private val civUnits = civInfo.getCivUnits()
|
||||
private val militaryUnits = civUnits.count { it.baseUnit.isMilitary() }
|
||||
@ -58,6 +55,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
choices.add(ConstructionChoice(choice, choiceModifier, cityConstructions.getRemainingWork(choice)))
|
||||
}
|
||||
|
||||
private fun Collection<INonPerpetualConstruction>.isBuildable(): Collection<INonPerpetualConstruction> {
|
||||
return this.filter { it.isBuildable(cityConstructions) }
|
||||
}
|
||||
|
||||
|
||||
fun chooseNextConstruction() {
|
||||
if (cityConstructions.getCurrentConstruction() !is PerpetualConstruction) return // don't want to be stuck on these forever
|
||||
@ -140,11 +141,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addWorkBoatChoice() {
|
||||
val buildableWorkboatUnits = buildableUnits
|
||||
val buildableWorkboatUnits = units
|
||||
.filter {
|
||||
it.hasUnique(UniqueType.CreateWaterImprovements)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||
}
|
||||
}.isBuildable()
|
||||
val alreadyHasWorkBoat = buildableWorkboatUnits.any()
|
||||
&& !cityInfo.getTiles().any {
|
||||
it.civilianUnit?.hasUnique(UniqueType.CreateWaterImprovements) == true
|
||||
@ -172,11 +173,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addWorkerChoice() {
|
||||
val workerEquivalents = buildableUnits
|
||||
val workerEquivalents = units
|
||||
.filter {
|
||||
it.hasUnique(UniqueType.BuildImprovements)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||
}
|
||||
}.isBuildable()
|
||||
if (workerEquivalents.none()) return // for mods with no worker units
|
||||
|
||||
if (workers < cities) {
|
||||
@ -187,9 +188,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addCultureBuildingChoice() {
|
||||
val cultureBuilding = buildableNotWonders
|
||||
val cultureBuilding = nonWonders
|
||||
.filter { it.isStatRelated(Stat.Culture)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (cultureBuilding != null) {
|
||||
var modifier = 0.5f
|
||||
if (cityInfo.cityStats.currentCityStats.culture == 0f) // It won't grow if we don't help it
|
||||
@ -200,16 +202,17 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addSpaceshipPartChoice() {
|
||||
val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.name in spaceshipParts }
|
||||
if (spaceshipPart != null) {
|
||||
val spaceshipPart = (nonWonders + units).filter { it.name in spaceshipParts }.isBuildable()
|
||||
if (spaceshipPart.isNotEmpty()) {
|
||||
val modifier = 2f
|
||||
addChoice(relativeCostEffectiveness, spaceshipPart.name, modifier)
|
||||
addChoice(relativeCostEffectiveness, spaceshipPart.first().name, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addOtherBuildingChoice() {
|
||||
val otherBuilding = buildableNotWonders
|
||||
.filter { Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||
val otherBuilding = nonWonders
|
||||
.filter { Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (otherBuilding != null) {
|
||||
val modifier = 0.6f
|
||||
addChoice(relativeCostEffectiveness, otherBuilding.name, modifier)
|
||||
@ -246,25 +249,26 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addWondersChoice() {
|
||||
if (!buildableWonders.any()) return
|
||||
if (!wonders.any()) return
|
||||
|
||||
val highestPriorityWonder = buildableWonders
|
||||
val highestPriorityWonder = wonders
|
||||
.filter { Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.maxByOrNull { getWonderPriority(it) }
|
||||
.isBuildable().maxByOrNull { getWonderPriority(it as Building) }
|
||||
?: return
|
||||
|
||||
val citiesBuildingWonders = civInfo.cities
|
||||
.count { it.cityConstructions.isBuildingWonder() }
|
||||
|
||||
var modifier = 2f * getWonderPriority(highestPriorityWonder) / (citiesBuildingWonders + 1)
|
||||
var modifier = 2f * getWonderPriority(highestPriorityWonder as Building) / (citiesBuildingWonders + 1)
|
||||
if (!cityIsOverAverageProduction) modifier /= 5 // higher production cities will deal with this
|
||||
addChoice(relativeCostEffectiveness, highestPriorityWonder.name, modifier)
|
||||
}
|
||||
|
||||
private fun addUnitTrainingBuildingChoice() {
|
||||
val unitTrainingBuilding = buildableNotWonders.asSequence()
|
||||
val unitTrainingBuilding = nonWonders
|
||||
.filter { it.hasUnique(UniqueType.UnitStartingExperience)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }.minByOrNull { it.cost }
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (unitTrainingBuilding != null && (!civInfo.wantsToFocusOn(Victory.Focus.Culture) || isAtWar)) {
|
||||
var modifier = if (cityIsOverAverageProduction) 0.5f else 0.1f // You shouldn't be cranking out units anytime soon
|
||||
if (isAtWar) modifier *= 2
|
||||
@ -275,17 +279,17 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addDefenceBuildingChoice() {
|
||||
val defensiveBuilding = buildableNotWonders.asSequence()
|
||||
val defensiveBuilding = nonWonders
|
||||
.filter { it.cityStrength > 0
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)}.minByOrNull { it.cost }
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it)}
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (defensiveBuilding != null && (isAtWar || !civInfo.wantsToFocusOn(Victory.Focus.Culture))) {
|
||||
var modifier = 0.2f
|
||||
if (isAtWar) modifier = 0.5f
|
||||
|
||||
// If this city is the closest city to another civ, that makes it a likely candidate for attack
|
||||
if (civInfo.getKnownCivs()
|
||||
.map { NextTurnAutomation.getClosestCities(civInfo, it) }
|
||||
.filterNotNull()
|
||||
.mapNotNull { NextTurnAutomation.getClosestCities(civInfo, it) }
|
||||
.any { it.city1 == cityInfo })
|
||||
modifier *= 1.5f
|
||||
|
||||
@ -294,11 +298,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addHappinessBuildingChoice() {
|
||||
val happinessBuilding = buildableNotWonders.asSequence()
|
||||
val happinessBuilding = nonWonders
|
||||
.filter { (it.isStatRelated(Stat.Happiness)
|
||||
|| it.uniques.contains("Remove extra unhappiness from annexed cities"))
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.minByOrNull { it.cost }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (happinessBuilding != null) {
|
||||
var modifier = 1f
|
||||
val civHappiness = civInfo.getHappiness()
|
||||
@ -310,10 +314,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
|
||||
private fun addScienceBuildingChoice() {
|
||||
if (allTechsAreResearched) return
|
||||
val scienceBuilding = buildableNotWonders.asSequence()
|
||||
val scienceBuilding = nonWonders
|
||||
.filter { it.isStatRelated(Stat.Science)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.minByOrNull { it.cost }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (scienceBuilding != null) {
|
||||
var modifier = 1.1f
|
||||
if (civInfo.wantsToFocusOn(Victory.Focus.Science))
|
||||
@ -323,9 +327,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addGoldBuildingChoice() {
|
||||
val goldBuilding = buildableNotWonders.asSequence().filter { it.isStatRelated(Stat.Gold)
|
||||
val goldBuilding = nonWonders.filter { it.isStatRelated(Stat.Gold)
|
||||
&& Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.minByOrNull { it.cost }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (goldBuilding != null) {
|
||||
val modifier = if (civInfo.statsForNextTurn.gold < 0) 3f else 1.2f
|
||||
addChoice(relativeCostEffectiveness, goldBuilding.name, modifier)
|
||||
@ -333,9 +337,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
}
|
||||
|
||||
private fun addProductionBuildingChoice() {
|
||||
val productionBuilding = buildableNotWonders.asSequence()
|
||||
val productionBuilding = nonWonders
|
||||
.filter { it.isStatRelated(Stat.Production) && Automation.allowAutomatedConstruction(civInfo, cityInfo, it) }
|
||||
.minByOrNull { it.cost }
|
||||
.isBuildable().minByOrNull { it.cost }
|
||||
if (productionBuilding != null) {
|
||||
addChoice(relativeCostEffectiveness, productionBuilding.name, 1.5f)
|
||||
}
|
||||
@ -343,12 +347,12 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
|
||||
private fun addFoodBuildingChoice() {
|
||||
val conditionalState = StateForConditionals(civInfo, cityInfo)
|
||||
val foodBuilding = buildableNotWonders.asSequence()
|
||||
val foodBuilding = nonWonders
|
||||
.filter {
|
||||
(it.isStatRelated(Stat.Food)
|
||||
|| it.hasUnique(UniqueType.CarryOverFood, conditionalState)
|
||||
) && Automation.allowAutomatedConstruction(civInfo, cityInfo, it)
|
||||
}.minByOrNull { it.cost }
|
||||
}.isBuildable().minByOrNull { it.cost }
|
||||
if (foodBuilding != null) {
|
||||
var modifier = 1f
|
||||
if (cityInfo.population.population < 5) modifier = 1.3f
|
||||
@ -370,12 +374,12 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
|
||||
// The performance of the regular getMatchingUniques is better, since it only tries to find one unique,
|
||||
// while the canBePurchasedWithStat tries (at time of writing) *6* different uniques.
|
||||
val missionary = buildableUnits
|
||||
val missionary = units
|
||||
.firstOrNull { it -> it.getMatchingUniques("Can [] [] times").any { it.params[0] == "Spread Religion"}
|
||||
&& it.canBePurchasedWithStat(cityInfo, Stat.Faith) }
|
||||
|
||||
|
||||
val inquisitor = buildableUnits
|
||||
val inquisitor = units
|
||||
.firstOrNull { it.hasUnique("Prevents spreading of religion to the city it is next to")
|
||||
&& it.canBePurchasedWithStat(cityInfo, Stat.Faith) }
|
||||
|
||||
@ -395,8 +399,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
|
||||
|
||||
val buildMissionary = possibleSpreadReligionTargets.toList().size.toFloat() / 15 + modifier
|
||||
|
||||
if (buildMissionary > buildInquisitor && missionary != null) faithConstruction.add(missionary)
|
||||
else if(inquisitor != null) faithConstruction.add(inquisitor)
|
||||
if (buildMissionary > buildInquisitor && missionary != null && missionary.isBuildable(cityConstructions)) faithConstruction.add(missionary)
|
||||
else if (inquisitor != null && inquisitor.isBuildable(cityConstructions)) faithConstruction.add(inquisitor)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ interface IConstruction : INamed {
|
||||
}
|
||||
|
||||
interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
|
||||
var cost: Int
|
||||
val hurryCostModifier: Int
|
||||
var requiredTech: String?
|
||||
|
||||
|
@ -31,8 +31,8 @@ import kotlin.math.pow
|
||||
class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
|
||||
override var requiredTech: String? = null
|
||||
override var cost: Int = 0
|
||||
|
||||
var cost: Int = 0
|
||||
var maintenance = 0
|
||||
private var percentStatBonus: Stats? = null
|
||||
var specialistSlots: Counter<String> = Counter()
|
||||
@ -181,7 +181,6 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats {
|
||||
// Calls the clone function of the NamedStats this class is derived from, not a clone function of this class
|
||||
val stats = cloneStats()
|
||||
val civInfo = city.civInfo
|
||||
|
||||
for (unique in localUniqueCache.get("StatsFromObject", city.getMatchingUniques(UniqueType.StatsFromObject))) {
|
||||
if (!matchesFilter(unique.params[1])) continue
|
||||
|
@ -28,7 +28,7 @@ import kotlin.math.pow
|
||||
in contrast to MapUnit, which is a specific unit of a certain type that appears on the map */
|
||||
class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
|
||||
var cost: Int = 0
|
||||
override var cost: Int = 0
|
||||
override var hurryCostModifier: Int = 0
|
||||
var movement: Int = 0
|
||||
var strength: Int = 0
|
||||
|
Reference in New Issue
Block a user