Enumified all remaining resource & improvement uniques (#5523)

* Added conditionals & enumified improvement stat uniques

* Enumified all the other uniques

* Fixed bug where improvemen stat icons didn't show up in tech tree & reviews
This commit is contained in:
Xander Lenstra 2021-10-24 19:47:29 +02:00 committed by GitHub
parent e9d36ea0ce
commit b66443574e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 195 additions and 105 deletions

View File

@ -390,7 +390,7 @@
"culture": 1, "culture": 1,
"isWonder": true, "isWonder": true,
"greatPersonPoints": {"Great Engineer": 1}, "greatPersonPoints": {"Great Engineer": 1},
"uniques": ["Must be next to [Desert]", "[+1 Food, +1 Production, +1 Gold] from [Desert] tiles without [Flood plains] [in this city]", "Gain a free [Amphitheater] [in this city]", "[+6 Culture] once [Archaeology] is discovered"], "uniques": ["Must be next to [Desert]", "[+1 Food, +1 Production, +1 Gold] from [Desert] tiles without [Flood plains] [in this city]", "Gain a free [Amphitheater] [in this city]", "[+6 Culture] <after discovering [Archaeology]>"],
"requiredTech": "Currency", "requiredTech": "Currency",
"quote": "'...who drinks the water I shall give him, says the Lord, will have a spring inside him welling up for eternal life. Let them bring me to your holy mountain in the place where you dwell. Across the desert and through the mountain to the Canyon of the Crescent Moon...' - Indiana Jones" "quote": "'...who drinks the water I shall give him, says the Lord, will have a spring inside him welling up for eternal life. Let them bring me to your holy mountain in the place where you dwell. Across the desert and through the mountain to the Canyon of the Crescent Moon...' - Indiana Jones"
}, },
@ -603,7 +603,7 @@
"culture": 2, "culture": 2,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredBuilding": "Walls", "requiredBuilding": "Walls",
"uniques": ["[+1 Gold] once [Flight] is discovered", "Destroyed when the city is captured"], "uniques": ["[+1 Gold] <after discovering [Flight]>", "Destroyed when the city is captured"],
"requiredTech": "Chivalry" "requiredTech": "Chivalry"
}, },
{ {

View File

@ -7,8 +7,8 @@
"turnsToBuild": 7, "turnsToBuild": 7,
"techRequired": "Agriculture", "techRequired": "Agriculture",
"uniques": ["Can also be built on tiles adjacent to fresh water", "uniques": ["Can also be built on tiles adjacent to fresh water",
"[+1 Food] on [Fresh water] tiles once [Civil Service] is discovered", "[+1 Food] from [Fresh water] tiles <after discovering [Civil Service]>",
"[+1 Food] on [non-fresh water] tiles once [Fertilizer] is discovered"], "[+1 Food] from [non-fresh water] tiles <after discovering [Fertilizer]>"],
"shortcutKey": "F" "shortcutKey": "F"
}, },
{ {
@ -17,7 +17,7 @@
"production": 1, "production": 1,
"turnsToBuild": 7, "turnsToBuild": 7,
"techRequired": "Construction", "techRequired": "Construction",
"uniques": ["[+1 Production] once [Scientific Theory] is discovered"], "uniques": ["[+1 Production] <after discovering [Scientific Theory]>"],
"shortcutKey": "L" "shortcutKey": "L"
}, },
{ {
@ -26,7 +26,7 @@
"production": 1, "production": 1,
"turnsToBuild": 7, "turnsToBuild": 7,
"techRequired": "Mining", "techRequired": "Mining",
"uniques": ["[+1 Production] once [Chemistry] is discovered"], "uniques": ["[+1 Production] <after discovering [Chemistry]>"],
"shortcutKey": "M" "shortcutKey": "M"
}, },
{ {
@ -35,7 +35,7 @@
"gold": 1, "gold": 1,
"turnsToBuild": 7, "turnsToBuild": 7,
"techRequired": "Guilds", "techRequired": "Guilds",
"uniques": ["[+1 Gold] once [Economics] is discovered"], "uniques": ["[+1 Gold] <after discovering [Economics]>"],
"shortcutKey": "T" "shortcutKey": "T"
}, },
@ -44,7 +44,7 @@
"name": "Camp", "name": "Camp",
"turnsToBuild": 7, "turnsToBuild": 7,
"techRequired": "Trapping", "techRequired": "Trapping",
"uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] once [Economics] is discovered"], "uniques": ["Does not need removal of [Forest]","Does not need removal of [Jungle]","[+1 Gold] <after discovering [Economics]>"],
"shortcutKey": "C" "shortcutKey": "C"
}, },
{ {
@ -59,7 +59,7 @@
"name": "Pasture", "name": "Pasture",
"turnsToBuild": 8, "turnsToBuild": 8,
"techRequired": "Animal Husbandry", "techRequired": "Animal Husbandry",
"uniques": ["[+1 Food] once [Fertilizer] is discovered"], "uniques": ["[+1 Food] <after discovering [Fertilizer]>"],
"shortcutKey": "P" "shortcutKey": "P"
}, },
{ {
@ -67,14 +67,14 @@
"turnsToBuild": 6, "turnsToBuild": 6,
"gold": 1, "gold": 1,
"techRequired": "Calendar", "techRequired": "Calendar",
"uniques": ["[+1 Food] once [Fertilizer] is discovered"], "uniques": ["[+1 Food] <after discovering [Fertilizer]>"],
"shortcutKey": "P" "shortcutKey": "P"
}, },
{ {
"name": "Quarry", "name": "Quarry",
"turnsToBuild": 8, "turnsToBuild": 8,
"techRequired": "Masonry", "techRequired": "Masonry",
"uniques": ["[+1 Production] once [Chemistry] is discovered"], "uniques": ["[+1 Production] <after discovering [Chemistry]>"],
"shortcutKey": "Q" "shortcutKey": "Q"
}, },
{ {
@ -82,7 +82,7 @@
"terrainsCanBeBuiltOn": ["Coast"], "terrainsCanBeBuiltOn": ["Coast"],
"food": 1, "food": 1,
"techRequired": "Sailing", "techRequired": "Sailing",
"uniques": ["[+1 Gold] once [Compass] is discovered"] "uniques": ["[+1 Gold] <after discovering [Compass]>"]
}, },
// Military improvement // Military improvement
@ -100,6 +100,7 @@
"name": "Road", "name": "Road",
"turnsToBuild": 4, "turnsToBuild": 4,
"techRequired": "The Wheel", "techRequired": "The Wheel",
// "Costs [1] gold per turn when in your territory" does nothing and is only to inform the user
"uniques": ["Can be built outside your borders", "Costs [1] gold per turn when in your territory"], "uniques": ["Can be built outside your borders", "Costs [1] gold per turn when in your territory"],
"shortcutKey": "R", "shortcutKey": "R",
"civilopediaText": [ "civilopediaText": [
@ -173,7 +174,7 @@
{ {
"name": "Academy", "name": "Academy",
"science": 8, "science": 8,
"uniques": ["Great Improvement", "[+2 Science] once [Scientific Theory] is discovered", "[+2 Science] once [Atomic Theory] is discovered"] "uniques": ["Great Improvement", "[+2 Science] <after discovering [Scientific Theory]>", "[+2 Science] <after discovering [Atomic Theory]>"]
}, },
{ {
"name": "Landmark", "name": "Landmark",
@ -183,12 +184,12 @@
{ {
"name": "Manufactory", "name": "Manufactory",
"production": 4, "production": 4,
"uniques": ["Great Improvement", "[+1 Production] once [Chemistry] is discovered"] "uniques": ["Great Improvement", "[+1 Production] <after discovering [Chemistry]>"]
}, },
{ {
"name": "Customs house", "name": "Customs house",
"gold": 4, "gold": 4,
"uniques": ["Great Improvement", "[+1 Gold] once [Economics] is discovered"] "uniques": ["Great Improvement", "[+1 Gold] <after discovering [Economics]>"]
}, },
{ {
"name": "Holy site", "name": "Holy site",
@ -197,7 +198,7 @@
}, },
{ {
"name": "Citadel", "name": "Citadel",
"uniques": ["Great Improvement", "Gives a defensive bonus of [100]%", "Deal 30 damage to adjacent enemy units", "Can be built just outside your borders"], "uniques": ["Great Improvement", "Gives a defensive bonus of [100]%", "Adjacent enemy units ending their turn take [30] damage", "Can be built just outside your borders"],
"civilopediaText": [{"text":"Constructing it will take over the tiles around it and assign them to your closest city"}] "civilopediaText": [{"text":"Constructing it will take over the tiles around it and assign them to your closest city"}]
}, },
@ -207,7 +208,7 @@
"uniqueTo": "Polynesia", "uniqueTo": "Polynesia",
"culture": 1, "culture": 1,
"turnsToBuild": 4, "turnsToBuild": 4,
"uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] once [Flight] is discovered"], "uniques": ["Can only be built on [Coastal] tiles", "[+1 Culture] for each adjacent [Moai]", "[+1 Gold] <after discovering [Flight]>"],
"techRequired": "Construction", "techRequired": "Construction",
"shortcutKey": "M" "shortcutKey": "M"
}, },
@ -219,8 +220,8 @@
"turnsToBuild": 7, "turnsToBuild": 7,
"uniques": ["Cannot be built on [Bonus resource] tiles", "uniques": ["Cannot be built on [Bonus resource] tiles",
"[+1 Food] for each adjacent [Mountain]", "[+1 Food] for each adjacent [Mountain]",
"[+1 Food] on [Fresh water] tiles once [Civil Service] is discovered", "[+1 Food] on [Fresh water] tiles <after discovering [Civil Service]>",
"[+1 Food] on [non-fresh water] tiles once [Fertilizer] is discovered"], "[+1 Food] on [non-fresh water] tiles <after discovering[Fertilizer]>"],
"techRequired": "Construction", "techRequired": "Construction",
"shortcutKey": "F" "shortcutKey": "F"
}, },
@ -230,7 +231,7 @@
"food": 3, "food": 3,
"terrainsCanBeBuiltOn": ["Marsh", "Flood plains"], "terrainsCanBeBuiltOn": ["Marsh", "Flood plains"],
"turnsToBuild": 7, "turnsToBuild": 7,
"uniques": ["[+1 Production, +2 Gold] once [Economics] is discovered"], "uniques": ["[+1 Production, +2 Gold] <after discovering [Economics]>"],
"techRequired": "Guilds", "techRequired": "Guilds",
"shortcutKey": "F" "shortcutKey": "F"
}, },

