mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-19 16:57:38 +07:00
Great people automation (#9125)
* Add automation for great scientist & merchant * Automate great people (great merchant, great engineer & great scientist). * Address comments * Rename method for consistency * Resolve comments
This commit is contained in:
parent
b9a7925285
commit
d25804ffb5
@ -11,6 +11,8 @@ import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.models.UnitAction
|
||||
import com.unciv.models.UnitActionType
|
||||
import com.unciv.models.ruleset.Building
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
|
||||
@ -183,13 +185,15 @@ object SpecificUnitAutomation {
|
||||
foundCityAction.action.invoke()
|
||||
}
|
||||
|
||||
fun automateImprovementPlacer(unit: MapUnit) {
|
||||
/** @return whether there was any progress in placing the improvement. A return value of `false`
|
||||
* can be interpreted as: the unit doesn't know where to place the improvement or is stuck. */
|
||||
fun automateImprovementPlacer(unit: MapUnit) : Boolean {
|
||||
val improvementBuildingUniques = unit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) +
|
||||
unit.getMatchingUniques(UniqueType.ConstructImprovementInstantly)
|
||||
|
||||
val improvementName = improvementBuildingUniques.first().params[0]
|
||||
val improvement = unit.civ.gameInfo.ruleset.tileImprovements[improvementName]
|
||||
?: return
|
||||
?: return false
|
||||
val relatedStat = improvement.maxByOrNull { it.value }?.key ?: Stat.Culture
|
||||
|
||||
val citiesByStatBoost = unit.civ.cities.sortedByDescending {
|
||||
@ -209,9 +213,18 @@ object SpecificUnitAutomation {
|
||||
|
||||
if (pathToCity.isEmpty()) continue
|
||||
if (pathToCity.size > 2 && unit.getTile().getCity() != city) {
|
||||
if (unit.getTile().militaryUnit == null) return // Don't move until you're accompanied by a military unit
|
||||
// Radius 5 is quite arbitrary. Few units have such a high movement radius although
|
||||
// streets might modify it. Also there might be invisible units, so this is just an
|
||||
// approximation for relative safety and simplicity.
|
||||
val enemyUnitsNearby = unit.getTile().getTilesInDistance(5).any { tileNearby ->
|
||||
tileNearby.getUnits().any { unitOnTileNearby ->
|
||||
unitOnTileNearby.isMilitary() && unitOnTileNearby.civ.isAtWarWith(unit.civ)
|
||||
}
|
||||
}
|
||||
// Don't move until you're accompanied by a military unit if there are enemies nearby.
|
||||
if (unit.getTile().militaryUnit == null && enemyUnitsNearby) return true
|
||||
unit.movement.headTowards(city.getCenterTile())
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
// if we got here, we're pretty close, start looking!
|
||||
@ -224,14 +237,108 @@ object SpecificUnitAutomation {
|
||||
.firstOrNull { unit.movement.canReach(it) }
|
||||
?: continue // to another city
|
||||
|
||||
val unitTileBeforeMovement = unit.currentTile
|
||||
unit.movement.headTowards(chosenTile)
|
||||
if (unit.currentTile == chosenTile)
|
||||
if (unit.currentTile == chosenTile) {
|
||||
if (unit.currentTile.isPillaged())
|
||||
UnitActions.getRepairAction(unit).invoke()
|
||||
else
|
||||
UnitActions.getImprovementConstructionActions(unit, unit.currentTile).firstOrNull()?.action?.invoke()
|
||||
return
|
||||
UnitActions.getImprovementConstructionActions(unit, unit.currentTile)
|
||||
.firstOrNull()?.action?.invoke()
|
||||
return true
|
||||
}
|
||||
return unitTileBeforeMovement != unit.currentTile
|
||||
}
|
||||
// No city needs this improvement.
|
||||
return false
|
||||
}
|
||||
|
||||
/** @return whether there was any progress in conducting the trade mission. A return value of
|
||||
* `false` can be interpreted as: the unit doesn't know where to go or there are no city
|
||||
* states. */
|
||||
fun conductTradeMission(unit: MapUnit): Boolean {
|
||||
val closestCityStateTile =
|
||||
unit.civ.gameInfo.civilizations
|
||||
.filter {
|
||||
!unit.civ.isAtWarWith(it) && it.isCityState() && it.cities.isNotEmpty()
|
||||
}
|
||||
.flatMap { it.cities[0].getTiles() }
|
||||
.filter { unit.civ.hasExplored(it) }
|
||||
.mapNotNull { tile ->
|
||||
val path = unit.movement.getShortestPath(tile)
|
||||
if (path.size <= 10) tile to path.size else null
|
||||
}
|
||||
.minByOrNull { it.second }?.first
|
||||
?: return false
|
||||
|
||||
val conductTradeMissionAction = UnitActions.getUnitActions(unit)
|
||||
.firstOrNull { it.type == UnitActionType.ConductTradeMission }
|
||||
if (conductTradeMissionAction?.action != null) {
|
||||
conductTradeMissionAction.action.invoke()
|
||||
return true
|
||||
}
|
||||
|
||||
val unitTileBeforeMovement = unit.currentTile
|
||||
unit.movement.headTowards(closestCityStateTile)
|
||||
|
||||
return unitTileBeforeMovement != unit.currentTile
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's a city nearby that can construct a wonder, walk there an get it built. Typically I
|
||||
* like to build all wonders in the same city to have the boni accumulate (and it typically ends
|
||||
* up being my capital), but that would need too much logic (e.g. how far away is the capital,
|
||||
* is the wonder likely still available by the time I'm there, is this particular wonder even
|
||||
* buildable in the capital, etc.)
|
||||
*
|
||||
* @return whether there was any progress in speeding up a wonder construction. A return value
|
||||
* of `false` can be interpreted as: the unit doesn't know where to go or is stuck. */
|
||||
fun speedupWonderConstruction(unit: MapUnit): Boolean {
|
||||
val nearbyCityWithAvailableWonders = unit.civ.cities.filter { city ->
|
||||
// Maybe it would be nice to make space in the city if there's already some
|
||||
// other civilian unit in there for whatever reason, but again that seems a lot of
|
||||
// additional complexity for questionable gain.
|
||||
(unit.movement.canMoveTo(city.getCenterTile()) || unit.currentTile == city.getCenterTile())
|
||||
// Don't speed up construction in small cities. There's a risk the great
|
||||
// engineer can't get it done entirely and then it takes forever for the small
|
||||
// city to finish the rest.
|
||||
&& city.population.population >= 3
|
||||
&& getWonderThatWouldBenefitFromBeingSpedUp(city) != null
|
||||
}.mapNotNull { city ->
|
||||
val path = unit.movement.getShortestPath(city.getCenterTile())
|
||||
if (path.size <= 5) city to path.size else null
|
||||
}.minByOrNull { it.second }?.first
|
||||
|
||||
if (nearbyCityWithAvailableWonders == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (unit.currentTile == nearbyCityWithAvailableWonders.getCenterTile()) {
|
||||
val wonderToHurry =
|
||||
getWonderThatWouldBenefitFromBeingSpedUp(nearbyCityWithAvailableWonders)!!
|
||||
nearbyCityWithAvailableWonders.cityConstructions.constructionQueue.add(
|
||||
0,
|
||||
wonderToHurry.name
|
||||
)
|
||||
UnitActions.getUnitActions(unit)
|
||||
.first {
|
||||
it.type == UnitActionType.HurryBuilding
|
||||
|| it.type == UnitActionType.HurryWonder }
|
||||
.action!!.invoke()
|
||||
return true
|
||||
}
|
||||
|
||||
// Walk towards the city.
|
||||
val tileBeforeMoving = unit.getTile()
|
||||
unit.movement.headTowards(nearbyCityWithAvailableWonders.getCenterTile())
|
||||
return tileBeforeMoving != unit.currentTile
|
||||
}
|
||||
|
||||
private fun getWonderThatWouldBenefitFromBeingSpedUp(city: City): Building? {
|
||||
return city.cityConstructions.getBuildableBuildings().filter { building ->
|
||||
building.isWonder && !building.hasUnique(UniqueType.CannotBeHurried)
|
||||
&& city.cityConstructions.turnsToConstruction(building.name) >= 5
|
||||
}.sortedBy { -city.cityConstructions.getRemainingWork(it.name) }.firstOrNull()
|
||||
}
|
||||
|
||||
fun automateAddInCapital(unit: MapUnit) {
|
||||
|
@ -9,11 +9,13 @@ import com.unciv.logic.battle.CityCombatant
|
||||
import com.unciv.logic.battle.ICombatant
|
||||
import com.unciv.logic.battle.MapUnitCombatant
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||
import com.unciv.logic.civilization.managers.ReligionState
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.models.UnitActionType
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActions
|
||||
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsPillage
|
||||
@ -270,16 +272,72 @@ object UnitAutomation {
|
||||
if (unit.hasUnique(UniqueType.PreventSpreadingReligion) || unit.canDoLimitedAction(Constants.removeHeresy))
|
||||
return SpecificUnitAutomation.automateInquisitor(unit)
|
||||
|
||||
if (unit.hasUnique(UniqueType.ConstructImprovementConsumingUnit)
|
||||
|| unit.hasUnique(UniqueType.ConstructImprovementInstantly))
|
||||
// catch great prophet for civs who can't found/enhance/spread religion
|
||||
return SpecificUnitAutomation.automateImprovementPlacer(unit) // includes great people plus moddable units
|
||||
val isLateGame = isLateGame(unit.civ)
|
||||
// Great scientist -> Hurry research if late game
|
||||
if (UnitActions.getUnitActions(unit).any { it.type == UnitActionType.HurryResearch }
|
||||
&& isLateGame) {
|
||||
UnitActions.getUnitActions(unit)
|
||||
.first { it.type == UnitActionType.HurryResearch }.action!!.invoke()
|
||||
return
|
||||
}
|
||||
|
||||
// ToDo: automation of great people skills (may speed up construction, provides a science boost, etc.)
|
||||
// Great merchant -> Conduct trade mission if late game and if not at war.
|
||||
// TODO: This could be more complex to walk to the city state that is most beneficial to
|
||||
// also have more influence.
|
||||
if (unit.hasUnique(UniqueType.CanTradeWithCityStateForGoldAndInfluence)
|
||||
// Don't wander around with the great merchant when at war. Barbs might also be a
|
||||
// problem, but hopefully by the time we have a great merchant, they're under
|
||||
// control.
|
||||
&& !unit.civ.isAtWar()
|
||||
&& isLateGame
|
||||
) {
|
||||
val tradeMissionCanBeConductedEventually =
|
||||
SpecificUnitAutomation.conductTradeMission(unit)
|
||||
if (tradeMissionCanBeConductedEventually)
|
||||
return
|
||||
}
|
||||
|
||||
// Great engineer -> Try to speed up wonder construction if late game
|
||||
if (isLateGame &&
|
||||
(unit.hasUnique(UniqueType.CanSpeedupConstruction)
|
||||
|| unit.hasUnique(UniqueType.CanSpeedupWonderConstruction))) {
|
||||
val wonderCanBeSpedUpEventually = SpecificUnitAutomation.speedupWonderConstruction(unit)
|
||||
if (wonderCanBeSpedUpEventually)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// This has to come after the individual abilities for the great people that can also place
|
||||
// instant improvements (e.g. great scientist).
|
||||
if (unit.hasUnique(UniqueType.ConstructImprovementConsumingUnit)
|
||||
|| unit.hasUnique(UniqueType.ConstructImprovementInstantly)
|
||||
) {
|
||||
// catch great prophet for civs who can't found/enhance/spread religion
|
||||
// includes great people plus moddable units
|
||||
val improvementCanBePlacedEventually =
|
||||
SpecificUnitAutomation.automateImprovementPlacer(unit)
|
||||
if (!improvementCanBePlacedEventually)
|
||||
startGoldenAgeIfHasAbility(unit)
|
||||
}
|
||||
|
||||
// TODO: The AI tends to have a lot of great generals. Maybe there should be a cutoff
|
||||
// (depending on number of cities) and after that they should just be used to start golden
|
||||
// ages?
|
||||
|
||||
return // The AI doesn't know how to handle unknown civilian units
|
||||
}
|
||||
|
||||
private fun isLateGame(civ: Civilization): Boolean {
|
||||
val researchCompletePercent =
|
||||
(civ.tech.researchedTechnologies.size * 1.0f) / civ.gameInfo.ruleset.technologies.size
|
||||
return researchCompletePercent >= 0.8f
|
||||
}
|
||||
|
||||
private fun startGoldenAgeIfHasAbility(unit: MapUnit) {
|
||||
UnitActions.getUnitActions(unit).filter { it.type == UnitActionType.StartGoldenAge }
|
||||
.firstOrNull()?.action!!.invoke()
|
||||
}
|
||||
|
||||
/** @return true only if the unit has 0 movement left */
|
||||
private fun tryAttacking(unit: MapUnit): Boolean {
|
||||
for (attackNumber in unit.attacksThisTurn until unit.maxAttacksPerTurn()) {
|
||||
|
@ -726,6 +726,8 @@ class UnitMovement(val unit: MapUnit) {
|
||||
if (!unitSpecificAllowOcean && unit.cache.cannotEnterOceanTiles) return false
|
||||
}
|
||||
|
||||
if (unit.hasUnique(UniqueType.CanTradeWithCityStateForGoldAndInfluence) && tile.getOwner()?.isCityState() == true)
|
||||
return true
|
||||
if (!unit.cache.canEnterForeignTerrain && !tile.canCivPassThrough(unit.civ)) return false
|
||||
|
||||
// The first unit is:
|
||||
|
Loading…
Reference in New Issue
Block a user