AI worker improvements (#12153)

* AI behaviour changes

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update ConstructionAutomation.kt

* Update Automation.kt

* Reverting some changes

* Changes

* revert changes

* revert changes

* revert changes

* revert changes

* Update CityLocationTileRanker.kt

* Citizen assignment for stat conversion

* Update CityLocationTileRanker.kt

* Reduce AI settling

* Avoid AI building units when in negative Supply

* Update CityLocationTileRanker.kt

* Update CityLocationTileRanker.kt

* Update CityLocationTileRanker.kt

* Update ConstructionAutomation.kt

* Update build.gradle.kts

* Update gradle-wrapper.properties

* Update CityLocationTileRanker.kt

* Update CityLocationTileRanker.kt

* Update ConstructionAutomation.kt

* Update CityLocationTileRanker.kt

* AI changes for humans

* Fix puppet focus

* Update Automation.kt

* Puppet focus

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Automation.kt

* Update Stats.kt

* Update CityTurnManager.kt

* Remove specialist science modifier

* Update ReligionAutomation.kt

* Update ReligionAutomation.kt

* Update ReligionAutomation.kt

* Update CivilianUnitAutomation.kt

* Update ReligionAutomation.kt

* Worker prioritization

Workers are valuable in expand cities.

* Update ConstructionAutomation.kt

Food always important, it's rarely good to skip e.g. granary if we're on 6 pop.

* Update ConstructionAutomation.kt

Should achieve about the same with less lines of code.

* Update Automation.kt

* Update ConstructionAutomation.kt

* Update Policies.json

* Update Policies.json

* Update Policies.json

* Update ConstructionAutomation.kt

* Update Policies.json

* Update ReligionAutomation.kt

* Update ReligionAutomation.kt

* Update ReligionAutomation.kt

* Update ReligionAutomation.kt

* Rename Crop Yield to Growth

* Update worker usage
This commit is contained in:
EmperorPinguin 2024-08-24 21:04:24 +02:00 committed by GitHub
parent e8565b0b6f
commit 1009669126
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 33 additions and 36 deletions

View File

@ -470,16 +470,16 @@ object Automation {
fun rankStatsValue(stats: Stats, civInfo: Civilization): Float { fun rankStatsValue(stats: Stats, civInfo: Civilization): Float {
var rank = 0.0f var rank = 0.0f
rank += if (stats.food <= 2) rank += stats.food * 1.2f //food get more value to keep city growing
(stats.food * 1.2f) //food get more value to keep city growing
else
(2.4f + (stats.food - 2) / 2) // 1.2 point for each food up to 2, from there on half a point
rank += if (civInfo.gold < 0 && civInfo.stats.statsForNextTurn.gold <= 0) rank += if (civInfo.gold < 0 && civInfo.stats.statsForNextTurn.gold <= 0)
stats.gold stats.gold //build more gold infrastructure if in serious gold problems
// This could lead to oscilliatory behaviour however: gold problem -> build trade post -> no gold problem -> replace trade posts -> gold problem
else else
stats.gold / 3 // 3 gold is much worse than 2 production stats.gold / 3 // Gold is valued less than is the case for citizen assignment,
//otherwise the AI would replace tiles with trade posts upon entering a golden age,
//and replace the trade post again when the golden age ends.
// We need a way to take golden age gold into account before the GA actually takes place
rank += stats.happiness rank += stats.happiness
rank += stats.production rank += stats.production
rank += stats.science rank += stats.science

View File

@ -165,6 +165,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
private fun addMilitaryUnitChoice() { private fun addMilitaryUnitChoice() {
if (!isAtWar && !cityIsOverAverageProduction) return // don't make any military units here. Infrastructure first! if (!isAtWar && !cityIsOverAverageProduction) return // don't make any military units here. Infrastructure first!
// There is a risk however, that these cities run out of things to build, and start to construct nothing
if (civInfo.stats.getUnitSupplyDeficit() > 0) return // we don't want more units if it's already hurting our empire if (civInfo.stats.getUnitSupplyDeficit() > 0) return // we don't want more units if it's already hurting our empire
// todo: add worker disbandment and consumption of great persons if under attack & short on unit supply // todo: add worker disbandment and consumption of great persons if under attack & short on unit supply
if (!isAtWar && (civInfo.stats.statsForNextTurn.gold < 0 || militaryUnits > max(7, cities * 5))) return if (!isAtWar && (civInfo.stats.statsForNextTurn.gold < 0 || militaryUnits > max(7, cities * 5))) return
@ -244,8 +245,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
}.filterBuildable() }.filterBuildable()
if (workerEquivalents.none()) return // for mods with no worker units if (workerEquivalents.none()) return // for mods with no worker units
// Dedicate a worker for the first 5 cities, from then on only build another worker for every 2 cities. // Dedicate 1.5 workers for the first 5 cities, from then on only build one worker for every city.
val numberOfWorkersWeWant = if (cities <= 5) cities else 5 + (cities - 5 / 2) val numberOfWorkersWeWant = if (cities <= 5) (cities * 1.5f) else 7.5f + ((cities - 5))
if (workers < numberOfWorkersWeWant) { if (workers < numberOfWorkersWeWant) {
val modifier = numberOfWorkersWeWant / (workers + 0.4f) // The worse our worker to city ratio is, the more desperate we are val modifier = numberOfWorkersWeWant / (workers + 0.4f) // The worse our worker to city ratio is, the more desperate we are
@ -266,7 +267,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
val localUniqueCache = LocalUniqueCache() val localUniqueCache = LocalUniqueCache()
for (building in buildings.filterBuildable()) { for (building in buildings.filterBuildable()) {
if (building.isWonder && city.isPuppet) continue if (building.isWonder && city.isPuppet) continue
// We shouldn't try to build wonders in undeveloped empires // We shouldn't try to build wonders in undeveloped cities and empires
if (building.isWonder && !cityIsOverAverageProduction)
if (building.isWonder && civInfo.cities.size < 3) continue if (building.isWonder && civInfo.cities.size < 3) continue
addChoice(relativeCostEffectiveness, building.name, getValueOfBuilding(building, localUniqueCache)) addChoice(relativeCostEffectiveness, building.name, getValueOfBuilding(building, localUniqueCache))
} }

View File

@ -528,9 +528,6 @@ object NextTurnAutomation {
if (civInfo.isCityState) return if (civInfo.isCityState) return
if (civInfo.isOneCityChallenger()) return if (civInfo.isOneCityChallenger()) return
if (civInfo.isAtWar()) return // don't train settlers when you could be training troops. if (civInfo.isAtWar()) return // don't train settlers when you could be training troops.
if (civInfo.wantsToFocusOn(Victory.Focus.Culture) && civInfo.cities.size > 3 &&
civInfo.getPersonality().isNeutralPersonality)
return
if (civInfo.cities.none()) return if (civInfo.cities.none()) return
if (civInfo.getHappiness() <= civInfo.cities.size) return if (civInfo.getHappiness() <= civInfo.cities.size) return

View File

@ -102,9 +102,8 @@ object CivilianUnitAutomation {
return return
} }
// Great engineer -> Try to speed up wonder construction if late game // Great engineer -> Try to speed up wonder construction
if (isLateGame && if ((unit.hasUnique(UniqueType.CanSpeedupConstruction)
(unit.hasUnique(UniqueType.CanSpeedupConstruction)
|| unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) { || unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) {
val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit) val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit)
if (wonderCanBeSpedUpEventually) if (wonderCanBeSpedUpEventually)

View File

@ -18,6 +18,7 @@ import com.unciv.models.ruleset.tile.Terrain
import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques
@ -74,8 +75,8 @@ class WorkerAutomation(
val currentTile = unit.getTile() val currentTile = unit.getTile()
// Must be called before any getPriority checks to guarantee the local road cache is processed // Must be called before any getPriority checks to guarantee the local road cache is processed
val citiesToConnect = roadBetweenCitiesAutomation.getNearbyCitiesToConnect(unit) val citiesToConnect = roadBetweenCitiesAutomation.getNearbyCitiesToConnect(unit)
// Shortcut, we are working a good tile (like resource) and don't need to check for other tiles to work // Shortcut, we are working a suitable tile, and we're better off minimizing worker-turns by finishing everything on this tile
if (!dangerousTiles.contains(currentTile) && getFullPriority(unit.getTile(), unit, localUniqueCache) >= 10 if (!dangerousTiles.contains(currentTile) && getFullPriority(unit.getTile(), unit, localUniqueCache) >= 2
&& currentTile.improvementInProgress != null) { && currentTile.improvementInProgress != null) {
return return
} }
@ -160,7 +161,7 @@ class WorkerAutomation(
} }
// Nothing to do, try again to connect cities // Nothing to do, try again to connect cities
if (civInfo.stats.statsForNextTurn.gold > 10 && roadBetweenCitiesAutomation.tryConnectingCities(unit, citiesToConnect)) return if (roadBetweenCitiesAutomation.tryConnectingCities(unit, citiesToConnect)) return
debug("WorkerAutomation: %s -> nothing to do", unit.toString()) debug("WorkerAutomation: %s -> nothing to do", unit.toString())
@ -231,24 +232,22 @@ class WorkerAutomation(
if (tile.providesYield()) priority += 2 if (tile.providesYield()) priority += 2
if (tile.isPillaged()) priority += 1 if (tile.isPillaged()) priority += 1
if (tile.hasFalloutEquivalent()) priority += 1 if (tile.hasFalloutEquivalent()) priority += 1
if (tile.terrainFeatures.isNotEmpty() && tile.lastTerrain.hasUnique("Provides a one-time Production bonus to the closest city when cut down")) priority += 1 // removing our forests is good for tempo
if (tile.terrainHasUnique(UniqueType.FreshWater)) priority += 1 // we want our farms up when unlocking Civil Service
} }
// give a minor priority to tiles that we could expand onto // give a minor priority to tiles that we could expand onto
else if (tile.getOwner() == null && tile.neighbors.any { it.getOwner() == civInfo }) else if (tile.getOwner() == null && tile.neighbors.any { it.getOwner() == civInfo })
priority += 1 priority += 1
if (priority <= 0 && tile.hasViewableResource(civInfo)) { if (tile.hasViewableResource(civInfo)) {
priority += 1 priority += 1
// New Resources are great! if (tile.tileResource.resourceType == ResourceType.Luxury) priority += 3
if (tile.tileResource.resourceType != ResourceType.Bonus //luxuries are more important than other types of resources
&& !civInfo.hasResource(tile.resource!!))
priority += 2
} }
if (tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) priority += when {
civInfo.stats.statsForNextTurn.gold <= 5 -> 0 if (tile in roadBetweenCitiesAutomation.tilesOfRoadsMap)
civInfo.stats.statsForNextTurn.gold <= 10 -> 1 priority += 3
civInfo.stats.statsForNextTurn.gold <= 30 -> 2
else -> 3
}
tileRankings[tile] = TileImprovementRank(priority) tileRankings[tile] = TileImprovementRank(priority)
return priority + unitSpecificPriority return priority + unitSpecificPriority
} }
@ -357,9 +356,10 @@ class WorkerAutomation(
// After gathering all the data, we conduct the hierarchy in one place // After gathering all the data, we conduct the hierarchy in one place
val improvementString = when { val improvementString = when {
bestBuildableImprovement != null && bestBuildableImprovement.isRoad() -> bestBuildableImprovement.name bestBuildableImprovement != null && bestBuildableImprovement.isRoad() -> bestBuildableImprovement.name
improvementStringForResource != null -> if (improvementStringForResource==tile.improvement) null else improvementStringForResource // For bonus resources we just want the highest-yield improvement, not necessarily the resource-yielding improvement
improvementStringForResource != null && tile.tileResource.resourceType != ResourceType.Bonus -> if (improvementStringForResource==tile.improvement) null else improvementStringForResource
// If this is a resource that HAS an improvement that we can see, but this unit can't build it, don't waste your time // If this is a resource that HAS an improvement that we can see, but this unit can't build it, don't waste your time
tile.resource != null && tile.hasViewableResource(civInfo) && tile.tileResource.getImprovements().any() -> return null tile.resource != null && tile.hasViewableResource(civInfo) && tile.tileResource.resourceType != ResourceType.Bonus && tile.tileResource.getImprovements().any() -> return null
bestBuildableImprovement == null -> null bestBuildableImprovement == null -> null
tile.improvement != null && tile.improvement != null &&
@ -386,10 +386,7 @@ class WorkerAutomation(
if (improvement.isRoad() && roadBetweenCitiesAutomation.bestRoadAvailable.improvement(ruleSet) == improvement if (improvement.isRoad() && roadBetweenCitiesAutomation.bestRoadAvailable.improvement(ruleSet) == improvement
&& tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) { && tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) {
val roadPlan = roadBetweenCitiesAutomation.tilesOfRoadsMap[tile]!! val roadPlan = roadBetweenCitiesAutomation.tilesOfRoadsMap[tile]!!
var value = roadPlan.priority val value = (roadPlan.priority - 5) // We want some forest chopping and farm building first if the road doesn't have high priority
if (civInfo.stats.statsForNextTurn.gold >= 20)
// Higher priority if we are closer to connecting the city
value += (5 - roadPlan.numberOfRoadsToBuild).coerceAtLeast(0)
return value return value
} }
@ -424,6 +421,8 @@ class WorkerAutomation(
stats.add(statDiff) stats.add(statDiff)
// Take into account that the resource might be improved by the *final* improvement // Take into account that the resource might be improved by the *final* improvement
isResourceImprovedByNewImprovement = newTile.resource != null && newTile.tileResource.isImprovedBy(wantedFinalImprovement.name) isResourceImprovedByNewImprovement = newTile.resource != null && newTile.tileResource.isImprovedBy(wantedFinalImprovement.name)
if (tile.terrainFeatures.isNotEmpty() && tile.lastTerrain.hasUnique("Provides a one-time Production bonus to the closest city when cut down"))
stats.add(Stat.Production, 0.5f) //We're gaining tempo by chopping the forest, adding an imaginary yield per turn is a way to correct for this
} }
} }