View File

@ -324,5 +324,5 @@
"gold": 2, "gold": 2,
"improvement": "Camp", "improvement": "Camp",
"improvementStats": {"gold": 1} "improvementStats": {"gold": 1}
}, }
] ]

View File

@ -212,7 +212,7 @@ object GameStarter {
val allMercantileResources = ruleset.tileResources.values.filter { val allMercantileResources = ruleset.tileResources.values.filter {
it.unique == "Can only be created by Mercantile City-States" // Deprecated as of 3.16.16 it.unique == "Can only be created by Mercantile City-States" // Deprecated as of 3.16.16
|| it.uniques.contains("Can only be created by Mercantile City-States") }.map { it.name } || it.hasUnique(UniqueType.CityStateOnlyResource) }.map { it.name }
val unusedMercantileResources = Stack<String>() val unusedMercantileResources = Stack<String>()

View File

@ -695,7 +695,7 @@ object Battle {
} }
// Remove improvements, add fallout // Remove improvements, add fallout
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique("Indestructible")) { if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Indestructible)) {
tile.improvement = null tile.improvement = null
} }
tile.improvementInProgress = null tile.improvementInProgress = null
@ -758,7 +758,7 @@ object Battle {
} }
// Remove improvements // Remove improvements
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique("Indestructible")) { if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Indestructible)) {
tile.improvement = null tile.improvement = null
} }
tile.improvementInProgress = null tile.improvementInProgress = null

