From 0279adadb93dece5207f22b1528dbb73e9ea436d Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Wed, 19 Jan 2022 14:16:33 +0200 Subject: [PATCH] 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 " 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 --- core/src/com/unciv/Constants.kt | 1 + .../logic/automation/WorkerAutomation.kt | 40 +++++++++++-------- core/src/com/unciv/logic/map/MapUnit.kt | 4 +- core/src/com/unciv/logic/map/TileInfo.kt | 2 +- .../ui/mapeditor/MapEditorOptionsTable.kt | 2 +- .../pickerscreens/ImprovementPickerScreen.kt | 4 +- core/src/com/unciv/ui/utils/ImageGetter.kt | 2 +- 7 files changed, 32 insertions(+), 23 deletions(-) diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index f6d7fa9bd4..1ac062ab71 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -63,6 +63,7 @@ object Constants { const val rising = "Rising" const val lowering = "Lowering" + const val remove = "Remove " const val minimumMovementEpsilon = 0.05 } diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index 2db84e9eb6..2c5e53fd53 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -11,7 +11,9 @@ import com.unciv.logic.map.BFS import com.unciv.logic.map.MapUnit import com.unciv.logic.map.RoadStatus 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.unique.UniqueType private object WorkerAutomationConst { /** 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 */ 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 val tileImprovements = ruleSet.tileImprovements.filter { @@ -346,8 +340,21 @@ class WorkerAutomation( val currentlyBuildableImprovements = tileImprovements.values.filter { tile.canBuildImprovement(it, civInfo) } val bestBuildableImprovement = currentlyBuildableImprovements.map { Pair(it, Automation.rankStatsValue(it, civInfo)) } - .filter { it.second > 0f } - .maxByOrNull { it.second }?.first + .filter { it.second > 0f } + .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 { tile.improvementInProgress != null -> tile.improvementInProgress!! @@ -357,14 +364,16 @@ class WorkerAutomation( // Defence is more important that civilian improvements // 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 uniqueImprovement != null && tile.canBuildImprovement(uniqueImprovement, civInfo) && unit.canBuildImprovement(uniqueImprovement, tile) -> uniqueImprovement.name - tile.terrainFeatures.contains("Fallout") -> "Remove Fallout" - tile.terrainFeatures.contains(Constants.marsh) -> "Remove Marsh" + lastTerrain.let { + 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("Oasis") -> return null 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) && 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 else -> return null } @@ -387,7 +393,7 @@ class WorkerAutomation( * * 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 ?: return false val resourceImprovement = ruleSet.tileImprovements[resourceImprovementName] diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 3863e8efcb..3791b74193 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -628,8 +628,8 @@ class MapUnit { UncivGame.Current.settings.addCompletedTutorialTask("Construct an improvement") when { - tile.improvementInProgress!!.startsWith("Remove ") -> { - val removedFeatureName = tile.improvementInProgress!!.removePrefix("Remove ") + tile.improvementInProgress!!.startsWith(Constants.remove) -> { + val removedFeatureName = tile.improvementInProgress!!.removePrefix(Constants.remove) val tileImprovement = tile.getTileImprovement() if (tileImprovement != null && tile.terrainFeatures.any { diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 343c54638f..f3a69f8544 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -487,7 +487,7 @@ open class TileInfo { && getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false // 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) .any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false diff --git a/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTable.kt b/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTable.kt index 437a6af549..85ac5d9c84 100644 --- a/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTable.kt +++ b/core/src/com/unciv/ui/mapeditor/MapEditorOptionsTable.kt @@ -131,7 +131,7 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(BaseScr }).row() 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 val improvementImage = getHex(ImageGetter.getImprovementIcon(improvement.name, 40f)) improvementImage.onClick { diff --git a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt index 05029ca5cf..b83b707ae1 100644 --- a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt @@ -84,7 +84,9 @@ class ImprovementPickerScreen(val tileInfo: TileInfo, unit: MapUnit, val onAccep val provideResource = tileInfo.hasViewableResource(currentPlayerCiv) && tileInfo.tileResource.improvement == improvement.name if (provideResource) labelText += "\n" + "Provides [${tileInfo.resource}]".tr() 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() val pickNow = if (tileInfo.improvementInProgress != improvement.name) diff --git a/core/src/com/unciv/ui/utils/ImageGetter.kt b/core/src/com/unciv/ui/utils/ImageGetter.kt index ff6356fb35..eafea4e421 100644 --- a/core/src/com/unciv/ui/utils/ImageGetter.kt +++ b/core/src/com/unciv/ui/utils/ImageGetter.kt @@ -261,7 +261,7 @@ object ImageGetter { 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) } val iconGroup = getImage("ImprovementIcons/$improvementName").surroundWithCircle(size)