From dcd8e6c8453247dac2a60aed589861896e8f619c Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Mon, 21 Mar 2022 20:05:43 +0100 Subject: [PATCH] Added an AI for building & using spaceship parts (#6374) --- .../automation/ConstructionAutomation.kt | 40 ++++++++++--------- .../automation/SpecificUnitAutomation.kt | 9 +++++ .../unciv/logic/automation/UnitAutomation.kt | 5 +++ .../com/unciv/logic/city/CityConstructions.kt | 1 + .../unciv/ui/worldscreen/unit/UnitActions.kt | 14 ++++--- 5 files changed, 46 insertions(+), 23 deletions(-) diff --git a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt index a5d93d4a6d..8ef9182ac3 100644 --- a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt @@ -26,6 +26,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ .filterNot { it.isAnyWonder() } private val buildableWonders = buildableBuildings .filter { it.isAnyWonder() } + + val buildableUnits = cityConstructions.getConstructableUnits() val civUnits = civInfo.getCivUnits() val militaryUnits = civUnits.count { it.baseUnit.isMilitary() } @@ -53,8 +55,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ fun chooseNextConstruction() { if (!UncivGame.Current.settings.autoAssignCityProduction && civInfo.playerType == PlayerType.Human && !cityInfo.isPuppet - ) + ) { return + } if (cityConstructions.getCurrentConstruction() !is PerpetualConstruction) return // don't want to be stuck on these forever addFoodBuildingChoice() @@ -65,15 +68,15 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ addDefenceBuildingChoice() addUnitTrainingBuildingChoice() addCultureBuildingChoice() - addSpaceshipPartChoice() addOtherBuildingChoice() - addReligiousUnit() if (!cityInfo.isPuppet) { + addSpaceshipPartChoice() addWondersChoice() addWorkerChoice() addWorkBoatChoice() addMilitaryUnitChoice() + addReligiousUnit() } val production = cityInfo.cityStats.currentCityStats.production @@ -138,15 +141,16 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ } private fun addWorkBoatChoice() { - val buildableWorkboatUnits = cityInfo.cityConstructions.getConstructableUnits() + val buildableWorkboatUnits = buildableUnits .filter { it.hasUnique(UniqueType.CreateWaterImprovements) - && Automation.allowSpendingResource(civInfo, it) + && Automation.allowSpendingResource(civInfo, it) } - val canBuildWorkboat = buildableWorkboatUnits.any() - && !cityInfo.getTiles() - .any { it.civilianUnit?.hasUnique(UniqueType.CreateWaterImprovements) == true } - if (!canBuildWorkboat) return + val alreadyHasWorkBoat = buildableWorkboatUnits.any() + && !cityInfo.getTiles().any { + it.civilianUnit?.hasUnique(UniqueType.CreateWaterImprovements) == true + } + if (!alreadyHasWorkBoat) return val bfs = BFS(cityInfo.getCenterTile()) { @@ -167,12 +171,12 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ } private fun addWorkerChoice() { - val workerEquivalents = civInfo.gameInfo.ruleSet.units.values + val workerEquivalents = buildableUnits .filter { it.hasUnique(UniqueType.BuildImprovements) - && it.isBuildable(cityConstructions) - && Automation.allowSpendingResource(civInfo, it) } - if (workerEquivalents.isEmpty()) return // for mods with no worker units + && Automation.allowSpendingResource(civInfo, it) + } + if (workerEquivalents.none()) return // for mods with no worker units if (workers < cities) { var modifier = cities / (workers + 0.1f) // The worse our worker to city ratio is, the more desperate we are @@ -195,7 +199,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ } private fun addSpaceshipPartChoice() { - val spaceshipPart = buildableNotWonders.firstOrNull { it.hasUnique(UniqueType.SpaceshipPart) } + val spaceshipPart = (buildableNotWonders + buildableUnits).firstOrNull { it.hasUnique(UniqueType.SpaceshipPart) } if (spaceshipPart != null) { var modifier = 1.5f if (preferredVictoryType == VictoryType.Scientific) modifier = 2f @@ -358,12 +362,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 = cityInfo.getRuleset().units.values + val missionary = buildableUnits .firstOrNull { it -> it.getMatchingUniques("Can [] [] times").any { it.params[0] == "Spread Religion"} && it.canBePurchasedWithStat(cityInfo, Stat.Faith) } - val inquisitor = cityInfo.getRuleset().units.values + val inquisitor = buildableUnits .firstOrNull { it.hasUnique("Prevents spreading of religion to the city it is next to") && it.canBePurchasedWithStat(cityInfo, Stat.Faith) } @@ -374,7 +378,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val citiesNotFollowingOurReligion = civInfo.cities.asSequence() .filterNot { it.religion.getMajorityReligion()?.name == civInfo.religionManager.religion!!.name } - val buildInqusitor = citiesNotFollowingOurReligion + val buildInquisitor = citiesNotFollowingOurReligion .filter { it.religion.getMajorityReligion()?.name == civInfo.religionManager.religion?.name } .toList().size.toFloat() / 10 + modifier @@ -383,7 +387,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ val buildMissionary = possibleSpreadReligionTargets.toList().size.toFloat() / 15 + modifier - if (buildMissionary > buildInqusitor && missionary != null) faithConstruction.add(missionary) + if (buildMissionary > buildInquisitor && missionary != null) faithConstruction.add(missionary) else if(inquisitor != null) faithConstruction.add(inquisitor) } diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 86eac1c232..0bec480b1a 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -282,6 +282,15 @@ object SpecificUnitAutomation { } } + fun automateAddInCapital(unit: MapUnit) { + val capitalTile = unit.civInfo.getCapital().getCenterTile() + val unitTile = unit.movement.headTowards(capitalTile) + if (unitTile == capitalTile) { + UnitActions.getAddInCapitalAction(unit, unitTile).action!!() + return + } + } + fun automateMissionary(unit: MapUnit) { if (unit.religion != unit.civInfo.religionManager.religion?.name) return unit.destroy() diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index c448482450..02d7a35154 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -153,6 +153,11 @@ object UnitAutomation { if (unit.hasUnique(UniqueType.CreateWaterImprovements)) return SpecificUnitAutomation.automateWorkBoats(unit) + // We try to add any unit in the capital we can, though that might not always be desirable + // For now its a simple option to allow AI to win a science victory again + if (unit.hasUnique(UniqueType.AddInCapital)) + return SpecificUnitAutomation.automateAddInCapital(unit) + if (unit.hasUnique("Bonus for units in 2 tile radius 15%")) return SpecificUnitAutomation.automateGreatGeneral(unit) diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index 10721574b1..3f8aa78a6b 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -71,6 +71,7 @@ class CityConstructions { return toReturn } + // Why is one of these called 'buildable' and the other 'constructable'? internal fun getBuildableBuildings(): Sequence = cityInfo.getRuleset().buildings.values .asSequence().filter { it.isBuildable(this) } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index a405744113..093c6a4f23 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -407,11 +407,9 @@ object UnitActions { }.takeIf { unit.currentMovement > 0 } ) } - - private fun addAddInCapitalAction(unit: MapUnit, actionList: ArrayList, tile: TileInfo) { - if (!unit.hasUnique(UniqueType.AddInCapital)) return - - actionList += UnitAction(UnitActionType.AddInCapital, + + fun getAddInCapitalAction(unit: MapUnit, tile: TileInfo): UnitAction { + return UnitAction(UnitActionType.AddInCapital, title = "Add to [${unit.getMatchingUniques(UniqueType.AddInCapital).first().params[0]}]", action = { unit.civInfo.victoryManager.currentsSpaceshipParts.add(unit.name, 1) @@ -420,6 +418,12 @@ object UnitActions { ) } + private fun addAddInCapitalAction(unit: MapUnit, actionList: ArrayList, tile: TileInfo) { + if (!unit.hasUnique(UniqueType.AddInCapital)) return + + actionList += getAddInCapitalAction(unit, tile) + } + private fun addGreatPersonActions(unit: MapUnit, actionList: ArrayList, tile: TileInfo) {