View File

@ -116,10 +116,12 @@ class CityConstructions {
stats.add(unique.stats.times(cityInfo.population.population / unique.params[1].toFloat())) stats.add(unique.stats.times(cityInfo.population.population / unique.params[1].toFloat()))
} }
for (unique in cityInfo.getLocalMatchingUniques("[] once [] is discovered")) { // Deprecated since 3.17.11
if (cityInfo.civInfo.tech.isResearched(unique.params[1])) for (unique in cityInfo.getLocalMatchingUniques(UniqueType.StatsWithTech)) {
stats.add(unique.stats) if (cityInfo.civInfo.tech.isResearched(unique.params[1]))
} stats.add(unique.stats)
}
//
return stats return stats
} }

View File

@ -28,7 +28,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
for (tech in startingTechs) for (tech in startingTechs)
civInfo.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet civInfo.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
val allMercantileResources = ruleset.tileResources.values.filter { it.hasUnique("Can only be created by Mercantile City-States") }.map { it.name } val allMercantileResources = ruleset.tileResources.values.filter { it.hasUnique(UniqueType.CityStateOnlyResource) }.map { it.name }
val allPossibleBonuses = HashSet<Unique>() // We look through these to determine what kind of city state we are val allPossibleBonuses = HashSet<Unique>() // We look through these to determine what kind of city state we are
var fallback = false var fallback = false
for (era in ruleset.eras.values) { for (era in ruleset.eras.values) {

View File

@ -1014,15 +1014,16 @@ class MapUnit {
private fun doCitadelDamage() { private fun doCitadelDamage() {
// Check for Citadel damage - note: 'Damage does not stack with other Citadels' // Check for Citadel damage - note: 'Damage does not stack with other Citadels'
val citadelTile = currentTile.neighbors val citadelTile = currentTile.neighbors
.firstOrNull { .filter {
it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) && it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) && it.improvement != null
with(it.getTileImprovement()) { }.maxByOrNull { tile ->
this != null && this.hasUnique("Deal 30 damage to adjacent enemy units") tile.getTileImprovement()!!
} .getMatchingUniques(UniqueType.DamagesAdjacentEnemyUnits)
.sumOf { it.params[0].toInt() }
} }
if (citadelTile != null) { if (citadelTile != null) {
health -= 30 health -= citadelTile.getTileImprovement()!!.getMatchingUniques(UniqueType.DamagesAdjacentEnemyUnits).sumOf { it.params[0].toInt() }
val locations = LocationAction(listOf(citadelTile.position, currentTile.position)) val locations = LocationAction(listOf(citadelTile.position, currentTile.position))
if (health <= 0) { if (health <= 0) {
civInfo.addNotification( civInfo.addNotification(

View File

@ -10,6 +10,7 @@ import com.unciv.logic.civilization.PlayerType
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.tile.* import com.unciv.models.ruleset.tile.*
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
@ -354,21 +355,31 @@ open class TileInfo {
} }
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats { fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
val stats = improvement.clone() // clones the stats of the improvement, not the improvement itself val stats = improvement.cloneStats()
if (hasViewableResource(observingCiv) && tileResource.improvement == improvement.name) if (hasViewableResource(observingCiv) && tileResource.improvement == improvement.name)
stats.add(tileResource.improvementStats!!.clone()) // resource-specific improvement stats.add(tileResource.improvementStats!!.clone()) // resource-specific improvement
for (unique in improvement.uniqueObjects) // Deprecated since 3.17.10
if (unique.placeholderText == "[] once [] is discovered" && observingCiv.tech.isResearched(unique.params[1])) for (unique in improvement.getMatchingUniques(UniqueType.StatsWithTech)) {
stats.add(unique.stats) if (observingCiv.tech.isResearched(unique.params[1]))
stats.add(unique.stats)
}
//
for (unique in improvement.getMatchingUniques(UniqueType.Stats, StateForConditionals(civInfo = observingCiv, cityInfo = city))) {
stats.add(unique.stats)
}
if (city != null) { if (city != null) {
val tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles) val tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals(civInfo = observingCiv, cityInfo = city))
.filter { city.matchesFilter(it.params[2]) } .filter { city.matchesFilter(it.params[2]) }
val improvementUniques = improvement.uniqueObjects.filter { val improvementUniques =
it.placeholderText == "[] on [] tiles once [] is discovered" // Deprecated since 3.17.10
&& observingCiv.tech.isResearched(it.params[2]) improvement.getMatchingUniques(UniqueType.StatsOnTileWithTech)
} .filter { observingCiv.tech.isResearched(it.params[2]) } +
//
improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile, StateForConditionals(civInfo = observingCiv, cityInfo = city))
for (unique in tileUniques + improvementUniques) { for (unique in tileUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1]) if (improvement.matchesFilter(unique.params[1])
// Freshwater and non-freshwater cannot be moved to matchesUniqueFilter since that creates an endless feedback. // Freshwater and non-freshwater cannot be moved to matchesUniqueFilter since that creates an endless feedback.
@ -385,15 +396,14 @@ open class TileInfo {
} }
} }
for (unique in improvement.uniqueObjects) for (unique in improvement.getMatchingUniques(UniqueType.ImprovementStatsForAdjacencies)) {
if (unique.placeholderText == "[] for each adjacent []") { val adjacent = unique.params[1]
val adjacent = unique.params[1] val numberOfBonuses = neighbors.count {
val numberOfBonuses = neighbors.count { it.matchesFilter(adjacent, observingCiv)
it.matchesFilter(adjacent, observingCiv) || it.roadStatus.name == adjacent
|| it.roadStatus.name == adjacent
}
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
} }
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
}
for (unique in observingCiv.getMatchingUniques("+[]% yield from every []")) for (unique in observingCiv.getMatchingUniques("+[]% yield from every []"))
if (improvement.matchesFilter(unique.params[1])) if (improvement.matchesFilter(unique.params[1]))
@ -408,16 +418,16 @@ open class TileInfo {
improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName -> false improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName -> false
improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!) -> false improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!) -> false
getOwner() != civInfo && !( getOwner() != civInfo && !(
improvement.hasUnique("Can be built outside your borders") improvement.hasUnique(UniqueType.CanBuildOutsideBorders)
// citadel can be built only next to or within own borders || ( // citadel can be built only next to or within own borders
|| improvement.hasUnique("Can be built just outside your borders") improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders)
&& neighbors.any { it.getOwner() == civInfo } && civInfo.cities.isNotEmpty() && neighbors.any { it.getOwner() == civInfo } && civInfo.cities.isNotEmpty()
) -> false )
) -> false
improvement.uniqueObjects.any { improvement.uniqueObjects.any {
it.placeholderText == "Obsolete with []" && civInfo.tech.isResearched(it.params[0]) it.placeholderText == "Obsolete with []" && civInfo.tech.isResearched(it.params[0])
} -> return false } -> return false
improvement.uniqueObjects.any { improvement.getMatchingUniques(UniqueType.RequiresTechToBuildOnTile).any {
it.placeholderText == "Cannot be built on [] tiles until [] is discovered" &&
matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1]) matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1])
} -> false } -> false
improvement.uniqueObjects.any { improvement.uniqueObjects.any {
@ -437,8 +447,8 @@ open class TileInfo {
return when { return when {
improvement.name == this.improvement -> false improvement.name == this.improvement -> false
isCityCenter() -> false isCityCenter() -> false
improvement.uniqueObjects.filter { it.placeholderText == "Cannot be built on [] tiles" }.any { improvement.getMatchingUniques(UniqueType.CannotBuildOnTile).any {
unique -> matchesTerrainFilter(unique.params[0]) unique -> matchesTerrainFilter(unique.params[0])
} -> false } -> false
// Road improvements can change on tiles with irremovable improvements - nothing else can, though. // Road improvements can change on tiles with irremovable improvements - nothing else can, though.
@ -463,7 +473,7 @@ open class TileInfo {
improvement.name == roadStatus.removeAction -> true improvement.name == roadStatus.removeAction -> true
topTerrain.unbuildable && !improvement.isAllowedOnFeature(topTerrain.name) -> false topTerrain.unbuildable && !improvement.isAllowedOnFeature(topTerrain.name) -> false
// DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests. // DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests.
improvement.hasUnique("Can also be built on tiles adjacent to fresh water") && isAdjacentToFreshwater -> true improvement.hasUnique(UniqueType.ImprovementBuildableByFreshWater) && isAdjacentToFreshwater -> true
// If an unique of this type exists, we want all to match (e.g. Hill _and_ Forest would be meaningful). // If an unique of this type exists, we want all to match (e.g. Hill _and_ Forest would be meaningful).
improvement.uniqueObjects.filter { it.placeholderText == "Can only be built on [] tiles" }.let { improvement.uniqueObjects.filter { it.placeholderText == "Can only be built on [] tiles" }.let {
@ -544,9 +554,8 @@ open class TileInfo {
var bonus = getLastTerrain().defenceBonus var bonus = getLastTerrain().defenceBonus
val tileImprovement = getTileImprovement() val tileImprovement = getTileImprovement()
if (tileImprovement != null) { if (tileImprovement != null) {
for (unique in tileImprovement.uniqueObjects) for (unique in tileImprovement.getMatchingUniques(UniqueType.DefensiveBonus))
if (unique.placeholderText == "Gives a defensive bonus of []%") bonus += unique.params[0].toFloat() / 100
bonus += unique.params[0].toFloat() / 100
} }
return bonus return bonus
} }
@ -761,7 +770,7 @@ open class TileInfo {
if (newResource.resourceType != ResourceType.Strategic) return if (newResource.resourceType != ResourceType.Strategic) return
for (unique in newResource.getMatchingUniques(UniqueType.OverrideDepositAmountOnTileFilter)) { for (unique in newResource.getMatchingUniques(UniqueType.ResourceAmountOnTiles)) {
if (matchesTerrainFilter(unique.params[0])) { if (matchesTerrainFilter(unique.params[0])) {
resourceAmount = unique.params[1].toInt() resourceAmount = unique.params[1].toInt()
return return

View File

@ -6,11 +6,10 @@ import com.unciv.UncivGame
import com.unciv.logic.civilization.CityStateType import com.unciv.logic.civilization.CityStateType
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.stats.INamed import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.squareBraceRegex import com.unciv.models.translations.squareBraceRegex
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
import com.unciv.ui.utils.Fonts import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.colorFromRGB import com.unciv.ui.utils.colorFromRGB
@ -177,7 +176,7 @@ class Nation : RulesetObject() {
if (showResources) { if (showResources) {
val allMercantileResources = ruleset.tileResources.values val allMercantileResources = ruleset.tileResources.values
.filter { it.unique == "Can only be created by Mercantile City-States" // Deprecated 3.16.16 .filter { it.unique == "Can only be created by Mercantile City-States" // Deprecated 3.16.16
|| it.uniques.contains("Can only be created by Mercantile City-States") } || it.hasUnique(UniqueType.CityStateOnlyResource) }
if (allMercantileResources.isNotEmpty()) { if (allMercantileResources.isNotEmpty()) {
textList += FormattedLine() textList += FormattedLine()

View File

@ -36,13 +36,26 @@ class Technology: RulesetObject() {
val lineList = ArrayList<String>() // more readable than StringBuilder, with same performance for our use-case val lineList = ArrayList<String>() // more readable than StringBuilder, with same performance for our use-case
for (unique in uniques) lineList += unique.tr() for (unique in uniques) lineList += unique.tr()
for (improvement in ruleset.tileImprovements.values) for (improvement in ruleset.tileImprovements.values) {
for (unique in improvement.uniqueObjects) { for (unique in improvement.uniqueObjects) {
if (unique.placeholderText == "[] once [] is discovered" && unique.params.last() == name) // Deprecated since 3.17.10
if (unique.isOfType(UniqueType.StatsWithTech) && unique.params.last() == name)
lineList += "[${unique.params[0]}] from every [${improvement.name}]"
else if (unique.isOfType(UniqueType.StatsOnTileWithTech) && unique.params.last() == name)
lineList += "[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles"
else
//
if (unique.isOfType(UniqueType.Stats)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += "[${unique.params[0]}] from every [${improvement.name}]" lineList += "[${unique.params[0]}] from every [${improvement.name}]"
else if (unique.placeholderText == "[] on [] tiles once [] is discovered" && unique.params.last() == name) } else if (unique.isOfType(UniqueType.ImprovementStatsOnTile)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += "[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles" lineList += "[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles"
}
} }
}
val viewingCiv = UncivGame.Current.worldScreen.viewingCiv val viewingCiv = UncivGame.Current.worldScreen.viewingCiv
val enabledUnits = getEnabledUnits(viewingCiv) val enabledUnits = getEnabledUnits(viewingCiv)
@ -176,12 +189,26 @@ class Technology: RulesetObject() {
var wantEmpty = true var wantEmpty = true
for (improvement in ruleset.tileImprovements.values) for (improvement in ruleset.tileImprovements.values)
for (unique in improvement.uniqueObjects) { for (unique in improvement.uniqueObjects) {
if (unique.placeholderText == "[] once [] is discovered" && unique.params.last() == name) { // Deprecated since 3.17.10
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false } if (unique.isOfType(UniqueType.StatsWithTech) && unique.params.last() == name) {
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false }
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}]",
link = improvement.makeLink())
} else if (unique.isOfType(UniqueType.StatsOnTileWithTech) && unique.params.last() == name) {
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false }
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles",
link = improvement.makeLink())
}
else
//
if (unique.isOfType(UniqueType.Stats)) {
val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}]", lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}]",
link = improvement.makeLink()) link = improvement.makeLink())
} else if (unique.placeholderText == "[] on [] tiles once [] is discovered" && unique.params.last() == name) { } else if (unique.placeholderText == "[] on [] tiles") {
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false } val requiredTech = unique.conditionals.firstOrNull { it.isOfType(UniqueType.ConditionalTech) }?.params?.get(0)
if (requiredTech != name) continue
lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles", lineList += FormattedLine("[${unique.params[0]}] from every [${improvement.name}] on [${unique.params[1]}] tiles",
link = improvement.makeLink()) link = improvement.makeLink())
} }

View File

@ -1,13 +1,11 @@
package com.unciv.models.ruleset.tile package com.unciv.models.ruleset.tile
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetStatsObject import com.unciv.models.ruleset.RulesetStatsObject
import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -67,9 +65,9 @@ class TileImprovement : RulesetStatsObject() {
return lines.joinToString("\n") return lines.joinToString("\n")
} }
fun isGreatImprovement() = hasUnique("Great Improvement") fun isGreatImprovement() = hasUnique(UniqueType.GreatImprovement)
fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name } fun isRoad() = RoadStatus.values().any { it != RoadStatus.None && it.name == this.name }
fun isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered") fun isAncientRuinsEquivalent() = hasUnique(UniqueType.IsAncientRuinsEquivalent)
/** /**
* Check: Is this improvement allowed on a [given][name] terrain feature? * Check: Is this improvement allowed on a [given][name] terrain feature?
@ -81,11 +79,7 @@ class TileImprovement : RulesetStatsObject() {
* so this check is done in conjunction - for the user, success means he does not need to remove * so this check is done in conjunction - for the user, success means he does not need to remove
* a terrain feature, thus the unique name. * a terrain feature, thus the unique name.
*/ */
fun isAllowedOnFeature(name: String): Boolean { fun isAllowedOnFeature(name: String) = getMatchingUniques(UniqueType.NoFeatureRemovalNeeded).any { it.params[0] == name }
return uniqueObjects.filter { it.placeholderText == "Does not need removal of []"
&& it.params[0] == name
}.any()
}
fun matchesFilter(filter: String): Boolean { fun matchesFilter(filter: String): Boolean {
return when (filter) { return when (filter) {

View File

@ -5,7 +5,6 @@ import com.unciv.logic.city.CityInfo
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.* import com.unciv.models.translations.*
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Ruleset
class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val sourceObjectName: String? = null) { class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val sourceObjectName: String? = null) {
@ -22,6 +21,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
} }
val conditionals: List<Unique> = text.getConditionals() val conditionals: List<Unique> = text.getConditionals()
val allParams = params + conditionals.flatMap { it.params }
fun isOfType(uniqueType: UniqueType) = uniqueType == type fun isOfType(uniqueType: UniqueType) = uniqueType == type
fun conditionalsApply(civInfo: CivilizationInfo? = null, city: CityInfo? = null): Boolean { fun conditionalsApply(civInfo: CivilizationInfo? = null, city: CityInfo? = null): Boolean {
@ -53,7 +54,15 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
state.civInfo != null && state.civInfo.getEraNumber() >= state.civInfo.gameInfo.ruleSet.eras[condition.params[0]]!!.eraNumber state.civInfo != null && state.civInfo.getEraNumber() >= state.civInfo.gameInfo.ruleSet.eras[condition.params[0]]!!.eraNumber
UniqueType.ConditionalDuringEra -> UniqueType.ConditionalDuringEra ->
state.civInfo != null && state.civInfo.getEraNumber() == state.civInfo.gameInfo.ruleSet.eras[condition.params[0]]!!.eraNumber state.civInfo != null && state.civInfo.getEraNumber() == state.civInfo.gameInfo.ruleSet.eras[condition.params[0]]!!.eraNumber
UniqueType.ConditionalTech ->
state.civInfo != null && state.civInfo.tech.isResearched(condition.params[0])
UniqueType.ConditionalNoTech ->
state.civInfo != null && !state.civInfo.tech.isResearched(condition.params[0])
UniqueType.ConditionalPolicy ->
state.civInfo != null && state.civInfo.policies.isAdopted(condition.params[0])
UniqueType.ConditionalNoPolicy ->
state.civInfo != null && !state.civInfo.policies.isAdopted(condition.params[0])
UniqueType.ConditionalSpecialistCount -> UniqueType.ConditionalSpecialistCount ->
state.cityInfo != null && state.cityInfo.population.getNumberOfSpecialists() >= condition.params[0].toInt() state.cityInfo != null && state.cityInfo.population.getNumberOfSpecialists() >= condition.params[0].toInt()

View File

@ -222,6 +222,17 @@ enum class UniqueParameterType(val parameterName:String) {
else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
} }
}, },
Policy("policy") {
override fun getErrorSeverity(
parameterText: String,
ruleset: Ruleset
): UniqueType.UniqueComplianceErrorSeverity? {
return when (parameterText) {
in ruleset.policies -> null
else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
}
},
/** Behaves like [Unknown], but states explicitly the parameter is OK and its contents are ignored */ /** Behaves like [Unknown], but states explicitly the parameter is OK and its contents are ignored */
Comment("comment") { Comment("comment") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):

View File

@ -62,7 +62,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
/////// Stat providing uniques /////// Stat providing uniques
Stats("[stats]", UniqueTarget.Global, UniqueTarget.FollowerBelief), Stats("[stats]", UniqueTarget.Global, UniqueTarget.FollowerBelief, UniqueTarget.Improvement),
StatsPerCity("[stats] [cityFilter]", UniqueTarget.Global), StatsPerCity("[stats] [cityFilter]", UniqueTarget.Global),
@Deprecated("As of 3.16.16 - removed as of 3.17.11", ReplaceWith("[stats] <if this city has at least [amount] specialists>"), DeprecationLevel.ERROR) @Deprecated("As of 3.16.16 - removed as of 3.17.11", ReplaceWith("[stats] <if this city has at least [amount] specialists>"), DeprecationLevel.ERROR)
StatBonusForNumberOfSpecialists("[stats] if this city has at least [amount] specialists", UniqueTarget.Global), StatBonusForNumberOfSpecialists("[stats] if this city has at least [amount] specialists", UniqueTarget.Global),
@ -175,7 +175,6 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
RequiresAnotherBuilding("Requires a [buildingName] in this city", UniqueTarget.Building), RequiresAnotherBuilding("Requires a [buildingName] in this city", UniqueTarget.Building),
///////////////////////////////////////// UNIT UNIQUES ///////////////////////////////////////// ///////////////////////////////////////// UNIT UNIQUES /////////////////////////////////////////
FoundCity("Founds a new city", UniqueTarget.Unit), FoundCity("Founds a new city", UniqueTarget.Unit),
@ -306,13 +305,41 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
FreshWater("Fresh water", UniqueTarget.Terrain), FreshWater("Fresh water", UniqueTarget.Terrain),
RoughTerrain("Rough terrain", UniqueTarget.Terrain), RoughTerrain("Rough terrain", UniqueTarget.Terrain),
// Resource uniques /////// Resource uniques
OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource), ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
CityStateOnlyResource("Can only be created by Mercantile City-States", UniqueTarget.Resource),
////// Improvement uniques
ImprovementBuildableByFreshWater("Can also be built on tiles adjacent to fresh water", UniqueTarget.Improvement),
ImprovementStatsOnTile("[stats] from [tileFilter] tiles", UniqueTarget.Improvement),
@Deprecated("As of 3.17.10", ReplaceWith("[stats] from [tileFilter] tiles <after discovering [tech]>"), DeprecationLevel.WARNING)
StatsOnTileWithTech("[stats] on [tileFilter] tiles once [tech] is discovered", UniqueTarget.Improvement),
@Deprecated("As of 3.17.10", ReplaceWith("[stats] <after discovering [tech]>"), DeprecationLevel.WARNING)
StatsWithTech("[stats] once [tech] is discovered", UniqueTarget.Improvement, UniqueTarget.Building),
ImprovementStatsForAdjacencies("[stats] for each adjacent [tileFilter]", UniqueTarget.Improvement),
CanBuildOutsideBorders("Can be built outside your borders", UniqueTarget.Improvement),
CanBuildJustOutsideBorders("Can be built just outside your borders", UniqueTarget.Improvement),
RequiresTechToBuildOnTile("Cannot be built on [tileFilter] tiles until [tech] is discovered", UniqueTarget.Improvement),
CannotBuildOnTile("Cannot be built on [tileFilter] tiles", UniqueTarget.Improvement),
NoFeatureRemovalNeeded("Does not need removal of [tileFilter]", UniqueTarget.Improvement),
DefensiveBonus("Gives a defensive bonus of [amount]%", UniqueTarget.Improvement),
ImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // Unused
DamagesAdjacentEnemyUnits("Deal [amount] damage to adjacent enemy units", UniqueTarget.Improvement),
@Deprecated("As of 3.17.10", ReplaceWith("Adjacent enemy units ending their turn take [30] damage"), DeprecationLevel.WARNING)
DamagesAdjacentEnemyUnitsForExactlyThirtyDamage("Deal 30 damage to adjacent enemy units", UniqueTarget.Improvement),
GreatImprovement("Great Improvement", UniqueTarget.Improvement),
IsAncientRuinsEquivalent("Provides a random bonus when entered", UniqueTarget.Improvement),
Unpillagable("Unpillagable", UniqueTarget.Improvement),
Indestructible("Indestructible", UniqueTarget.Improvement),
///////////////////////////////////////// CONDITIONALS ///////////////////////////////////////// ///////////////////////////////////////// CONDITIONALS /////////////////////////////////////////
// civ conditionals /////// civ conditionals
ConditionalWar("when at war", UniqueTarget.Conditional), ConditionalWar("when at war", UniqueTarget.Conditional),
ConditionalNotWar("when not at war", UniqueTarget.Conditional), ConditionalNotWar("when not at war", UniqueTarget.Conditional),
ConditionalHappy("while the empire is happy", UniqueTarget.Conditional), ConditionalHappy("while the empire is happy", UniqueTarget.Conditional),
@ -321,11 +348,16 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
ConditionalDuringEra("during the [era]", UniqueTarget.Conditional), ConditionalDuringEra("during the [era]", UniqueTarget.Conditional),
ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional), ConditionalBeforeEra("before the [era]", UniqueTarget.Conditional),
ConditionalStartingFromEra("starting from the [era]", UniqueTarget.Conditional), ConditionalStartingFromEra("starting from the [era]", UniqueTarget.Conditional),
ConditionalTech("after discovering [tech]", UniqueTarget.Conditional),
ConditionalNoTech("before discovering [tech]", UniqueTarget.Conditional),
ConditionalPolicy("after adopting [policy]", UniqueTarget.Conditional),
ConditionalNoPolicy("before adopting [policy]", UniqueTarget.Conditional),
// city conditionals /////// city conditionals
ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional), ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional),
// unit conditionals /////// unit conditionals
ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional), ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional),
ConditionalVsCity("vs cities", UniqueTarget.Conditional), ConditionalVsCity("vs cities", UniqueTarget.Conditional),
ConditionalVsUnits("vs [mapUnitFilter] units", UniqueTarget.Conditional), ConditionalVsUnits("vs [mapUnitFilter] units", UniqueTarget.Conditional),
@ -333,9 +365,8 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
ConditionalAttacking("when attacking", UniqueTarget.Conditional), ConditionalAttacking("when attacking", UniqueTarget.Conditional),
ConditionalDefending("when defending", UniqueTarget.Conditional), ConditionalDefending("when defending", UniqueTarget.Conditional),
ConditionalInTiles("when fighting in [tileFilter] tiles", UniqueTarget.Conditional), ConditionalInTiles("when fighting in [tileFilter] tiles", UniqueTarget.Conditional),
// ConditionalIntercepting("when intercepting", UniqueTarget.Conditional),
// tile conditionals /////// tile conditionals
ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional),
ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional),

