From 1009669126658e4cbb76fc891c6927ed3e06fe03 Mon Sep 17 00:00:00 2001 From: EmperorPinguin <99119424+EmperorPinguin@users.noreply.github.com> Date: Sat, 24 Aug 2024 21:04:24 +0200 Subject: [PATCH] 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 --- .../com/unciv/logic/automation/Automation.kt | 14 +++---- .../automation/city/ConstructionAutomation.kt | 8 ++-- .../civilization/NextTurnAutomation.kt | 3 -- .../automation/unit/CivilianUnitAutomation.kt | 5 +-- .../logic/automation/unit/WorkerAutomation.kt | 39 +++++++++---------- 5 files changed, 33 insertions(+), 36 deletions(-) diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index cd09365a93..39c701a8a9 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -470,16 +470,16 @@ object Automation { fun rankStatsValue(stats: Stats, civInfo: Civilization): Float { var rank = 0.0f - rank += if (stats.food <= 2) - (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 += stats.food * 1.2f //food get more value to keep city growing 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 - 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.production rank += stats.science diff --git a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt index 070e9eba32..d3811ff131 100644 --- a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt @@ -165,6 +165,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) { private fun addMilitaryUnitChoice() { 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 // 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 @@ -244,8 +245,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) { }.filterBuildable() 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. - val numberOfWorkersWeWant = if (cities <= 5) cities else 5 + (cities - 5 / 2) + // 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 * 1.5f) else 7.5f + ((cities - 5)) if (workers < numberOfWorkersWeWant) { 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() for (building in buildings.filterBuildable()) { 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 addChoice(relativeCostEffectiveness, building.name, getValueOfBuilding(building, localUniqueCache)) } diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index ae696bf53d..47af7f9887 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -528,9 +528,6 @@ object NextTurnAutomation { if (civInfo.isCityState) return if (civInfo.isOneCityChallenger()) return 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.getHappiness() <= civInfo.cities.size) return diff --git a/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt index 6e3c6fbcc0..da53636294 100644 --- a/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/CivilianUnitAutomation.kt @@ -102,9 +102,8 @@ object CivilianUnitAutomation { return } - // Great engineer -> Try to speed up wonder construction if late game - if (isLateGame && - (unit.hasUnique(UniqueType.CanSpeedupConstruction) + // Great engineer -> Try to speed up wonder construction + if ((unit.hasUnique(UniqueType.CanSpeedupConstruction) || unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) { val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit) if (wonderCanBeSpedUpEventually) diff --git a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt index 3c25a7a3c6..00f50ded9e 100644 --- a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt @@ -18,6 +18,7 @@ import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.LocalUniqueCache import com.unciv.models.ruleset.unique.UniqueType +import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsFromUniques @@ -74,8 +75,8 @@ class WorkerAutomation( val currentTile = unit.getTile() // Must be called before any getPriority checks to guarantee the local road cache is processed 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 - if (!dangerousTiles.contains(currentTile) && getFullPriority(unit.getTile(), unit, localUniqueCache) >= 10 + // 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) >= 2 && currentTile.improvementInProgress != null) { return } @@ -160,7 +161,7 @@ class WorkerAutomation( } // 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()) @@ -231,24 +232,22 @@ class WorkerAutomation( if (tile.providesYield()) priority += 2 if (tile.isPillaged()) 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 else if (tile.getOwner() == null && tile.neighbors.any { it.getOwner() == civInfo }) priority += 1 - if (priority <= 0 && tile.hasViewableResource(civInfo)) { + if (tile.hasViewableResource(civInfo)) { priority += 1 - // New Resources are great! - if (tile.tileResource.resourceType != ResourceType.Bonus - && !civInfo.hasResource(tile.resource!!)) - priority += 2 + if (tile.tileResource.resourceType == ResourceType.Luxury) priority += 3 + //luxuries are more important than other types of resources } - if (tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) priority += when { - civInfo.stats.statsForNextTurn.gold <= 5 -> 0 - civInfo.stats.statsForNextTurn.gold <= 10 -> 1 - civInfo.stats.statsForNextTurn.gold <= 30 -> 2 - else -> 3 - } + + if (tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) + priority += 3 + tileRankings[tile] = TileImprovementRank(priority) return priority + unitSpecificPriority } @@ -357,9 +356,10 @@ class WorkerAutomation( // After gathering all the data, we conduct the hierarchy in one place val improvementString = when { 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 - 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 tile.improvement != null && @@ -386,10 +386,7 @@ class WorkerAutomation( if (improvement.isRoad() && roadBetweenCitiesAutomation.bestRoadAvailable.improvement(ruleSet) == improvement && tile in roadBetweenCitiesAutomation.tilesOfRoadsMap) { val roadPlan = roadBetweenCitiesAutomation.tilesOfRoadsMap[tile]!! - var value = roadPlan.priority - if (civInfo.stats.statsForNextTurn.gold >= 20) - // Higher priority if we are closer to connecting the city - value += (5 - roadPlan.numberOfRoadsToBuild).coerceAtLeast(0) + val value = (roadPlan.priority - 5) // We want some forest chopping and farm building first if the road doesn't have high priority return value } @@ -424,6 +421,8 @@ class WorkerAutomation( stats.add(statDiff) // Take into account that the resource might be improved by the *final* improvement 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 } }