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,
"isWonder": true,
"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",
"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,
"hurryCostModifier": 25,
"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"
},
{

View File

@ -7,8 +7,8 @@
"turnsToBuild": 7,
"techRequired": "Agriculture",
"uniques": ["Can also be built on tiles adjacent to fresh water",
"[+1 Food] on [Fresh water] tiles once [Civil Service] is discovered",
"[+1 Food] on [non-fresh water] tiles once [Fertilizer] is discovered"],
"[+1 Food] from [Fresh water] tiles <after discovering [Civil Service]>",
"[+1 Food] from [non-fresh water] tiles <after discovering [Fertilizer]>"],
"shortcutKey": "F"
},
{
@ -17,7 +17,7 @@
"production": 1,
"turnsToBuild": 7,
"techRequired": "Construction",
"uniques": ["[+1 Production] once [Scientific Theory] is discovered"],
"uniques": ["[+1 Production] <after discovering [Scientific Theory]>"],
"shortcutKey": "L"
},
{
@ -26,7 +26,7 @@
"production": 1,
"turnsToBuild": 7,
"techRequired": "Mining",
"uniques": ["[+1 Production] once [Chemistry] is discovered"],
"uniques": ["[+1 Production] <after discovering [Chemistry]>"],
"shortcutKey": "M"
},
{
@ -35,7 +35,7 @@
"gold": 1,
"turnsToBuild": 7,
"techRequired": "Guilds",
"uniques": ["[+1 Gold] once [Economics] is discovered"],
"uniques": ["[+1 Gold] <after discovering [Economics]>"],
"shortcutKey": "T"
},
@ -44,7 +44,7 @@
"name": "Camp",
"turnsToBuild": 7,
"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"
},
{
@ -59,7 +59,7 @@
"name": "Pasture",
"turnsToBuild": 8,
"techRequired": "Animal Husbandry",
"uniques": ["[+1 Food] once [Fertilizer] is discovered"],
"uniques": ["[+1 Food] <after discovering [Fertilizer]>"],
"shortcutKey": "P"
},
{
@ -67,14 +67,14 @@
"turnsToBuild": 6,
"gold": 1,
"techRequired": "Calendar",
"uniques": ["[+1 Food] once [Fertilizer] is discovered"],
"uniques": ["[+1 Food] <after discovering [Fertilizer]>"],
"shortcutKey": "P"
},
{
"name": "Quarry",
"turnsToBuild": 8,
"techRequired": "Masonry",
"uniques": ["[+1 Production] once [Chemistry] is discovered"],
"uniques": ["[+1 Production] <after discovering [Chemistry]>"],
"shortcutKey": "Q"
},
{
@ -82,7 +82,7 @@
"terrainsCanBeBuiltOn": ["Coast"],
"food": 1,
"techRequired": "Sailing",
"uniques": ["[+1 Gold] once [Compass] is discovered"]
"uniques": ["[+1 Gold] <after discovering [Compass]>"]
},
// Military improvement
@ -100,6 +100,7 @@
"name": "Road",
"turnsToBuild": 4,
"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"],
"shortcutKey": "R",
"civilopediaText": [
@ -173,7 +174,7 @@
{
"name": "Academy",
"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",
@ -183,12 +184,12 @@
{
"name": "Manufactory",
"production": 4,
"uniques": ["Great Improvement", "[+1 Production] once [Chemistry] is discovered"]
"uniques": ["Great Improvement", "[+1 Production] <after discovering [Chemistry]>"]
},
{
"name": "Customs house",
"gold": 4,
"uniques": ["Great Improvement", "[+1 Gold] once [Economics] is discovered"]
"uniques": ["Great Improvement", "[+1 Gold] <after discovering [Economics]>"]
},
{
"name": "Holy site",
@ -197,7 +198,7 @@
},
{
"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"}]
},
@ -207,7 +208,7 @@
"uniqueTo": "Polynesia",
"culture": 1,
"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",
"shortcutKey": "M"
},
@ -219,8 +220,8 @@
"turnsToBuild": 7,
"uniques": ["Cannot be built on [Bonus resource] tiles",
"[+1 Food] for each adjacent [Mountain]",
"[+1 Food] on [Fresh water] tiles once [Civil Service] is discovered",
"[+1 Food] on [non-fresh water] tiles once [Fertilizer] is discovered"],
"[+1 Food] on [Fresh water] tiles <after discovering [Civil Service]>",
"[+1 Food] on [non-fresh water] tiles <after discovering[Fertilizer]>"],
"techRequired": "Construction",
"shortcutKey": "F"
},
@ -230,7 +231,7 @@
"food": 3,
"terrainsCanBeBuiltOn": ["Marsh", "Flood plains"],
"turnsToBuild": 7,
"uniques": ["[+1 Production, +2 Gold] once [Economics] is discovered"],
"uniques": ["[+1 Production, +2 Gold] <after discovering [Economics]>"],
"techRequired": "Guilds",
"shortcutKey": "F"
},