View File

@ -7,4 +7,8 @@ open class NamedStats : Stats(), INamed {
override fun toString(): String { override fun toString(): String {
return name return name
} }
fun cloneStats(): Stats {
return clone()
}
} }

View File

@ -213,7 +213,7 @@ class Translations : LinkedHashMap<String, TranslationEntry>(){
// Whenever this string is changed, it should also be changed in the translation files! // Whenever this string is changed, it should also be changed in the translation files!
// It is mostly used as the template for translating the order of conditionals // It is mostly used as the template for translating the order of conditionals
const val englishConditionalOrderingString = const val englishConditionalOrderingString =
"<for [mapUnitFilter] units> <vs cities> <vs [mapUnitFilter] units> <when fighting in [tileFilter] tiles> <when attacking> <when defending> <if this city has at least [amount] specialists> <when at war> <when not at war> <while the empire is happy> <during a Golden Age> <during the [era]> <before the [era]> <starting from the [era]>" "<for [mapUnitFilter] units> <vs cities> <vs [mapUnitFilter] units> <when fighting in [tileFilter] tiles> <when attacking> <when defending> <if this city has at least [amount] specialists> <when at war> <when not at war> <while the empire is happy> <during a Golden Age> <during the [era]> <before the [era]> <starting from the [era]> <with [techOrPolicy]> <without [techOrPolicy]>"
const val conditionalUniqueOrderString = "ConditionalsPlacement" const val conditionalUniqueOrderString = "ConditionalsPlacement"
const val shouldCapitalizeString = "StartWithCapitalLetter" const val shouldCapitalizeString = "StartWithCapitalLetter"
} }

