Worker AI for mods, step 2 (#5998)

* Worker AI for mods, step 2

Support for removing terrain features, both when we need to get rid of them for a resource, and when they plain reduce the yield of a tile

Note that the "Remove <improvement>" was implemented WAY before templating and uniques were a thing - the correct solution in these enlightened times would be to separate the improvement *name* from the *effect*, so we could have e.g. a "Deforestation" improvement which would contain botth ["Removes [Jungle]", "Removes [Forest]"] or whatnot.

For now I Constant'D the "Remove " so we can at least follow where it's used.

* Reverted some of the auto-formatting so the PR is cleaner

* Caught 'nullify yields' unique for terrain removal
This commit is contained in:
Yair Morgenstern
2022-01-19 14:16:33 +02:00
committed by GitHub
parent 19927d89ba
commit 0279adadb9
7 changed files with 32 additions and 23 deletions

View File

@ -63,6 +63,7 @@ object Constants {
const val rising = "Rising" const val rising = "Rising"
const val lowering = "Lowering" const val lowering = "Lowering"
const val remove = "Remove "
const val minimumMovementEpsilon = 0.05 const val minimumMovementEpsilon = 0.05
} }

View File

@ -11,7 +11,9 @@ import com.unciv.logic.map.BFS
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
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.UniqueType
private object WorkerAutomationConst { private object WorkerAutomationConst {
/** Controls detailed logging of decisions to the console -Turn off for release builds! */ /** Controls detailed logging of decisions to the console -Turn off for release builds! */
@ -329,14 +331,6 @@ class WorkerAutomation(
* Determine the improvement appropriate to a given tile and worker * Determine the improvement appropriate to a given tile and worker
*/ */
private fun chooseImprovement(unit: MapUnit, tile: TileInfo): TileImprovement? { private fun chooseImprovement(unit: MapUnit, tile: TileInfo): TileImprovement? {
val improvementStringForResource: String? = when {
tile.resource == null || !tile.hasViewableResource(civInfo) -> null
tile.terrainFeatures.contains(Constants.marsh) && !isImprovementOnFeatureAllowed(tile) -> "Remove Marsh"
tile.terrainFeatures.contains("Fallout") && !isImprovementOnFeatureAllowed(tile) -> "Remove Fallout" // for really mad modders
tile.terrainFeatures.contains(Constants.jungle) && !isImprovementOnFeatureAllowed(tile) -> "Remove Jungle"
tile.terrainFeatures.contains(Constants.forest) && !isImprovementOnFeatureAllowed(tile) -> "Remove Forest"
else -> tile.tileResource.improvement
}
// turnsToBuild is what defines them as buildable // turnsToBuild is what defines them as buildable
val tileImprovements = ruleSet.tileImprovements.filter { val tileImprovements = ruleSet.tileImprovements.filter {
@ -349,6 +343,19 @@ class WorkerAutomation(
.filter { it.second > 0f } .filter { it.second > 0f }
.maxByOrNull { it.second }?.first .maxByOrNull { it.second }?.first
val lastTerrain = tile.getLastTerrain()
fun isUnbuildableAndRemovable(terrain: Terrain): Boolean = terrain.unbuildable
&& ruleSet.tileImprovements.containsKey(Constants.remove + terrain.name)
val improvementStringForResource: String? = when {
tile.resource == null || !tile.hasViewableResource(civInfo) -> null
tile.terrainFeatures.isNotEmpty()
&& isUnbuildableAndRemovable(lastTerrain)
&& !isResourceImprovementAllowedOnFeature(tile) -> Constants.remove + lastTerrain.name
else -> tile.tileResource.improvement
}
val improvementString = when { val improvementString = when {
tile.improvementInProgress != null -> tile.improvementInProgress!! tile.improvementInProgress != null -> tile.improvementInProgress!!
improvementStringForResource != null && tileImprovements.containsKey(improvementStringForResource) -> improvementStringForResource improvementStringForResource != null && tileImprovements.containsKey(improvementStringForResource) -> improvementStringForResource
@ -357,14 +364,16 @@ class WorkerAutomation(
// Defence is more important that civilian improvements // Defence is more important that civilian improvements
// While AI sucks in strategical placement of forts, allow a human does it manually // While AI sucks in strategical placement of forts, allow a human does it manually
!civInfo.isPlayerCivilization() && evaluateFortPlacement(tile, civInfo, false) -> Constants.fort !civInfo.isPlayerCivilization() && evaluateFortPlacement(tile, civInfo,false) -> Constants.fort
// I think we can assume that the unique improvement is better // I think we can assume that the unique improvement is better
uniqueImprovement != null && tile.canBuildImprovement(uniqueImprovement, civInfo) uniqueImprovement != null && tile.canBuildImprovement(uniqueImprovement, civInfo)
&& unit.canBuildImprovement(uniqueImprovement, tile) -> && unit.canBuildImprovement(uniqueImprovement, tile) ->
uniqueImprovement.name uniqueImprovement.name
tile.terrainFeatures.contains("Fallout") -> "Remove Fallout" lastTerrain.let {
tile.terrainFeatures.contains(Constants.marsh) -> "Remove Marsh" isUnbuildableAndRemovable(it) &&
(Automation.rankStatsValue(it, civInfo) < 0 || it.hasUnique(UniqueType.NullifyYields) )
} -> Constants.remove + lastTerrain.name
tile.terrainFeatures.contains(Constants.jungle) -> Constants.tradingPost tile.terrainFeatures.contains(Constants.jungle) -> Constants.tradingPost
tile.terrainFeatures.contains("Oasis") -> return null tile.terrainFeatures.contains("Oasis") -> return null
tile.terrainFeatures.contains(Constants.forest) && tileImprovements.containsKey("Lumber mill") -> "Lumber mill" tile.terrainFeatures.contains(Constants.forest) && tileImprovements.containsKey("Lumber mill") -> "Lumber mill"
@ -372,10 +381,7 @@ class WorkerAutomation(
tile.baseTerrain in listOf(Constants.grassland, Constants.desert, Constants.plains) tile.baseTerrain in listOf(Constants.grassland, Constants.desert, Constants.plains)
&& tileImprovements.containsKey("Farm") -> "Farm" && tileImprovements.containsKey("Farm") -> "Farm"
tile.isAdjacentToFreshwater && tileImprovements.containsKey("Farm") -> "Farm" tile.isAdjacentToFreshwater && tileImprovements.containsKey("Farm") -> "Farm"
tile.baseTerrain in listOf(Constants.tundra, Constants.snow) && tileImprovements.containsKey(Constants.tradingPost)
-> Constants.tradingPost
// This is the ONLY thing that will catch modded non-unique improvements
bestBuildableImprovement != null -> bestBuildableImprovement.name bestBuildableImprovement != null -> bestBuildableImprovement.name
else -> return null else -> return null
} }
@ -387,7 +393,7 @@ class WorkerAutomation(
* *
* Assumes the caller ensured that terrainFeature and resource are both present! * Assumes the caller ensured that terrainFeature and resource are both present!
*/ */
private fun isImprovementOnFeatureAllowed(tile: TileInfo): Boolean { private fun isResourceImprovementAllowedOnFeature(tile: TileInfo): Boolean {
val resourceImprovementName = tile.tileResource.improvement val resourceImprovementName = tile.tileResource.improvement
?: return false ?: return false
val resourceImprovement = ruleSet.tileImprovements[resourceImprovementName] val resourceImprovement = ruleSet.tileImprovements[resourceImprovementName]

View File

@ -628,8 +628,8 @@ class MapUnit {
UncivGame.Current.settings.addCompletedTutorialTask("Construct an improvement") UncivGame.Current.settings.addCompletedTutorialTask("Construct an improvement")
when { when {
tile.improvementInProgress!!.startsWith("Remove ") -> { tile.improvementInProgress!!.startsWith(Constants.remove) -> {
val removedFeatureName = tile.improvementInProgress!!.removePrefix("Remove ") val removedFeatureName = tile.improvementInProgress!!.removePrefix(Constants.remove)
val tileImprovement = tile.getTileImprovement() val tileImprovement = tile.getTileImprovement()
if (tileImprovement != null if (tileImprovement != null
&& tile.terrainFeatures.any { && tile.terrainFeatures.any {

View File

@ -487,7 +487,7 @@ open class TileInfo {
&& getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false && getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false
// Terrain blocks BUILDING improvements - removing things (such as fallout) is fine // Terrain blocks BUILDING improvements - removing things (such as fallout) is fine
!improvement.name.startsWith("Remove ") && !improvement.name.startsWith(Constants.remove) &&
getAllTerrains().any { it.getMatchingUniques(UniqueType.RestrictedBuildableImprovements) getAllTerrains().any { it.getMatchingUniques(UniqueType.RestrictedBuildableImprovements)
.any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false .any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false

View File

@ -131,7 +131,7 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(BaseScr
}).row() }).row()
for (improvement in ruleset.tileImprovements.values) { for (improvement in ruleset.tileImprovements.values) {
if (improvement.name.startsWith("Remove")) continue if (improvement.name.startsWith(Constants.remove)) continue
if (improvement.name == Constants.cancelImprovementOrder) continue if (improvement.name == Constants.cancelImprovementOrder) continue
val improvementImage = getHex(ImageGetter.getImprovementIcon(improvement.name, 40f)) val improvementImage = getHex(ImageGetter.getImprovementIcon(improvement.name, 40f))
improvementImage.onClick { improvementImage.onClick {

View File

@ -84,7 +84,9 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep
val provideResource = tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.tileResource.improvement == improvement.name val provideResource = tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.tileResource.improvement == improvement.name
if (provideResource) labelText += "\n" + "Provides [${tileInfo.resource}]".tr() if (provideResource) labelText += "\n" + "Provides [${tileInfo.resource}]".tr()
val removeImprovement = (improvement.name != RoadStatus.Road.name val removeImprovement = (improvement.name != RoadStatus.Road.name
&& improvement.name != RoadStatus.Railroad.name && !improvement.name.startsWith("Remove") && improvement.name != Constants.cancelImprovementOrder) && improvement.name != RoadStatus.Railroad.name
&& !improvement.name.startsWith(Constants.remove)
&& improvement.name != Constants.cancelImprovementOrder)
if (tileInfo.improvement != null && removeImprovement) labelText += "\n" + "Replaces [${tileInfo.improvement}]".tr() if (tileInfo.improvement != null && removeImprovement) labelText += "\n" + "Replaces [${tileInfo.improvement}]".tr()
val pickNow = if (tileInfo.improvementInProgress != improvement.name) val pickNow = if (tileInfo.improvementInProgress != improvement.name)

View File

@ -261,7 +261,7 @@ object ImageGetter {
fun getImprovementIcon(improvementName: String, size: Float = 20f): Actor { fun getImprovementIcon(improvementName: String, size: Float = 20f): Actor {
if (improvementName.startsWith("Remove") || improvementName == Constants.cancelImprovementOrder) if (improvementName.startsWith(Constants.remove) || improvementName == Constants.cancelImprovementOrder)
return Table().apply { add(getImage("OtherIcons/Stop")).size(size) } return Table().apply { add(getImage("OtherIcons/Stop")).size(size) }
val iconGroup = getImage("ImprovementIcons/$improvementName").surroundWithCircle(size) val iconGroup = getImage("ImprovementIcons/$improvementName").surroundWithCircle(size)