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 lowering = "Lowering"
const val remove = "Remove "
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.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 {
@ -349,6 +343,19 @@ class WorkerAutomation(
.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!!
improvementStringForResource != null && tileImprovements.containsKey(improvementStringForResource) -> improvementStringForResource
@ -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]

View File

@ -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 {

View File

@ -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

View File

@ -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 {

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
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)

View File

@ -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)