View File

@ -154,7 +154,7 @@ class FormattedLine (
val ruleSet = getCurrentRuleset() val ruleSet = getCurrentRuleset()
if (allObjectNamesCategoryMap == null || rulesetCachedInNameMap !== ruleSet) if (allObjectNamesCategoryMap == null || rulesetCachedInNameMap !== ruleSet)
allObjectNamesCategoryMap = initNamesCategoryMap(ruleSet) allObjectNamesCategoryMap = initNamesCategoryMap(ruleSet)
for (parameter in unique.params) { for (parameter in unique.params + unique.conditionals.flatMap { it.params }) {
val category = allObjectNamesCategoryMap!![parameter] ?: continue val category = allObjectNamesCategoryMap!![parameter] ?: continue
return category.name + "/" + parameter return category.name + "/" + parameter
} }

View File

@ -65,12 +65,14 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
}) })
for (improvement in ruleset.tileImprovements.values for (improvement in ruleset.tileImprovements.values
.filter { .filter {
it.techRequired == techName || it.uniqueObjects.any { u -> u.params.contains(techName) } it.techRequired == techName
|| it.uniqueObjects.any { it.placeholderText == "[] once [] is discovered" && it.params[1] == techName } || it.uniqueObjects.any { u -> u.allParams.contains(techName) }
} }
.filter { it.uniqueTo == null || it.uniqueTo == civName }) .filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
techEnabledIcons.add(ImageGetter.getImprovementIcon(improvement.name, techIconSize)) techEnabledIcons.add(ImageGetter.getImprovementIcon(improvement.name, techIconSize))
}
for (resource in ruleset.tileResources.values.filter { it.revealedBy == techName }) for (resource in ruleset.tileResources.values.filter { it.revealedBy == techName })
@ -78,7 +80,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
for (unique in tech.uniques) for (unique in tech.uniques)
techEnabledIcons.add(ImageGetter.getImage("OtherIcons/Star") techEnabledIcons.add(ImageGetter.getImage("OtherIcons/Star")
.apply { color = Color.BLACK }.surroundWithCircle(techIconSize)) .apply { color = Color.BLACK }.surroundWithCircle(techIconSize))
if (isWorldScreen) rightSide.add(techEnabledIcons) if (isWorldScreen) rightSide.add(techEnabledIcons)
else rightSide.add(techEnabledIcons) else rightSide.add(techEnabledIcons)

View File

@ -748,7 +748,7 @@ object UnitActions {
fun canPillage(unit: MapUnit, tile: TileInfo): Boolean { fun canPillage(unit: MapUnit, tile: TileInfo): Boolean {
val tileImprovement = tile.getTileImprovement() val tileImprovement = tile.getTileImprovement()
// City ruins, Ancient Ruins, Barbarian Camp, City Center marked in json // City ruins, Ancient Ruins, Barbarian Camp, City Center marked in json
if (tileImprovement == null || tileImprovement.hasUnique("Unpillagable")) return false if (tileImprovement == null || tileImprovement.hasUnique(UniqueType.Unpillagable)) return false
val tileOwner = tile.getOwner() val tileOwner = tile.getOwner()
// Can't pillage friendly tiles, just like you can't attack them - it's an 'act of war' thing // Can't pillage friendly tiles, just like you can't attack them - it's an 'act of war' thing
return tileOwner == null || unit.civInfo.isAtWarWith(tileOwner) return tileOwner == null || unit.civInfo.isAtWarWith(tileOwner)