View File

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

View File

@ -212,7 +212,7 @@ object GameStarter {
val allMercantileResources = ruleset.tileResources.values.filter {
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>()

View File

@ -695,7 +695,7 @@ object Battle {
}
// 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.improvementInProgress = null
@ -758,7 +758,7 @@ object Battle {
}
// Remove improvements
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique("Indestructible")) {
if (tile.improvement != null && !tile.getTileImprovement()!!.hasUnique(UniqueType.Indestructible)) {
tile.improvement = null
}
tile.improvementInProgress = null

View File

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

View File

@ -28,7 +28,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
for (tech in startingTechs)
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
var fallback = false
for (era in ruleset.eras.values) {

View File

@ -1014,15 +1014,16 @@ class MapUnit {
private fun doCitadelDamage() {
// Check for Citadel damage - note: 'Damage does not stack with other Citadels'
val citadelTile = currentTile.neighbors
.firstOrNull {
it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) &&
with(it.getTileImprovement()) {
this != null && this.hasUnique("Deal 30 damage to adjacent enemy units")
}
.filter {
it.getOwner() != null && civInfo.isAtWarWith(it.getOwner()!!) && it.improvement != null
}.maxByOrNull { tile ->
tile.getTileImprovement()!!
.getMatchingUniques(UniqueType.DamagesAdjacentEnemyUnits)
.sumOf { it.params[0].toInt() }
}
if (citadelTile != null) {
health -= 30
health -= citadelTile.getTileImprovement()!!.getMatchingUniques(UniqueType.DamagesAdjacentEnemyUnits).sumOf { it.params[0].toInt() }
val locations = LocationAction(listOf(citadelTile.position, currentTile.position))
if (health <= 0) {
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.unique.UniqueType
import com.unciv.models.ruleset.tile.*
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr
import com.unciv.ui.civilopedia.FormattedLine
@ -354,21 +355,31 @@ open class TileInfo {
}
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)
stats.add(tileResource.improvementStats!!.clone()) // resource-specific improvement
for (unique in improvement.uniqueObjects)
if (unique.placeholderText == "[] once [] is discovered" && observingCiv.tech.isResearched(unique.params[1]))
stats.add(unique.stats)
// Deprecated since 3.17.10
for (unique in improvement.getMatchingUniques(UniqueType.StatsWithTech)) {
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) {
val tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles)
val tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals(civInfo = observingCiv, cityInfo = city))
.filter { city.matchesFilter(it.params[2]) }
val improvementUniques = improvement.uniqueObjects.filter {
it.placeholderText == "[] on [] tiles once [] is discovered"
&& observingCiv.tech.isResearched(it.params[2])
}
val improvementUniques =
// Deprecated since 3.17.10
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) {
if (improvement.matchesFilter(unique.params[1])
// 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)
if (unique.placeholderText == "[] for each adjacent []") {
val adjacent = unique.params[1]
val numberOfBonuses = neighbors.count {
it.matchesFilter(adjacent, observingCiv)
|| it.roadStatus.name == adjacent
}
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
for (unique in improvement.getMatchingUniques(UniqueType.ImprovementStatsForAdjacencies)) {
val adjacent = unique.params[1]
val numberOfBonuses = neighbors.count {
it.matchesFilter(adjacent, observingCiv)
|| it.roadStatus.name == adjacent
}
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
}
for (unique in observingCiv.getMatchingUniques("+[]% yield from every []"))
if (improvement.matchesFilter(unique.params[1]))
@ -408,16 +418,16 @@ open class TileInfo {
improvement.uniqueTo != null && improvement.uniqueTo != civInfo.civName -> false
improvement.techRequired != null && !civInfo.tech.isResearched(improvement.techRequired!!) -> false
getOwner() != civInfo && !(
improvement.hasUnique("Can be built outside your borders")
// citadel can be built only next to or within own borders
|| improvement.hasUnique("Can be built just outside your borders")
&& neighbors.any { it.getOwner() == civInfo } && civInfo.cities.isNotEmpty()
) -> false
improvement.hasUnique(UniqueType.CanBuildOutsideBorders)
|| ( // citadel can be built only next to or within own borders
improvement.hasUnique(UniqueType.CanBuildJustOutsideBorders)
&& neighbors.any { it.getOwner() == civInfo } && civInfo.cities.isNotEmpty()
)
) -> false
improvement.uniqueObjects.any {
it.placeholderText == "Obsolete with []" && civInfo.tech.isResearched(it.params[0])
} -> return false
improvement.uniqueObjects.any {
it.placeholderText == "Cannot be built on [] tiles until [] is discovered" &&
improvement.getMatchingUniques(UniqueType.RequiresTechToBuildOnTile).any {
matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1])
} -> false
improvement.uniqueObjects.any {
@ -437,8 +447,8 @@ open class TileInfo {
return when {
improvement.name == this.improvement -> false
isCityCenter() -> false
improvement.uniqueObjects.filter { it.placeholderText == "Cannot be built on [] tiles" }.any {
unique -> matchesTerrainFilter(unique.params[0])
improvement.getMatchingUniques(UniqueType.CannotBuildOnTile).any {
unique -> matchesTerrainFilter(unique.params[0])
} -> false
// 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
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.
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).
improvement.uniqueObjects.filter { it.placeholderText == "Can only be built on [] tiles" }.let {
@ -544,9 +554,8 @@ open class TileInfo {
var bonus = getLastTerrain().defenceBonus
val tileImprovement = getTileImprovement()
if (tileImprovement != null) {
for (unique in tileImprovement.uniqueObjects)
if (unique.placeholderText == "Gives a defensive bonus of []%")
bonus += unique.params[0].toFloat() / 100
for (unique in tileImprovement.getMatchingUniques(UniqueType.DefensiveBonus))
bonus += unique.params[0].toFloat() / 100
}
return bonus
}
@ -761,7 +770,7 @@ open class TileInfo {
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])) {
resourceAmount = unique.params[1].toInt()
return

View File

@ -6,11 +6,10 @@ import com.unciv.UncivGame
import com.unciv.logic.civilization.CityStateType
import com.unciv.models.ruleset.unique.Unique
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.tr
import com.unciv.ui.civilopedia.FormattedLine
import com.unciv.ui.civilopedia.ICivilopediaText
import com.unciv.ui.utils.Fonts
import com.unciv.ui.utils.colorFromRGB
@ -177,7 +176,7 @@ class Nation : RulesetObject() {
if (showResources) {
val allMercantileResources = ruleset.tileResources.values
.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()) {
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
for (unique in uniques) lineList += unique.tr()
for (improvement in ruleset.tileImprovements.values)
for (improvement in ruleset.tileImprovements.values) {
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}]"
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"
}
}
}
val viewingCiv = UncivGame.Current.worldScreen.viewingCiv
val enabledUnits = getEnabledUnits(viewingCiv)
@ -176,12 +189,26 @@ class Technology: RulesetObject() {
var wantEmpty = true
for (improvement in ruleset.tileImprovements.values)
for (unique in improvement.uniqueObjects) {
if (unique.placeholderText == "[] once [] is discovered" && unique.params.last() == name) {
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false }
// Deprecated since 3.17.10
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}]",
link = improvement.makeLink())
} else if (unique.placeholderText == "[] on [] tiles once [] is discovered" && unique.params.last() == name) {
if (wantEmpty) { lineList += FormattedLine(); wantEmpty = false }
} else if (unique.placeholderText == "[] on [] tiles") {
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",
link = improvement.makeLink())
}

View File

@ -1,13 +1,11 @@
package com.unciv.models.ruleset.tile
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.RoadStatus
import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.Ruleset
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.UniqueType
import com.unciv.models.translations.tr
@ -67,9 +65,9 @@ class TileImprovement : RulesetStatsObject() {
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 isAncientRuinsEquivalent() = hasUnique("Provides a random bonus when entered")
fun isAncientRuinsEquivalent() = hasUnique(UniqueType.IsAncientRuinsEquivalent)
/**
* 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
* a terrain feature, thus the unique name.
*/
fun isAllowedOnFeature(name: String): Boolean {
return uniqueObjects.filter { it.placeholderText == "Does not need removal of []"
&& it.params[0] == name
}.any()
}
fun isAllowedOnFeature(name: String) = getMatchingUniques(UniqueType.NoFeatureRemovalNeeded).any { it.params[0] == name }
fun matchesFilter(filter: String): Boolean {
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.translations.*
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) {
@ -22,6 +21,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
}
val conditionals: List<Unique> = text.getConditionals()
val allParams = params + conditionals.flatMap { it.params }
fun isOfType(uniqueType: UniqueType) = uniqueType == type
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
UniqueType.ConditionalDuringEra ->
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 ->
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
}
},
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 */
Comment("comment") {
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
Stats("[stats]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
Stats("[stats]", UniqueTarget.Global, UniqueTarget.FollowerBelief, UniqueTarget.Improvement),
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)
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),
///////////////////////////////////////// UNIT UNIQUES /////////////////////////////////////////
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),
RoughTerrain("Rough terrain", UniqueTarget.Terrain),
// Resource uniques
OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
/////// Resource uniques
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 /////////////////////////////////////////
// civ conditionals
/////// civ conditionals
ConditionalWar("when at war", UniqueTarget.Conditional),
ConditionalNotWar("when not at war", 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),
ConditionalBeforeEra("before 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),
// unit conditionals
/////// unit conditionals
ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional),
ConditionalVsCity("vs cities", 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),
ConditionalDefending("when defending", 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),
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 {
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!
// It is mostly used as the template for translating the order of conditionals
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 shouldCapitalizeString = "StartWithCapitalLetter"
}

View File

@ -154,7 +154,7 @@ class FormattedLine (
val ruleSet = getCurrentRuleset()
if (allObjectNamesCategoryMap == null || rulesetCachedInNameMap !== ruleSet)
allObjectNamesCategoryMap = initNamesCategoryMap(ruleSet)
for (parameter in unique.params) {
for (parameter in unique.params + unique.conditionals.flatMap { it.params }) {
val category = allObjectNamesCategoryMap!![parameter] ?: continue
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
.filter {
it.techRequired == techName || it.uniqueObjects.any { u -> u.params.contains(techName) }
|| it.uniqueObjects.any { it.placeholderText == "[] once [] is discovered" && it.params[1] == techName }
}
.filter { it.uniqueTo == null || it.uniqueTo == civName })
.filter {
it.techRequired == techName
|| it.uniqueObjects.any { u -> u.allParams.contains(techName) }
}
.filter { it.uniqueTo == null || it.uniqueTo == civName }
) {
techEnabledIcons.add(ImageGetter.getImprovementIcon(improvement.name, techIconSize))
}
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)
techEnabledIcons.add(ImageGetter.getImage("OtherIcons/Star")
.apply { color = Color.BLACK }.surroundWithCircle(techIconSize))
.apply { color = Color.BLACK }.surroundWithCircle(techIconSize))
if (isWorldScreen) rightSide.add(techEnabledIcons)
else rightSide.add(techEnabledIcons)

View File

@ -748,7 +748,7 @@ object UnitActions {
fun canPillage(unit: MapUnit, tile: TileInfo): Boolean {
val tileImprovement = tile.getTileImprovement()
// 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()
// 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)