diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index 9e3d5000dd..022dd62a10 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -246,7 +246,7 @@ object Automation { if (!construction.hasCreateOneImprovementUnique()) return true // redundant but faster??? val improvement = construction.getImprovementToCreate(cityInfo.getRuleset()) ?: return true return cityInfo.getTiles().any { - it.canBuildImprovement(improvement, civInfo) + it.improvementFunctions.canBuildImprovement(improvement, civInfo) } } @@ -338,7 +338,7 @@ object Automation { /** Support [UniqueType.CreatesOneImprovement] unique - find best tile for placement automation */ fun getTileForConstructionImprovement(cityInfo: CityInfo, improvement: TileImprovement): TileInfo? { return cityInfo.getTiles().filter { - it.canBuildImprovement(improvement, cityInfo.civInfo) + it.improvementFunctions.canBuildImprovement(improvement, cityInfo.civInfo) }.maxByOrNull { rankTileForCityWork(it, cityInfo, cityInfo.cityStats.currentCityStats) } diff --git a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt index e23997ab0d..357da577b8 100644 --- a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt @@ -154,7 +154,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ .any { tile -> tile.hasViewableResource(civInfo) && tile.improvement == null && tile.getOwner() == civInfo && tile.tileResource.getImprovements().any { - tile.canBuildImprovement(tile.ruleset.tileImprovements[it]!!, civInfo) + tile.improvementFunctions.canBuildImprovement(tile.ruleset.tileImprovements[it]!!, civInfo) } } ) return diff --git a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt index a0f2dbc236..768e062610 100644 --- a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt @@ -263,7 +263,7 @@ object SpecificUnitAutomation { val applicableTiles = city.getWorkableTiles().filter { it.isLand && it.resource == null && !it.isCityCenter() && (unit.currentTile == it || unit.movement.canMoveTo(it)) - && !it.containsGreatImprovement() && it.canBuildImprovement(improvement, unit.civInfo) + && !it.containsGreatImprovement() && it.improvementFunctions.canBuildImprovement(improvement, unit.civInfo) } if (applicableTiles.none()) continue diff --git a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt index ed610616d2..000c7e693b 100644 --- a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt @@ -331,11 +331,11 @@ class WorkerAutomation( if (tile.improvement == null || junkImprovement == true) { if (tile.improvementInProgress != null && unit.canBuildImprovement(tile.getTileImprovementInProgress()!!, tile)) return true val chosenImprovement = chooseImprovement(unit, tile) - if (chosenImprovement != null && tile.canBuildImprovement(chosenImprovement, civInfo) && unit.canBuildImprovement(chosenImprovement, tile)) return true + if (chosenImprovement != null && tile.improvementFunctions.canBuildImprovement(chosenImprovement, civInfo) && unit.canBuildImprovement(chosenImprovement, tile)) return true } else if (!tile.containsGreatImprovement() && tile.hasViewableResource(civInfo) && tile.tileResource.isImprovedBy(tile.improvement!!) && (chooseImprovement(unit, tile) // if the chosen improvement is not null and buildable - .let { it != null && tile.canBuildImprovement(it, civInfo) && unit.canBuildImprovement(it, tile)})) + .let { it != null && tile.improvementFunctions.canBuildImprovement(it, civInfo) && unit.canBuildImprovement(it, tile)})) return true return false // couldn't find anything to construct here } @@ -365,7 +365,7 @@ class WorkerAutomation( val potentialTileImprovements = ruleSet.tileImprovements.filter { unit.canBuildImprovement(it.value, tile) - && tile.canBuildImprovement(it.value, civInfo) + && tile.improvementFunctions.canBuildImprovement(it.value, civInfo) && (it.value.uniqueTo == null || it.value.uniqueTo == unit.civInfo.civName) } if (potentialTileImprovements.isEmpty()) return null @@ -405,7 +405,7 @@ class WorkerAutomation( // While AI sucks in strategical placement of forts, allow a human does it manually !civInfo.isHuman() && evaluateFortPlacement(tile, civInfo,false) -> Constants.fort // I think we can assume that the unique improvement is better - uniqueImprovement != null && tile.canBuildImprovement(uniqueImprovement, civInfo) + uniqueImprovement != null && tile.improvementFunctions.canBuildImprovement(uniqueImprovement, civInfo) -> uniqueImprovement.name lastTerrain.let { diff --git a/core/src/com/unciv/logic/map/tile/TileInfo.kt b/core/src/com/unciv/logic/map/tile/TileInfo.kt index d8846b680c..c5807b02d9 100644 --- a/core/src/com/unciv/logic/map/tile/TileInfo.kt +++ b/core/src/com/unciv/logic/map/tile/TileInfo.kt @@ -9,8 +9,8 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.PlayerType import com.unciv.logic.map.HexMath import com.unciv.logic.map.MapResources -import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.TileMap +import com.unciv.logic.map.mapunit.MapUnit import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.Terrain @@ -39,6 +39,9 @@ open class TileInfo : IsPartOfGameInfoSerialization { @Transient lateinit var ruleset: Ruleset // a tile can be a tile with a ruleset, even without a map. + @Transient + public val improvementFunctions = TileInfoImprovementFunctions(this) + @Transient private var isCityCenterInternal = false @@ -749,157 +752,6 @@ open class TileInfo : IsPartOfGameInfoSerialization { return (neighbors + this).any { neighbor -> neighbor.matchesFilter(terrainFilter) } } - /** Returns true if the [improvement] can be built on this [TileInfo] */ - fun canBuildImprovement(improvement: TileImprovement, civInfo: CivilizationInfo): Boolean = getImprovementBuildingProblems(improvement, civInfo).none() - - enum class ImprovementBuildingProblem { - WrongCiv, MissingTech, Unbuildable, NotJustOutsideBorders, OutsideBorders, UnmetConditional, Obsolete, MissingResources, Other - } - - /** Generates a sequence of reasons that prevent building given [improvement]. - * If the sequence is empty, improvement can be built immediately. - */ - fun getImprovementBuildingProblems(improvement: TileImprovement, civInfo: CivilizationInfo): Sequence = sequence { - val stateForConditionals = StateForConditionals(civInfo, tile = this@TileInfo) - - if (improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName) - yield(ImprovementBuildingProblem.WrongCiv) - if (improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!)) - yield(ImprovementBuildingProblem.MissingTech) - if (improvement.hasUnique(UniqueType.Unbuildable, stateForConditionals)) - yield(ImprovementBuildingProblem.Unbuildable) - - if (getOwner() != civInfo && !improvement.hasUnique(UniqueType.CanBuildOutsideBorders, stateForConditionals)) { - if (!improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders, stateForConditionals)) - yield(ImprovementBuildingProblem.OutsideBorders) - else if (neighbors.none { it.getOwner() == civInfo }) - yield(ImprovementBuildingProblem.NotJustOutsideBorders) - } - - if (improvement.getMatchingUniques(UniqueType.OnlyAvailableWhen, StateForConditionals.IgnoreConditionals).any { - !it.conditionalsApply(stateForConditionals) - }) - yield(ImprovementBuildingProblem.UnmetConditional) - - if (improvement.getMatchingUniques(UniqueType.ObsoleteWith, stateForConditionals).any { - civInfo.tech.isResearched(it.params[0]) - }) - yield(ImprovementBuildingProblem.Obsolete) - - if (improvement.getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals).any { - civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() - }) - yield(ImprovementBuildingProblem.MissingResources) - - val knownFeatureRemovals = ruleset.tileImprovements.values - .filter { rulesetImprovement -> - rulesetImprovement.name.startsWith(Constants.remove) - && RoadStatus.values().none { it.removeAction == rulesetImprovement.name } - && (rulesetImprovement.techRequired == null || civInfo.tech.isResearched(rulesetImprovement.techRequired!!)) - } - - if (!canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo), knownFeatureRemovals, stateForConditionals)) - // There are way too many conditions in that functions, besides, they are not interesting - // at least for the current usecases. Improve if really needed. - yield(ImprovementBuildingProblem.Other) - } - - /** Without regards to what CivInfo it is, a lot of the checks are just for the improvement on the tile. - * Doubles as a check for the map editor. - */ - private fun canImprovementBeBuiltHere( - improvement: TileImprovement, - resourceIsVisible: Boolean = resource != null, - knownFeatureRemovals: List? = null, - stateForConditionals: StateForConditionals = StateForConditionals(tile=this) - ): Boolean { - - fun TileImprovement.canBeBuildOnThisUnbuildableTerrain( - knownFeatureRemovals: List? = null, - ): Boolean { - val topTerrain = getLastTerrain() - // We can build if we are specifically allowed to build on this terrain - if (isAllowedOnFeature(topTerrain.name)) return true - - // Otherwise, we can if this improvement removes the top terrain - if (!hasUnique(UniqueType.RemovesFeaturesIfBuilt, stateForConditionals)) return false - val removeAction = ruleset.tileImprovements[Constants.remove + topTerrain.name] ?: return false - // and we have the tech to remove that top terrain - if (removeAction.techRequired != null && (knownFeatureRemovals == null || removeAction !in knownFeatureRemovals)) return false - // and we can build it on the tile without the top terrain - val clonedTile = this@TileInfo.clone() - clonedTile.removeTerrainFeature(topTerrain.name) - return clonedTile.canImprovementBeBuiltHere(improvement, resourceIsVisible, knownFeatureRemovals, stateForConditionals) - } - - return when { - improvement.name == this.improvement -> false - isCityCenter() -> false - - // First we handle a few special improvements - - // Can only cancel if there is actually an improvement being built - improvement.name == Constants.cancelImprovementOrder -> (this.improvementInProgress != null) - // Can only remove roads if that road is actually there - RoadStatus.values().any { it.removeAction == improvement.name } -> roadStatus.removeAction == improvement.name - // Can only remove features if that feature is actually there - improvement.name.startsWith(Constants.remove) -> terrainFeatures.any { it == improvement.name.removePrefix(Constants.remove) } - // Can only build roads if on land and they are better than the current road - RoadStatus.values().any { it.name == improvement.name } -> !isWater && RoadStatus.valueOf(improvement.name) > roadStatus - - // Then we check if there is any reason to not allow this improvement to be build - - // Can't build if there is already an irremovable improvement here - this.improvement != null && getTileImprovement()!!.hasUnique(UniqueType.Irremovable, stateForConditionals) -> false - - // Can't build if this terrain is unbuildable, except when we are specifically allowed to - getLastTerrain().unbuildable && !improvement.canBeBuildOnThisUnbuildableTerrain(knownFeatureRemovals) -> false - - // Can't build if any terrain specifically prevents building this improvement - getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any { - unique -> !improvement.matchesFilter(unique.params[0]) - } -> false - - // Can't build if the improvement specifically prevents building on some present feature - improvement.getMatchingUniques(UniqueType.CannotBuildOnTile, stateForConditionals).any { - unique -> matchesTerrainFilter(unique.params[0]) - } -> false - - // Can't build if an improvement is only allowed to be built on specific tiles and this is not one of them - // If multiple uniques of this type exists, we want all to match (e.g. Hill _and_ Forest would be meaningful) - improvement.getMatchingUniques(UniqueType.CanOnlyBeBuiltOnTile, stateForConditionals).let { - it.any() && it.any { unique -> !matchesTerrainFilter(unique.params[0]) } - } -> false - - // Can't build if the improvement requires an adjacent terrain that is not present - improvement.getMatchingUniques(UniqueType.MustBeNextTo, stateForConditionals).any { - !isAdjacentTo(it.params[0]) - } -> false - - // Can't build it if it is only allowed to improve resources and it doesn't improve this resource - improvement.hasUnique(UniqueType.CanOnlyImproveResource, stateForConditionals) && ( - !resourceIsVisible || !tileResource.isImprovedBy(improvement.name) - ) -> false - - // At this point we know this is a normal improvement and that there is no reason not to allow it to be built. - - // Lastly we check if the improvement may be built on this terrain or resource - improvement.canBeBuiltOn(getLastTerrain().name) -> true - isLand && improvement.canBeBuiltOn("Land") -> true - isWater && improvement.canBeBuiltOn("Water") -> true - // DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests. - improvement.hasUnique(UniqueType.ImprovementBuildableByFreshWater, stateForConditionals) - && isAdjacentTo(Constants.freshWater) -> true - - // I don't particularly like this check, but it is required to build mines on non-hill resources - resourceIsVisible && tileResource.isImprovedBy(improvement.name) -> true - // DEPRECATED since 4.0.14, REMOVE SOON: - isLand && improvement.terrainsCanBeBuiltOn.isEmpty() && !improvement.hasUnique(UniqueType.CanOnlyImproveResource) -> true - // No reason this improvement should be built here, so can't build it - else -> false - } - } - /** Implements [UniqueParameterType.TileFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TileFilter] */ fun matchesFilter(filter: String, civInfo: CivilizationInfo? = null): Boolean { if (matchesTerrainFilter(filter, civInfo)) return true @@ -1098,7 +950,7 @@ open class TileInfo : IsPartOfGameInfoSerialization { else FormattedLine(resource!!, link="Resource/$resource") if (resource != null && viewingCiv != null && hasViewableResource(viewingCiv)) { - val resourceImprovement = tileResource.getImprovements().firstOrNull { canBuildImprovement(ruleset.tileImprovements[it]!!, viewingCiv) } + val resourceImprovement = tileResource.getImprovements().firstOrNull { improvementFunctions.canBuildImprovement(ruleset.tileImprovements[it]!!, viewingCiv) } val tileImprovement = ruleset.tileImprovements[resourceImprovement] if (tileImprovement?.techRequired != null && !viewingCiv.tech.isResearched(tileImprovement.techRequired!!)) { @@ -1428,7 +1280,7 @@ open class TileInfo : IsPartOfGameInfoSerialization { return } changeImprovement(null) // Unset, and check if it can be reset. If so, do it, if not, invalid. - if (canImprovementBeBuiltHere(improvementObject, stateForConditionals = StateForConditionals.IgnoreConditionals)) + if (improvementFunctions.canImprovementBeBuiltHere(improvementObject, stateForConditionals = StateForConditionals.IgnoreConditionals)) changeImprovement(improvementObject.name) } diff --git a/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt new file mode 100644 index 0000000000..de01047b43 --- /dev/null +++ b/core/src/com/unciv/logic/map/tile/TileInfoImprovementFunctions.kt @@ -0,0 +1,165 @@ +package com.unciv.logic.map.tile + +import com.unciv.Constants +import com.unciv.logic.civilization.CivilizationInfo +import com.unciv.models.ruleset.tile.TileImprovement +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.UniqueType + + +enum class ImprovementBuildingProblem { + WrongCiv, MissingTech, Unbuildable, NotJustOutsideBorders, OutsideBorders, UnmetConditional, Obsolete, MissingResources, Other +} + +class TileInfoImprovementFunctions(val tileInfo: TileInfo) { + + /** Returns true if the [improvement] can be built on this [TileInfo] */ + fun canBuildImprovement(improvement: TileImprovement, civInfo: CivilizationInfo): Boolean = getImprovementBuildingProblems(improvement, civInfo).none() + + /** Generates a sequence of reasons that prevent building given [improvement]. + * If the sequence is empty, improvement can be built immediately. + */ + fun getImprovementBuildingProblems(improvement: TileImprovement, civInfo: CivilizationInfo): Sequence = sequence { + val stateForConditionals = StateForConditionals(civInfo, tile = tileInfo) + + if (improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName) + yield(ImprovementBuildingProblem.WrongCiv) + if (improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!)) + yield(ImprovementBuildingProblem.MissingTech) + if (improvement.hasUnique(UniqueType.Unbuildable, stateForConditionals)) + yield(ImprovementBuildingProblem.Unbuildable) + + if (tileInfo.getOwner() != civInfo && !improvement.hasUnique(UniqueType.CanBuildOutsideBorders, stateForConditionals)) { + if (!improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders, stateForConditionals)) + yield(ImprovementBuildingProblem.OutsideBorders) + else if (tileInfo.neighbors.none { it.getOwner() == civInfo }) + yield(ImprovementBuildingProblem.NotJustOutsideBorders) + } + + if (improvement.getMatchingUniques(UniqueType.OnlyAvailableWhen, StateForConditionals.IgnoreConditionals).any { + !it.conditionalsApply(stateForConditionals) + }) + yield(ImprovementBuildingProblem.UnmetConditional) + + if (improvement.getMatchingUniques(UniqueType.ObsoleteWith, stateForConditionals).any { + civInfo.tech.isResearched(it.params[0]) + }) + yield(ImprovementBuildingProblem.Obsolete) + + if (improvement.getMatchingUniques(UniqueType.ConsumesResources, stateForConditionals).any { + civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() + }) + yield(ImprovementBuildingProblem.MissingResources) + + val knownFeatureRemovals = tileInfo.ruleset.tileImprovements.values + .filter { rulesetImprovement -> + rulesetImprovement.name.startsWith(Constants.remove) + && RoadStatus.values().none { it.removeAction == rulesetImprovement.name } + && (rulesetImprovement.techRequired == null || civInfo.tech.isResearched(rulesetImprovement.techRequired!!)) + } + + if (!canImprovementBeBuiltHere(improvement, tileInfo.hasViewableResource(civInfo), knownFeatureRemovals, stateForConditionals)) + // There are way too many conditions in that functions, besides, they are not interesting + // at least for the current usecases. Improve if really needed. + yield(ImprovementBuildingProblem.Other) + } + + /** Without regards to what CivInfo it is, a lot of the checks are just for the improvement on the tile. + * Doubles as a check for the map editor. + */ + internal fun canImprovementBeBuiltHere( + improvement: TileImprovement, + resourceIsVisible: Boolean = tileInfo.resource != null, + knownFeatureRemovals: List? = null, + stateForConditionals: StateForConditionals = StateForConditionals(tile=tileInfo) + ): Boolean { + + fun TileImprovement.canBeBuildOnThisUnbuildableTerrain( + knownFeatureRemovals: List? = null, + ): Boolean { + val topTerrain = tileInfo.getLastTerrain() + // We can build if we are specifically allowed to build on this terrain + if (isAllowedOnFeature(topTerrain.name)) return true + + // Otherwise, we can if this improvement removes the top terrain + if (!hasUnique(UniqueType.RemovesFeaturesIfBuilt, stateForConditionals)) return false + val removeAction = tileInfo.ruleset.tileImprovements[Constants.remove + topTerrain.name] ?: return false + // and we have the tech to remove that top terrain + if (removeAction.techRequired != null && (knownFeatureRemovals == null || removeAction !in knownFeatureRemovals)) return false + // and we can build it on the tile without the top terrain + val clonedTile = tileInfo.clone() + clonedTile.removeTerrainFeature(topTerrain.name) + return clonedTile.improvementFunctions.canImprovementBeBuiltHere(improvement, resourceIsVisible, knownFeatureRemovals, stateForConditionals) + } + + return when { + improvement.name == tileInfo.improvement -> false + tileInfo.isCityCenter() -> false + + // First we handle a few special improvements + + // Can only cancel if there is actually an improvement being built + improvement.name == Constants.cancelImprovementOrder -> (tileInfo.improvementInProgress != null) + // Can only remove roads if that road is actually there + RoadStatus.values().any { it.removeAction == improvement.name } -> tileInfo.roadStatus.removeAction == improvement.name + // Can only remove features if that feature is actually there + improvement.name.startsWith(Constants.remove) -> tileInfo.terrainFeatures.any { it == improvement.name.removePrefix( + Constants.remove) } + // Can only build roads if on land and they are better than the current road + RoadStatus.values().any { it.name == improvement.name } -> !tileInfo.isWater + && RoadStatus.valueOf(improvement.name) > tileInfo.roadStatus + + // Then we check if there is any reason to not allow this improvement to be build + + // Can't build if there is already an irremovable improvement here + tileInfo.improvement != null && tileInfo.getTileImprovement()!!.hasUnique(UniqueType.Irremovable, stateForConditionals) -> false + + // Can't build if this terrain is unbuildable, except when we are specifically allowed to + tileInfo.getLastTerrain().unbuildable && !improvement.canBeBuildOnThisUnbuildableTerrain(knownFeatureRemovals) -> false + + // Can't build if any terrain specifically prevents building this improvement + tileInfo.getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any { + unique -> !improvement.matchesFilter(unique.params[0]) + } -> false + + // Can't build if the improvement specifically prevents building on some present feature + improvement.getMatchingUniques(UniqueType.CannotBuildOnTile, stateForConditionals).any { + unique -> tileInfo.matchesTerrainFilter(unique.params[0]) + } -> false + + // Can't build if an improvement is only allowed to be built on specific tiles and this is not one of them + // If multiple uniques of this type exists, we want all to match (e.g. Hill _and_ Forest would be meaningful) + improvement.getMatchingUniques(UniqueType.CanOnlyBeBuiltOnTile, stateForConditionals).let { + it.any() && it.any { unique -> !tileInfo.matchesTerrainFilter(unique.params[0]) } + } -> false + + // Can't build if the improvement requires an adjacent terrain that is not present + improvement.getMatchingUniques(UniqueType.MustBeNextTo, stateForConditionals).any { + !tileInfo.isAdjacentTo(it.params[0]) + } -> false + + // Can't build it if it is only allowed to improve resources and it doesn't improve this resource + improvement.hasUnique(UniqueType.CanOnlyImproveResource, stateForConditionals) && ( + !resourceIsVisible || !tileInfo.tileResource.isImprovedBy(improvement.name) + ) -> false + + // At this point we know this is a normal improvement and that there is no reason not to allow it to be built. + + // Lastly we check if the improvement may be built on this terrain or resource + improvement.canBeBuiltOn(tileInfo.getLastTerrain().name) -> true + tileInfo.isLand && improvement.canBeBuiltOn("Land") -> true + tileInfo.isWater && improvement.canBeBuiltOn("Water") -> true + // DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests. + improvement.hasUnique(UniqueType.ImprovementBuildableByFreshWater, stateForConditionals) + && tileInfo.isAdjacentTo(Constants.freshWater) -> true + + // I don't particularly like this check, but it is required to build mines on non-hill resources + resourceIsVisible && tileInfo.tileResource.isImprovedBy(improvement.name) -> true + // DEPRECATED since 4.0.14, REMOVE SOON: + tileInfo.isLand && improvement.terrainsCanBeBuiltOn.isEmpty() && !improvement.hasUnique( + UniqueType.CanOnlyImproveResource) -> true + // No reason this improvement should be built here, so can't build it + else -> false + } + } +} diff --git a/core/src/com/unciv/models/UnitAction.kt b/core/src/com/unciv/models/UnitAction.kt index 0757535588..99be69e046 100644 --- a/core/src/com/unciv/models/UnitAction.kt +++ b/core/src/com/unciv/models/UnitAction.kt @@ -1,14 +1,11 @@ package com.unciv.models import com.badlogic.gdx.Input -import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.Actor import com.unciv.Constants import com.unciv.models.translations.getPlaceholderParameters import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.extensions.surroundWithCircle -import com.unciv.ui.utils.extensions.surroundWithThinCircle /** Unit Actions - class - carries dynamic data and actual execution. diff --git a/core/src/com/unciv/models/ruleset/tile/TileResource.kt b/core/src/com/unciv/models/ruleset/tile/TileResource.kt index 09ef25b90e..56ab252948 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileResource.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileResource.kt @@ -131,7 +131,7 @@ class TileResource : RulesetStatsObject() { fun getImprovingImprovement(tile: TileInfo, civInfo: CivilizationInfo): String? { return getImprovements().firstOrNull { - tile.canBuildImprovement(civInfo.gameInfo.ruleSet.tileImprovements[it]!!, civInfo) + tile.improvementFunctions.canBuildImprovement(civInfo.gameInfo.ruleSet.tileImprovements[it]!!, civInfo) } } diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index c61e40e9f8..3f95214ce9 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -215,7 +215,7 @@ class CityScreen( val improvementToPlace = pickTileData!!.improvement return when { tileInfo.isMarkedForCreatesOneImprovement() -> Color.BROWN to 0.7f - !tileInfo.canBuildImprovement(improvementToPlace, city.civInfo) -> Color.RED to 0.4f + !tileInfo.improvementFunctions.canBuildImprovement(improvementToPlace, city.civInfo) -> Color.RED to 0.4f isExistingImprovementValuable(tileInfo, improvementToPlace) -> Color.ORANGE to 0.5f tileInfo.improvement != null -> Color.YELLOW to 0.6f tileInfo.turnsToImprovement > 0 -> Color.YELLOW to 0.6f @@ -337,7 +337,7 @@ class CityScreen( val pickTileData = this.pickTileData!! this.pickTileData = null val improvement = pickTileData.improvement - if (tileInfo.canBuildImprovement(improvement, cityInfo.civInfo)) { + if (tileInfo.improvementFunctions.canBuildImprovement(improvement, cityInfo.civInfo)) { if (pickTileData.isBuying) { constructionsTable.askToBuyConstruction(pickTileData.building, pickTileData.buyStat, tileInfo) } else { diff --git a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt index 0d67815868..275b7eab45 100644 --- a/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ImprovementPickerScreen.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.logic.map.mapunit.MapUnit +import com.unciv.logic.map.tile.ImprovementBuildingProblem import com.unciv.logic.map.tile.TileInfo import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.LocalUniqueCache @@ -29,13 +30,14 @@ class ImprovementPickerScreen( companion object { /** Set of resolvable improvement building problems that this class knows how to report. */ private val reportableProblems = setOf( - TileInfo.ImprovementBuildingProblem.MissingTech, - TileInfo.ImprovementBuildingProblem.NotJustOutsideBorders, - TileInfo.ImprovementBuildingProblem.OutsideBorders, - TileInfo.ImprovementBuildingProblem.MissingResources) + ImprovementBuildingProblem.MissingTech, + ImprovementBuildingProblem.NotJustOutsideBorders, + ImprovementBuildingProblem.OutsideBorders, + ImprovementBuildingProblem.MissingResources + ) /** Return true if we can report improvements associated with the [problems] (or there are no problems for it at all). */ - fun canReport(problems: Collection) = problems.all { it in reportableProblems } + fun canReport(problems: Collection) = problems.all { it in reportableProblems } } private var selectedImprovement: TileImprovement? = null @@ -89,10 +91,10 @@ class ImprovementPickerScreen( if (improvement.name == tileInfo.improvement) continue // also checked by canImprovementBeBuiltHere, but after more expensive tests if (!unit.canBuildImprovement(improvement)) continue - var unbuildableBecause = tileInfo.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet() + var unbuildableBecause = tileInfo.improvementFunctions.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet() if (!canReport(unbuildableBecause)) { // Try after pretending to have removed the top terrain layer. - unbuildableBecause = tileInfoWithoutLastTerrain.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet() + unbuildableBecause = tileInfoWithoutLastTerrain.improvementFunctions.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet() if (!canReport(unbuildableBecause)) continue else suggestRemoval = true } @@ -107,7 +109,7 @@ class ImprovementPickerScreen( // *other* improvements with same shortcutKey .filter { it.shortcutKey == improvement.shortcutKey && it != improvement } // civ can build it (checks tech researched) - .filter { tileInfo.canBuildImprovement(it, currentPlayerCiv) } + .filter { tileInfo.improvementFunctions.canBuildImprovement(it, currentPlayerCiv) } // is technologically more advanced .filter { getRequiredTechColumn(it) > techLevel } .any() @@ -131,13 +133,13 @@ class ImprovementPickerScreen( if (suggestRemoval) proposedSolutions.add("${Constants.remove}[${tileInfo.getLastTerrain().name}] first") - if (TileInfo.ImprovementBuildingProblem.MissingTech in unbuildableBecause) + if (ImprovementBuildingProblem.MissingTech in unbuildableBecause) proposedSolutions.add("Research [${improvement.techRequired}] first") - if (TileInfo.ImprovementBuildingProblem.NotJustOutsideBorders in unbuildableBecause) + if (ImprovementBuildingProblem.NotJustOutsideBorders in unbuildableBecause) proposedSolutions.add("Have this tile close to your borders") - if (TileInfo.ImprovementBuildingProblem.OutsideBorders in unbuildableBecause) + if (ImprovementBuildingProblem.OutsideBorders in unbuildableBecause) proposedSolutions.add("Have this tile inside your empire") - if (TileInfo.ImprovementBuildingProblem.MissingResources in unbuildableBecause) { + if (ImprovementBuildingProblem.MissingResources in unbuildableBecause) { proposedSolutions.addAll(improvement.getMatchingUniques(UniqueType.ConsumesResources).filter { currentPlayerCiv.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() }.map { "Acquire more [$it]" }) diff --git a/core/src/com/unciv/ui/pickerscreens/UnitRenamePopup.kt b/core/src/com/unciv/ui/pickerscreens/UnitRenamePopup.kt index 5ae7f08900..1889ddb618 100644 --- a/core/src/com/unciv/ui/pickerscreens/UnitRenamePopup.kt +++ b/core/src/com/unciv/ui/pickerscreens/UnitRenamePopup.kt @@ -1,25 +1,11 @@ package com.unciv.ui.pickerscreens -import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.ui.Table -import com.badlogic.gdx.utils.Align -import com.unciv.UncivGame import com.unciv.logic.map.mapunit.MapUnit -import com.unciv.models.TutorialTrigger -import com.unciv.models.UncivSound -import com.unciv.models.ruleset.unique.UniqueType -import com.unciv.models.ruleset.unit.Promotion import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.popup.AskTextPopup import com.unciv.ui.utils.BaseScreen -import com.unciv.ui.utils.KeyCharAndCode -import com.unciv.ui.utils.RecreateOnResize -import com.unciv.ui.utils.extensions.isEnabled -import com.unciv.ui.utils.extensions.onClick import com.unciv.ui.utils.extensions.surroundWithCircle -import com.unciv.ui.utils.extensions.toLabel -import com.unciv.ui.utils.extensions.toTextButton class UnitRenamePopup(val screen: BaseScreen, val unit: MapUnit, val actionOnClose: ()->Unit) { init { diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 8e4b6455ba..8ac3455ddf 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -433,7 +433,7 @@ class DiplomacyScreen( for (improvableTile in improvableResourceTiles) for (tileImprovement in improvements.values) if (improvableTile.tileResource.isImprovedBy(tileImprovement.name) - && improvableTile.canBuildImprovement(tileImprovement, otherCiv) + && improvableTile.improvementFunctions.canBuildImprovement(tileImprovement, otherCiv) ) needsImprovements = true @@ -495,7 +495,7 @@ class DiplomacyScreen( return diplomacyTable } - fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital()!!.getTiles().filter { + private fun getImprovableResourceTiles(otherCiv:CivilizationInfo) = otherCiv.getCapital()!!.getTiles().filter { it.hasViewableResource(otherCiv) && it.tileResource.resourceType != ResourceType.Bonus && (it.improvement == null || !it.tileResource.isImprovedBy(it.improvement!!)) @@ -512,7 +512,7 @@ class DiplomacyScreen( for (improvableTile in improvableResourceTiles) { for (tileImprovement in tileImprovements.values) { if (improvableTile.tileResource.isImprovedBy(tileImprovement.name) - && improvableTile.canBuildImprovement(tileImprovement, otherCiv) + && improvableTile.improvementFunctions.canBuildImprovement(tileImprovement, otherCiv) ) { val improveTileButton = "Build [${tileImprovement}] on [${improvableTile.tileResource}] (200 Gold)".toTextButton() diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 59e7e43808..12a3989b6a 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -150,7 +150,7 @@ object UnitActions { val improvementName = tile.tileResource.getImprovingImprovement(tile, unit.civInfo) ?: return null val improvement = tile.ruleset.tileImprovements[improvementName] ?: return null - if (!tile.canBuildImprovement(improvement, unit.civInfo)) return null + if (!tile.improvementFunctions.canBuildImprovement(improvement, unit.civInfo)) return null return UnitAction(UnitActionType.Create, "Create [$improvementName]", action = { @@ -544,7 +544,7 @@ object UnitActions { val couldConstruct = unit.currentMovement > 0 && !tile.isCityCenter() && unit.civInfo.gameInfo.ruleSet.tileImprovements.values.any { - ImprovementPickerScreen.canReport(tile.getImprovementBuildingProblems(it, unit.civInfo).toSet()) + ImprovementPickerScreen.canReport(tile.improvementFunctions.getImprovementBuildingProblems(it, unit.civInfo).toSet()) && unit.canBuildImprovement(it) } @@ -841,7 +841,7 @@ object UnitActions { }.takeIf { resourcesAvailable && unit.currentMovement > 0f - && tile.canBuildImprovement(improvement, unit.civInfo) + && tile.improvementFunctions.canBuildImprovement(improvement, unit.civInfo) // Next test is to prevent interfering with UniqueType.CreatesOneImprovement - // not pretty, but users *can* remove the building from the city queue an thus clear this: && !tile.isMarkedForCreatesOneImprovement() diff --git a/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt b/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt index 576c162df3..2a5aaeabce 100644 --- a/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt +++ b/tests/src/com/unciv/logic/map/TileImprovementConstructionTests.kt @@ -63,7 +63,7 @@ class TileImprovementConstructionTests { tile.setTransients() if (improvement.uniqueTo != null) civInfo.civName = improvement.uniqueTo!! - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertTrue(improvement.name, canBeBuilt) } } @@ -80,7 +80,7 @@ class TileImprovementConstructionTests { if (improvement.hasUnique(UniqueType.CanOnlyBeBuiltOnTile, StateForConditionals.IgnoreConditionals)) continue tile.setTransients() - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertTrue(improvement.name, canBeBuilt) } } @@ -101,7 +101,7 @@ class TileImprovementConstructionTests { for (improvement in ruleSet.tileImprovements.values) { if (!improvement.uniques.contains("Can only be built on [Coastal] tiles")) continue civInfo.civName = improvement.uniqueTo ?: "OtherCiv" - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertTrue(improvement.name, canBeBuilt) } } @@ -114,7 +114,7 @@ class TileImprovementConstructionTests { for (improvement in ruleSet.tileImprovements.values) { if (!improvement.uniques.contains("Can only be built on [Coastal] tiles")) continue civInfo.civName = improvement.uniqueTo ?: "OtherCiv" - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertFalse(improvement.name, canBeBuilt) } } @@ -126,7 +126,7 @@ class TileImprovementConstructionTests { civInfo.civName = "OtherCiv" val tile = getTile() tile.setTransients() - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertFalse(improvement.name, canBeBuilt) } } @@ -146,7 +146,7 @@ class TileImprovementConstructionTests { tile.baseTerrain = "Plains" tile.resource = wrongResource.name tile.setTransients() - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertFalse(improvement.name, canBeBuilt) } } @@ -161,7 +161,7 @@ class TileImprovementConstructionTests { for (improvement in ruleSet.tileImprovements.values) { if (!improvement.uniques.contains("Cannot be built on [Bonus resource] tiles")) continue - val canBeBuilt = tile.canBuildImprovement(improvement, civInfo) + val canBeBuilt = tile.improvementFunctions.canBuildImprovement(improvement, civInfo) Assert.assertFalse(improvement.name, canBeBuilt) } }