Typed some uniques, added more examples for parameters in unique documentation (#6020)

* Typed some uniques, etc.

* Missed a few square braces

* Missed a parameter

* Missed another parameter

* Made a conditional, spelling, added check to `isStatRelated`

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
Xander Lenstra 2022-01-24 10:03:40 +01:00 committed by GitHub
parent 7a1341c822
commit 39ed8bd269
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 288 additions and 158 deletions

View File

@ -102,7 +102,7 @@
{
"name": "Religious Settlements",
"type": "Pantheon",
"uniques": ["[-15]% cost of natural border growth"]
"uniques": ["[-15]% Culture cost of natural border growth [in cities following this religion]"]
},
{
"name": "Sacred Path",

View File

@ -197,7 +197,7 @@
"uniqueTo": "Russia",
"hurryCostModifier": 25,
"maintenance": 1,
"uniques": ["-[25]% Culture cost of acquiring tiles [in this city]","-[25]% Gold cost of acquiring tiles [in this city]",
"uniques": ["[-25]% Culture cost of natural border growth [in this city]","[-25]% Gold cost of acquiring tiles [in this city]",
"New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"],
"requiredTech": "Bronze Working"
},
@ -580,7 +580,7 @@
"culture": 1,
"greatPersonPoints": {"Great Engineer": 1},
"isWonder": true,
"uniques": ["-[25]% Culture cost of acquiring tiles [in all cities]","-[25]% Gold cost of acquiring tiles [in all cities]"],
"uniques": ["[-25]% Culture cost of natural border growth [in all cities]","[-25]% Gold cost of acquiring tiles [in all cities]"],
"requiredTech": "Education",
"quote": "'The temple is like no other building in the world. It has towers and decoration and all the refinements which the human genius can conceive of.' - Antonio da Magdalena"
},

View File

@ -272,7 +272,7 @@
"innerColor": [255,255,255],
"favoredReligion": "Christianity",
"uniqueName": "Manifest Destiny",
"uniques": ["[+1] Sight <for [{Military} {Land}] units>", "-[50]% Gold cost of acquiring tiles [in all cities]"],
"uniques": ["[+1] Sight <for [{Military} {Land}] units>", "[-50]% Gold cost of acquiring tiles [in all cities]"],
"cities": ["Washington","New York","Boston","Philadelphia","Atlanta","Chicago","Seattle","San Francisco","Los Angeles","Houston",
"Portland","St. Louis","Miami","Buffalo","Detroit","New Orleans","Baltimore","Denver","Cincinnati","Dallas","Memphis",
"Cleveland","Kansas City","San Diego","Richmond","Las Vegas","Phoenix","Albuquerque","Minneapolis","Pittsburgh",

View File

@ -2,7 +2,7 @@
{
"name": "Tradition",
"era": "Ancient era",
"uniques": ["[+3 Culture] [in capital]", "-[25]% Culture cost of acquiring tiles [in all cities]"],
"uniques": ["[+3 Culture] [in capital]", "[-25]% Culture cost of natural border growth [in all cities]"],
"policies": [
{
"name": "Aristocracy",
@ -18,7 +18,7 @@
},
{
"name": "Oligarchy",
"uniques": ["Units in cities cost no Maintenance", "+[50]% attacking strength for cities with garrisoned units"],
"uniques": ["Units in cities cost no Maintenance", "[+50]% Strength for cities <with a garrison> <when attacking>"],
"row": 1,
"column": 5
},

View File

@ -178,7 +178,7 @@
"uniqueTo": "Russia",
"hurryCostModifier": 25,
"maintenance": 1,
"uniques": ["-[25]% Culture cost of acquiring tiles [in this city]","-[25]% Gold cost of acquiring tiles [in this city]",
"uniques": ["[-25]% Culture cost of natural border growth [in this city]","[-25]% Gold cost of acquiring tiles [in this city]",
"New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"],
"requiredTech": "Bronze Working"
},
@ -405,7 +405,7 @@
"culture": 1,
"greatPersonPoints": {"Great Engineer": 1},
"isWonder": true,
"uniques": ["-[25]% Culture cost of acquiring tiles [in all cities]","-[25]% Gold cost of acquiring tiles [in all cities]"],
"uniques": ["[-25]% Culture cost of natural border growth [in all cities]","[-25]% Gold cost of acquiring tiles [in all cities]"],
"requiredTech": "Theology",
"quote": "'The temple is like no other building in the world. It has towers and decoration and all the refinements which the human genius can conceive of.' - Antonio da Magdalena"
},

View File

@ -262,7 +262,7 @@
"outerColor": [ 28,51,119],
"innerColor": [255,255,255],
"uniqueName": "Manifest Destiny",
"uniques": ["[+1] Sight <for [{Military} {Land}] units>", "-[50]% Gold cost of acquiring tiles [in all cities]"],
"uniques": ["[+1] Sight <for [{Military} {Land}] units>", "[-50]% Gold cost of acquiring tiles [in all cities]"],
"cities": ["Washington","New York","Boston","Philadelphia","Atlanta","Chicago","Seattle","San Francisco","Los Angeles","Houston",
"Portland","St. Louis","Miami","Buffalo","Detroit","New Orleans","Baltimore","Denver","Cincinnati","Dallas","Memphis",
"Cleveland","Kansas City","San Diego","Richmond","Las Vegas","Phoenix","Albuquerque","Minneapolis","Pittsburgh",

View File

@ -2,7 +2,7 @@
{
"name": "Tradition",
"era": "Ancient era",
"uniques": ["[+3 Culture] [in capital]", "-[25]% Culture cost of acquiring tiles [in all cities]"],
"uniques": ["[+3 Culture] [in capital]", "[-25]% Culture cost of natural border growth [in all cities]"],
"policies": [
{
"name": "Aristocracy",
@ -18,7 +18,7 @@
},
{
"name": "Oligarchy",
"uniques": ["Units in cities cost no Maintenance", "+[100]% attacking strength for cities with garrisoned units"],
"uniques": ["Units in cities cost no Maintenance", "[+100]% Strength for cities <with a garrison> <when attacking>"],
"row": 1,
"column": 5
},

View File

@ -149,7 +149,7 @@ object GameStarter {
civInfo.tech.addTechnology(tech)
// generic start with technology unique
for (unique in civInfo.getMatchingUniques("Starts with []")) {
for (unique in civInfo.getMatchingUniques(UniqueType.StartsWithTech)) {
// get the parameter from the unique
val techName = unique.params[0]

View File

@ -109,8 +109,8 @@ object BattleHelper {
return false
if (combatant is MapUnitCombatant &&
combatant.unit.hasUnique("Can only attack [] tiles") &&
combatant.unit.getMatchingUniques("Can only attack [] tiles").none { tile.matchesFilter(it.params[0]) }
combatant.unit.getMatchingUniques(UniqueType.CanOnlyAttackTiles)
.let { unique -> unique.any() && unique.none { tile.matchesFilter(it.params[0]) } }
)
return false

View File

@ -73,7 +73,7 @@ object ChooseBeliefsAutomation {
// If obsoleted, continue
score += modifier * when (unique.placeholderText) {
UniqueType.GrowthPercentBonus.placeholderText -> unique.params[0].toFloat() / 3f
"[]% cost of natural border growth" -> -unique.params[0].toFloat() * 2f / 10f
UniqueType.BorderGrowthPercentage.placeholderText -> -unique.params[0].toFloat() * 2f / 10f
"[]% Strength for cities" -> unique.params[0].toFloat() / 10f // Modified by personality
"[] Units adjacent to this city heal [] HP per turn when healing" -> unique.params[1].toFloat() / 10f
"+[]% Production when constructing []" -> unique.params[0].toFloat() / 3f

View File

@ -325,10 +325,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
}
private fun addFoodBuildingChoice() {
val foodBuilding = buildableNotWonders.asSequence().filter { (it.isStatRelated(Stat.Food)
|| it.uniqueObjects.any { it.placeholderText=="[]% of food is carried over after population increases" })
&& Automation.allowSpendingResource(civInfo, it) }
.minByOrNull { it.cost }
val foodBuilding = buildableNotWonders.asSequence()
.filter {
(it.isStatRelated(Stat.Food) || it.hasUnique(UniqueType.CarryOverFood))
&& Automation.allowSpendingResource(civInfo, it)
}.minByOrNull { it.cost }
if (foodBuilding != null) {
var modifier = 1f
if (cityInfo.population.population < 5) modifier = 1.3f

View File

@ -76,7 +76,7 @@ object Battle {
// Withdraw from melee ability
if (attacker is MapUnitCombatant && attacker.isMelee() && defender is MapUnitCombatant) {
val withdraw = defender.unit.getMatchingUniques("May withdraw before melee ([]%)")
val withdraw = defender.unit.getMatchingUniques(UniqueType.MayWithdraw)
.maxByOrNull{ it.params[0] } // If a mod allows multiple withdraw properties, ensure the best is used
if (withdraw != null && doWithdrawFromMeleeAbility(attacker, defender, withdraw)) return
}
@ -196,7 +196,9 @@ object Battle {
// https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines
if (attacker.unit.getMatchingUniques("May capture killed [] units").none { defender.matchesCategory(it.params[0]) }) return false
if (attacker.unit.getMatchingUniques(UniqueType.KillUnitCapture)
.none { defender.matchesCategory(it.params[0]) }
) return false
val captureChance = min(0.8f, 0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength().toFloat() * 0.4f)
if (Random().nextFloat() > captureChance) return false
@ -247,9 +249,6 @@ object Battle {
plunderFromDamage(attacker, defender, defenderHealthBefore - defender.getHealth())
}
private object PlunderableStats {
val stats = setOf (Stat.Gold, Stat.Science, Stat.Culture, Stat.Faith)
}
private fun plunderFromDamage(
plunderingUnit: ICombatant,
plunderedUnit: ICombatant,
@ -259,16 +258,10 @@ object Battle {
if (plunderingUnit !is MapUnitCombatant) return
val plunderedGoods = Stats()
for (unique in plunderingUnit.unit.getMatchingUniques("Earn []% of the damage done to [] units as []")) {
for (unique in plunderingUnit.unit.getMatchingUniques(UniqueType.DamageUnitsPlunder, checkCivInfoUniques = true)) {
if (plunderedUnit.matchesCategory(unique.params[1])) {
// silently ignore bad mods here - or test in checkModLinks
val stat = Stat.values().firstOrNull { it.name == unique.params[2] }
?: continue // stat badly defined in unique
if (stat !in PlunderableStats.stats)
continue // stat known but not valid
val percentage = unique.params[0].toFloatOrNull()
?: continue // percentage parameter invalid
plunderedGoods.add(stat, percentage / 100f * damageDealt)
val percentage = unique.params[0].toFloat()
plunderedGoods.add(Stat.valueOf(unique.params[2]), percentage / 100f * damageDealt)
}
}
@ -327,7 +320,7 @@ object Battle {
private fun tryHealAfterKilling(attacker: ICombatant) {
if (attacker is MapUnitCombatant)
for (unique in attacker.unit.getMatchingUniques(UniqueType.HealsAfterKilling)) {
for (unique in attacker.unit.getMatchingUniques(UniqueType.HealsAfterKilling, checkCivInfoUniques = true)) {
val amountToHeal = unique.params[0].toInt()
attacker.unit.healBy(amountToHeal)
}

View File

@ -36,9 +36,10 @@ object BattleDamage {
if (combatAction == CombatAction.Attack) enemy.getTile()
else combatant.getTile()
val conditionalState = StateForConditionals(civInfo, ourCombatant = combatant, theirCombatant = enemy,
val conditionalState = StateForConditionals(civInfo, cityInfo = (combatant as? CityCombatant)?.city, ourCombatant = combatant, theirCombatant = enemy,
attackedTile = attackedTile, combatAction = combatAction)
if (combatant is MapUnitCombatant) {
for (unique in combatant.getMatchingUniques(UniqueType.Strength, conditionalState, true)) {
@ -118,7 +119,7 @@ object BattleDamage {
)
modifiers["vs [City-States]"] = 30
} else if (combatant is CityCombatant) {
for (unique in combatant.getCivInfo().getMatchingUniques(UniqueType.StrengthForCities, conditionalState)) {
for (unique in combatant.city.getMatchingUniques(UniqueType.StrengthForCities, conditionalState)) {
modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt())
}
}
@ -153,7 +154,7 @@ object BattleDamage {
}
if (numberOfAttackersSurroundingDefender > 1) {
var flankingBonus = 10f //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
for (unique in attacker.unit.getMatchingUniques("[]% to Flank Attack bonuses"))
for (unique in attacker.unit.getMatchingUniques(UniqueType.FlankAttackBonus, checkCivInfoUniques = true))
flankingBonus *= unique.params[0].toPercent()
modifiers["Flanking"] =
(flankingBonus * (numberOfAttackersSurroundingDefender - 1)).toInt()
@ -180,15 +181,17 @@ object BattleDamage {
}
} else if (attacker is CityCombatant) {
if (attacker.city.getCenterTile().militaryUnit != null) {
val garrisonBonus = attacker.city.getMatchingUniques("+[]% attacking strength for cities with garrisoned units")
.sumOf { it.params[0].toInt() }
if (garrisonBonus != 0)
modifiers["Garrisoned unit"] = garrisonBonus
}
for (unique in attacker.city.getMatchingUniques(UniqueType.StrengthForCitiesAttacking)) {
modifiers.add("Attacking Bonus", unique.params[0].toInt())
}
// Deprecated since 3.19.1
if (attacker.city.getCenterTile().militaryUnit != null) {
val garrisonBonus = attacker.city.getMatchingUniques(UniqueType.StrengthForGarrisonedCitiesAttacking)
.sumOf { it.params[0].toInt() }
if (garrisonBonus != 0)
modifiers["Garrisoned unit"] = garrisonBonus
}
for (unique in attacker.city.getMatchingUniques(UniqueType.StrengthForCitiesAttacking)) {
modifiers.add("Attacking Bonus", unique.params[0].toInt())
}
//
}
return modifiers
@ -202,7 +205,7 @@ object BattleDamage {
if (defender.unit.isEmbarked()) {
// embarked units get no defensive modifiers apart from this unique
if (defender.unit.hasUnique(UniqueType.DefenceBonusWhenEmbarked) ||
if (defender.unit.hasUnique(UniqueType.DefenceBonusWhenEmbarked, checkCivInfoUniques = true) ||
defender.getCivInfo().hasUnique(UniqueType.DefenceBonusWhenEmbarkedCivwide)
)
modifiers["Embarked"] = 100
@ -213,8 +216,8 @@ object BattleDamage {
modifiers.putAll(getTileSpecificModifiers(defender, tile))
val tileDefenceBonus = tile.getDefensiveBonus()
if (!defender.unit.hasUnique(UniqueType.NoDefensiveTerrainBonus) && tileDefenceBonus > 0
|| !defender.unit.hasUnique(UniqueType.NoDefensiveTerrainPenalty) && tileDefenceBonus < 0
if (!defender.unit.hasUnique(UniqueType.NoDefensiveTerrainBonus, checkCivInfoUniques = true) && tileDefenceBonus > 0
|| !defender.unit.hasUnique(UniqueType.NoDefensiveTerrainPenalty, checkCivInfoUniques = true) && tileDefenceBonus < 0
)
modifiers["Tile"] = (tileDefenceBonus * 100).toInt()

View File

@ -5,6 +5,7 @@ import com.unciv.logic.automation.Automation
import com.unciv.logic.civilization.LocationAction
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.utils.toPercent
import com.unciv.ui.utils.withItem
import com.unciv.ui.utils.withoutItem
@ -34,19 +35,26 @@ class CityExpansionManager {
// https://www.reddit.com/r/civ/comments/58rxkk/how_in_gods_name_do_borders_expand_in_civ_vi/ has it
// (per game XML files) at 6*(t+0.4813)^1.3
// The second seems to be more based, so I'll go with that
// -- Note (added later) that this last link is specific to civ VI and not civ V
fun getCultureToNextTile(): Int {
var cultureToNextTile = 6 * (max(0, tilesClaimed()) + 1.4813).pow(1.3)
if (cityInfo.civInfo.isCityState())
cultureToNextTile *= 1.5f // City states grow slower, perhaps 150% cost?
for (unique in cityInfo.getMatchingUniques("-[]% Culture cost of acquiring tiles []")) {
if (cityInfo.matchesFilter(unique.params[1]))
cultureToNextTile /= unique.params[0].toPercent()
}
// Deprecated since 3.19.1
for (unique in cityInfo.getMatchingUniques(UniqueType.DecreasedAcquiringTilesCost)) {
if (cityInfo.matchesFilter(unique.params[1]))
cultureToNextTile /= unique.params[0].toPercent()
}
for (unique in cityInfo.getMatchingUniques("[]% cost of natural border growth"))
cultureToNextTile *= unique.params[0].toPercent()
for (unique in cityInfo.getMatchingUniques(UniqueType.CostOfNaturalBorderGrowth))
cultureToNextTile *= unique.params[0].toPercent()
//
for (unique in cityInfo.getMatchingUniquesWithNonLocalEffects(UniqueType.BorderGrowthPercentage))
if (cityInfo.matchesFilter(unique.params[1]))
cultureToNextTile *= unique.params[0].toPercent()
return cultureToNextTile.roundToInt()
}
@ -66,10 +74,18 @@ class CityExpansionManager {
val distanceFromCenter = tileInfo.aerialDistanceTo(cityInfo.getCenterTile())
var cost = baseCost * (distanceFromCenter - 1) + tilesClaimed() * 5.0
for (unique in cityInfo.getMatchingUniques("-[]% Gold cost of acquiring tiles []")) {
// Deprecated since 3.19.1
for (unique in cityInfo.getMatchingUniques(UniqueType.TileCostPercentageDiscount)) {
if (cityInfo.matchesFilter(unique.params[1]))
cost *= (100 - unique.params[0].toFloat()) / 100
}
//
for (unique in cityInfo.getMatchingUniques(UniqueType.TileCostPercentage)) {
if (cityInfo.matchesFilter(unique.params[1]))
cost *= (100 - unique.params[0].toFloat()) / 100
cost *= unique.params[0].toPercent()
}
return cost.roundToInt()
}

View File

@ -277,9 +277,12 @@ class MapUnit {
return tempUniques.any { it.placeholderText == unique }
}
fun hasUnique(uniqueType: UniqueType, stateForConditionals: StateForConditionals
= StateForConditionals(civInfo, unit=this)): Boolean {
return getMatchingUniques(uniqueType, stateForConditionals).any()
fun hasUnique(
uniqueType: UniqueType,
stateForConditionals: StateForConditionals = StateForConditionals(civInfo, unit=this),
checkCivInfoUniques: Boolean = false
): Boolean {
return getMatchingUniques(uniqueType, stateForConditionals, checkCivInfoUniques).any()
}
fun updateUniques(ruleset: Ruleset) {
@ -458,7 +461,7 @@ class MapUnit {
fun getRange(): Int {
if (baseUnit.isMelee()) return 1
var range = baseUnit().range
range += getMatchingUniques(UniqueType.Range).sumOf { it.params[0].toInt() }
range += getMatchingUniques(UniqueType.Range, checkCivInfoUniques = true).sumOf { it.params[0].toInt() }
return range
}
@ -686,7 +689,11 @@ class MapUnit {
if (civInfo.hasUnique("Can only heal by pillaging")) return
var amountToHealBy = rankTileForHealing(getTile())
if (amountToHealBy == 0 && !(hasUnique(UniqueType.HealsOutsideFriendlyTerritory) && !getTile().isFriendlyTerritory(civInfo))) return
if (amountToHealBy == 0
&& !(hasUnique(UniqueType.HealsOutsideFriendlyTerritory, checkCivInfoUniques = true)
&& !getTile().isFriendlyTerritory(civInfo)
)
) return
amountToHealBy += getMatchingUniques("[] HP when healing").sumOf { it.params[0].toInt() }
@ -699,7 +706,7 @@ class MapUnit {
}
fun healBy(amount: Int) {
health += if (hasUnique(UniqueType.HealingEffectsDoubled))
health += if (hasUnique(UniqueType.HealingEffectsDoubled, checkCivInfoUniques = true))
amount * 2
else
amount

View File

@ -755,8 +755,9 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
fun isStatRelated(stat: Stat): Boolean {
if (get(stat) > 0) return true
if (getStatPercentageBonuses(null)[stat] > 0) return true
if (uniqueObjects.any { it.isOfType(UniqueType.StatsPerPopulation) && it.stats[stat] > 0 }) return true
if (uniqueObjects.any { it.isOfType(UniqueType.StatsFromTiles) && it.stats[stat] > 0 }) return true
if (getMatchingUniques(UniqueType.Stats).any { it.stats[stat] > 0 }) return true
if (getMatchingUniques(UniqueType.StatsFromTiles).any { it.stats[stat] > 0 }) return true
if (getMatchingUniques(UniqueType.StatsPerPopulation).any { it.stats[stat] > 0 }) return true
return false
}

View File

@ -77,6 +77,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
UniqueType.ConditionalSpecialistCount ->
state.cityInfo != null && state.cityInfo.population.getNumberOfSpecialists() >= condition.params[0].toInt()
UniqueType.ConditionalWhenGarrisoned ->
state.cityInfo != null && state.cityInfo.getCenterTile().militaryUnit != null && state.cityInfo.getCenterTile().militaryUnit!!.canGarrison()
UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesCategory("City") == true
UniqueType.ConditionalVsUnits -> state.theirCombatant?.matchesCategory(condition.params[0]) == true

View File

@ -84,6 +84,16 @@ enum class UniqueParameterType(val parameterName:String) {
return UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
}
},
PlunderableStatName("plunderableStat") {
private val knownValues = setOf(Stat.Gold.name, Stat.Science.name, Stat.Culture.name, Stat.Faith.name)
override fun getErrorSeverity(
parameterText: String,
ruleset: Ruleset
): UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null
return UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
}
},
CityFilter("cityFilter") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
@ -129,7 +139,7 @@ enum class UniqueParameterType(val parameterName:String) {
private val knownValues = setOf("All",
"Coastal", "River", "Open terrain", "Rough terrain", "Water resource",
"Foreign Land", "Foreign", "Friendly Land", "Friendly", "Enemy Land", "Enemy",
"Featureless", "Fresh Water")
"Featureless", "Fresh Water", "Natural Wonder")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null

View File

@ -137,7 +137,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// endregion
/////// Other global uniques
/////// region Other global uniques
FreeUnits("[amount] units cost no maintenance", UniqueTarget.Global),
@ -145,7 +145,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
ProvidesResources("Provides [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Building),
GrowthPercentBonus("[amount]% growth [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
CarryOverFood("[amount]% of food is carried over after population increases", UniqueTarget.Global, UniqueTarget.FollowerBelief),
GainFreeBuildings("Gain a free [buildingName] [cityFilter]", UniqueTarget.Global),
@ -162,6 +162,14 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
@Deprecated("As of 3.18.2", ReplaceWith("[50]% of excess happiness converted to [Culture]"), DeprecationLevel.WARNING)
ExcessHappinessToCultureDeprecated("50% of excess happiness added to culture towards policies", UniqueTarget.Global),
BorderGrowthPercentage("[amount] Culture cost of natural border growth [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@Deprecated("As of 3.19.1", ReplaceWith("[-amount] Culture cost of natural border growth [cityFilter]"))
DecreasedAcquiringTilesCost("-[amount]% Culture cost of acquiring tiles [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@Deprecated("As of 3.19.1", ReplaceWith("[amount] Culture cost of natural border growth [cityFilter]"))
CostOfNaturalBorderGrowth("[amount]% cost of natural border growth", UniqueTarget.Global, UniqueTarget.FollowerBelief),
TileCostPercentage("[amount]% Gold cost of acquiring tiles [cityFilter]", UniqueTarget.FollowerBelief, UniqueTarget.Global),
@Deprecated("As of 3.19.1", ReplaceWith("[-amount]% Gold cost of acquiring tiles [cityFilter]"))
TileCostPercentageDiscount("-[amount]% Gold cost of acquiring tiles [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
// There is potential to merge these
BuyUnitsIncreasingCost("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount])", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@ -238,11 +246,13 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
@Deprecated("As of 3.18.17", ReplaceWith("[+amount]% Golden Age length"))
GoldenAgeLengthIncreased("Golden Age length increased by [amount]%", UniqueTarget.Global),
StrengthForCities("[amount]% Strength for cities", UniqueTarget.Global),
StrengthForCities("[amount]% Strength for cities", UniqueTarget.Global, UniqueTarget.FollowerBelief),
@Deprecated("As of 3.18.17", ReplaceWith("[+amount]% Strength for cities <when defending>"))
StrengthForCitiesDefending("+[amount]% Defensive Strength for cities", UniqueTarget.Global),
@Deprecated("As of 3.18.17", ReplaceWith("[amount]% Strength for cities <when attacking>"))
StrengthForCitiesAttacking("[amount]% Attacking Strength for cities", UniqueTarget.Global),
@Deprecated("As of 3.19.1", ReplaceWith("[amount]% Strength for cities <with a garrison> <when attacking>"))
StrengthForGarrisonedCitiesAttacking("+[amount]% attacking strength for cities with garrisoned units", UniqueTarget.Global),
UnitStartingExperience("New [baseUnitFilter] units start with [amount] Experience [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
// ToDo: make per unit and use unit filters?
@ -251,8 +261,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
IncompatibleWith("Incompatible with [policy/tech/promotion]", UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion),
StartingTech("Starting tech", UniqueTarget.Tech),
StartsWithTech("Starts with [tech]", UniqueTarget.Nation),
ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global),
//endregion
//endregion Global uniques
///////////////////////////////////////// region CONSTRUCTION UNIQUES /////////////////////////////////////////
@ -306,32 +319,39 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global),
StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit, UniqueTarget.Global),
FlankAttackBonus("[amount]% to Flank Attack bonuses", UniqueTarget.Unit, UniqueTarget.Global),
Movement("[amount] Movement", UniqueTarget.Unit, UniqueTarget.Global),
Sight("[amount] Sight", UniqueTarget.Unit, UniqueTarget.Global, UniqueTarget.Terrain),
Range("[amount] Range", UniqueTarget.Unit, UniqueTarget.Global),
SpreadReligionStrength("[amount]% Spread Religion Strength", UniqueTarget.Unit, UniqueTarget.Global),
MayFoundReligion("May found a religion", UniqueTarget.Unit),
MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit),
CanOnlyAttackUnits("Can only attack [combatantFilter] units", UniqueTarget.Unit),
CanOnlyAttackTiles("Can only attack [tileFilter] tiles", UniqueTarget.Unit),
CannotAttack("Cannot attack", UniqueTarget.Unit),
MustSetUp("Must set up to ranged attack", UniqueTarget.Unit),
SelfDestructs("Self-destructs when attacking", UniqueTarget.Unit),
BlastRadius("Blast radius [amount]", UniqueTarget.Unit),
NoDefensiveTerrainBonus("No defensive terrain bonus", UniqueTarget.Unit, UniqueTarget.Global),
NoDefensiveTerrainPenalty("No defensive terrain penalty", UniqueTarget.Unit, UniqueTarget.Global),
Uncapturable("Uncapturable", UniqueTarget.Unit),
SelfDestructs("Self-destructs when attacking", UniqueTarget.Unit),
HealsEvenAfterAction("Unit will heal every turn, even if it performs an action", UniqueTarget.Unit),
MayWithdraw("May withdraw before melee ([amount]%)", UniqueTarget.Unit),
NoMovementToPillage("No movement cost to pillage", UniqueTarget.Unit, UniqueTarget.Global),
@Deprecated("As of 3.18.17", ReplaceWith("No movement cost to pillage <for [Melee] units>"), DeprecationLevel.WARNING)
NoMovementToPillageMelee("Melee units pay no movement cost to pillage", UniqueTarget.Unit, UniqueTarget.Global),
CanMoveAfterAttacking("Can move after attacking", UniqueTarget.Unit),
MoveImmediatelyOnceBought("Can move immediately once bought", UniqueTarget.Unit),
BlastRadius("Blast radius [amount]", UniqueTarget.Unit),
HealsOutsideFriendlyTerritory("May heal outside of friendly territory", UniqueTarget.Unit, UniqueTarget.Global),
HealingEffectsDoubled("All healing effects doubled", UniqueTarget.Unit, UniqueTarget.Global),
HealsAfterKilling("Heals [amount] damage if it kills a unit", UniqueTarget.Unit, UniqueTarget.Global),
HealsEvenAfterAction("Unit will heal every turn, even if it performs an action", UniqueTarget.Unit),
NormalVisionWhenEmbarked("Normal vision when embarked", UniqueTarget.Unit, UniqueTarget.Global),
DefenceBonusWhenEmbarked("Defense bonus when embarked", UniqueTarget.Unit, UniqueTarget.Global),
@ -347,9 +367,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
UnitUpgradeCost("[amount]% Gold cost of upgrading", UniqueTarget.Unit, UniqueTarget.Global),
GreatPersonEarnedFaster("[greatPerson] is earned [amount]% faster", UniqueTarget.Unit, UniqueTarget.Global),
CaptureCityPlunder("Upon capturing a city, receive [amount] times its [stat] production as [stat] immediately", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunder("Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat]", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunderNearCity("Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [stat] when killed within 4 tiles of a city following this religion", UniqueTarget.FollowerBelief),
DamageUnitsPlunder("Earn [amount]% of the damage done to [mapUnitFilter] units as [plunderableStat]", UniqueTarget.Unit, UniqueTarget.Global),
CaptureCityPlunder("Upon capturing a city, receive [amount] times its [stat] production as [plunderableStat] immediately", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunder("Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [plunderableStat]", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunderNearCity("Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [plunderableStat] when killed within 4 tiles of a city following this religion", UniqueTarget.FollowerBelief),
KillUnitCapture("May capture killed [mapUnitFilter] units", UniqueTarget.Unit),
FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
PercentageXPGain("[amount]% XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
@ -371,10 +393,11 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
CannotEnterOcean("Cannot enter ocean tiles", UniqueTarget.Unit),
@Deprecated("As of 3.18.6", ReplaceWith("Cannot enter ocean tiles <before discovering [Astronomy]>"))
CannotEnterOceanUntilAstronomy("Cannot enter ocean tiles until Astronomy", UniqueTarget.Unit),
CannotBeBarbarian("Never appears as a Barbarian unit", UniqueTarget.Unit, flags = listOf(UniqueFlag.HiddenToUsers)),
CanEnterForeignTiles("May enter foreign tiles without open borders", UniqueTarget.Unit),
CanEnterForeignTilesButLosesReligiousStrength("May enter foreign tiles without open borders, but loses [amount] religious strength each turn it ends there", UniqueTarget.Unit),
CannotBeBarbarian("Never appears as a Barbarian unit", UniqueTarget.Unit, flags = listOf(UniqueFlag.HiddenToUsers)),
ReligiousUnit("Religious Unit", UniqueTarget.Unit),
SpaceshipPart("Spaceship part", UniqueTarget.Building, UniqueTarget.Unit),
@ -392,7 +415,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
///////////////////////////////////////// region TILE UNIQUES /////////////////////////////////////////
// region natural wonders
// Natural wonders
NaturalWonderNeighborCount("Must be adjacent to [amount] [simpleTerrain] tiles", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HiddenToUsers)),
NaturalWonderNeighborsRange("Must be adjacent to [amount] to [amount] [simpleTerrain] tiles", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HiddenToUsers)),
NaturalWonderSmallerLandmass("Must not be on [amount] largest landmasses", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HiddenToUsers)),
@ -403,8 +426,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// The "Except [terrainFilter]" could theoretically be implemented with a conditional
NaturalWonderConvertNeighborsExcept("Neighboring tiles except [baseTerrain] will convert to [baseTerrain]", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HiddenToUsers)),
GrantsGoldToFirstToDiscover("Grants 500 Gold to the first civilization to discover it", UniqueTarget.Terrain),
// endregion
// General terrain
DamagesContainingUnits("Units ending their turn on this terrain take [amount] damage", UniqueTarget.Terrain),
TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain),
GrantsCityStrength("[amount] Strength for cities built on this terrain", UniqueTarget.Terrain),
@ -456,9 +479,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
CityStateOnlyResource("Can only be created by Mercantile City-States", UniqueTarget.Resource),
//endregion
////// region Improvement uniques
////// Improvement uniques
ImprovementBuildableByFreshWater("Can also be built on tiles adjacent to fresh water", UniqueTarget.Improvement),
ImprovementStatsOnTile("[stats] from [tileFilter] tiles", UniqueTarget.Improvement),
ImprovementStatsForAdjacencies("[stats] for each adjacent [tileFilter]", UniqueTarget.Improvement),
@ -504,6 +526,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
/////// city conditionals
ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional),
ConditionalWhenGarrisoned("with a garrison", UniqueTarget.Conditional),
/////// unit conditionals
ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional),
@ -574,7 +597,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
//endregion
///////////////////////////////////////////// META /////////////////////////////////////////////
///////////////////////////////////////////// region META /////////////////////////////////////////////
AvailableAfterCertainTurns("Only available after [amount] turns", UniqueTarget.Ruins),
HiddenWithoutReligion("Hidden when religion is disabled", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Ruins, flags = listOf(UniqueFlag.HiddenToUsers)),
@ -584,6 +607,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = listOf(UniqueFlag.HiddenToUsers)),
HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.values(), flags = listOf(UniqueFlag.HiddenToUsers)),
// endregion
// region DEPRECATED AND REMOVED
@ -711,3 +735,4 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
return errorList
}
}

View File

@ -214,7 +214,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> <above [amount] HP> <below [amount] HP> <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]>"
"<with a garrison> <for [mapUnitFilter] units> <above [amount] HP> <below [amount] HP> <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

@ -285,10 +285,10 @@ object UnitActions {
tile.improvement = null
if (tile.resource != null) tile.getOwner()?.updateDetailedCivResources() // this might take away a resource
val freePillage = unit.hasUnique(UniqueType.NoMovementToPillage)
|| unit.civInfo.hasUnique(UniqueType.NoMovementToPillage)
// Deprecated 3.18.17
|| (unit.baseUnit.isMelee() && unit.civInfo.hasUnique(UniqueType.NoMovementToPillageMelee))
val freePillage = unit.hasUnique(UniqueType.NoMovementToPillage, checkCivInfoUniques = true)
// Deprecated 3.18.17
|| (unit.baseUnit.isMelee() && unit.civInfo.hasUnique(UniqueType.NoMovementToPillageMelee))
//
if (!freePillage) unit.useMovementPoints(1f)
unit.healBy(25)

View File

@ -16,17 +16,38 @@ class UniqueDocsWriter {
.toSortedMap()
fun replaceExamples(text:String):String {
return text.replace("[amount]", "[20]")
.replace("[stat]", "[Culture]")
return text
.replace("[amount]", "[20]")
.replace("[combatantFilter]", "[City]")
.replace("[mapUnitFilter]", "[Wounded]")
.replace("[baseUnitFilter]", "[Melee]")
.replace("[great person]", "[Great Scientist]")
.replace("[stats]", "[+1 Gold, +2 Production]")
.replace("[stat]", "[Culture]")
.replace("[plunderableStat]", "[Gold]")
.replace("[cityFilter]", "[in all cities]")
.replace("[buildingName]", "[Library]")
.replace("[buildingFilter]", "[Culture]")
.replace("[constructionFilter]", "[Spaceship Part]")
.replace("[terrainFilter]", "[Forest]")
.replace("[tileFilter]", "[Farm]")
.replace("[terrainFilter]", "[Grassland]")
.replace("[baseUnitFilter]", "[Melee]")
.replace("[mapUnitFilter]", "[Wounded]")
.replace("[simpleTerrain]", "[Elevated]")
.replace("[baseTerrain]", "[Grassland]")
.replace("[regionType]", "[Hybrid]")
.replace("[terrainQuality]","[Undesirable]")
.replace("[promotion]","[Shock I]")
.replace("[era]", "[Ancient era]")
.replace("[improvementName]", "[Trading Post]")
.replace("[improvementFilter]", "[All Road]")
.replace("[resource]", "[Iron]")
.replace("[beliefType]", "[Follower]")
.replace("[belief]","[God of War]")
.replace("[foundingOrEnhancing]", "[founding]")
.replace("[tech]", "[Agriculture]")
.replace("[specialist]","[Merchant]")
.replace("[policy]", "[Oligarchy]")
.replace("[victoryType]", "[Domination]")
.replace("[costOrStrength]", "[Cost]")
}
lines += "## Table of Contents\n"
@ -38,7 +59,6 @@ class UniqueDocsWriter {
lines += ""
val deprecatedUniques = ArrayList<UniqueType>()
for (targetType in targetTypesToUniques) {
lines += "## " + targetType.key.name + " uniques"

View File

@ -42,7 +42,7 @@ Example: "[+1 Gold, +2 Production] in cities with [20] or more population"
Applicable to: Global, FollowerBelief
#### [stats] in cities on [terrainFilter] tiles
Example: "[+1 Gold, +2 Production] in cities on [Grassland] tiles"
Example: "[+1 Gold, +2 Production] in cities on [Forest] tiles"
Applicable to: Global, FollowerBelief
@ -97,12 +97,12 @@ Example: "[20]% [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
#### [amount]% Production when constructing [buildingFilter] wonders [cityFilter]
Example: "[20]% Production when constructing [buildingFilter] wonders [in all cities]"
Example: "[20]% Production when constructing [Culture] wonders [in all cities]"
Applicable to: Global, FollowerBelief, Resource
#### [amount]% Production when constructing [buildingFilter] buildings [cityFilter]
Example: "[20]% Production when constructing [buildingFilter] buildings [in all cities]"
Example: "[20]% Production when constructing [Culture] buildings [in all cities]"
Applicable to: Global, FollowerBelief
@ -172,18 +172,23 @@ Example: "[20]% growth [in all cities]"
Applicable to: Global, FollowerBelief
#### [amount]% of food is carried over after population increases
Example: "[20]% of food is carried over after population increases"
Applicable to: Global, FollowerBelief
#### Gain a free [buildingName] [cityFilter]
Example: "Gain a free [Library] [in all cities]"
Applicable to: Global
#### May choose [amount] additional [beliefType] beliefs when [foundingOrEnhancing] a religion
Example: "May choose [20] additional [Follower] beliefs when [foundingOrEnhancing] a religion"
Example: "May choose [20] additional [Follower] beliefs when [founding] a religion"
Applicable to: Global
#### May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion
Example: "May choose [20] additional belief(s) of any type when [foundingOrEnhancing] a religion"
Example: "May choose [20] additional belief(s) of any type when [founding] a religion"
Applicable to: Global
@ -207,13 +212,23 @@ Example: "[20]% of excess happiness converted to [Culture]"
Applicable to: Global
#### [amount] Culture cost of natural border growth [cityFilter]
Example: "[20] Culture cost of natural border growth [in all cities]"
Applicable to: Global, FollowerBelief
#### [amount]% Gold cost of acquiring tiles [cityFilter]
Example: "[20]% Gold cost of acquiring tiles [in all cities]"
Applicable to: Global, FollowerBelief
#### May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount])
Example: "May buy [Melee] units for [20] [Culture] [in all cities] at an increasing price ([20])"
Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount])
Example: "May buy [buildingFilter] buildings for [20] [Culture] [in all cities] at an increasing price ([20])"
Example: "May buy [Culture] buildings for [20] [Culture] [in all cities] at an increasing price ([20])"
Applicable to: Global, FollowerBelief
@ -223,7 +238,7 @@ Example: "May buy [Melee] units for [20] [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings for [amount] [stat] [cityFilter]
Example: "May buy [buildingFilter] buildings for [20] [Culture] [in all cities]"
Example: "May buy [Culture] buildings for [20] [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
@ -233,7 +248,7 @@ Example: "May buy [Melee] units with [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings with [stat] [cityFilter]
Example: "May buy [buildingFilter] buildings with [Culture] [in all cities]"
Example: "May buy [Culture] buildings with [Culture] [in all cities]"
Applicable to: Global, FollowerBelief
@ -243,7 +258,7 @@ Example: "May buy [Melee] units with [Culture] for [20] times their normal Produ
Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost
Example: "May buy [buildingFilter] buildings with [Culture] for [20] times their normal Production cost"
Example: "May buy [Culture] buildings with [Culture] for [20] times their normal Production cost"
Applicable to: Global, FollowerBelief
@ -259,7 +274,7 @@ Example: "[Culture] cost of purchasing items in cities [20]%"
Applicable to: Global, FollowerBelief
#### [stat] cost of purchasing [buildingFilter] buildings [amount]%
Example: "[Culture] cost of purchasing [buildingFilter] buildings [20]%"
Example: "[Culture] cost of purchasing [Culture] buildings [20]%"
Applicable to: Global, FollowerBelief
@ -285,7 +300,7 @@ Example: "[20]% maintenance cost for buildings [in all cities]"
Applicable to: Global, FollowerBelief
#### Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once.
Example: "Receive a free Great Person at the end of every [comment] (every 394 years), after researching [tech]. Each bonus person can only be chosen once."
Example: "Receive a free Great Person at the end of every [comment] (every 394 years), after researching [Agriculture]. Each bonus person can only be chosen once."
Applicable to: Global
@ -357,7 +372,7 @@ Applicable to: Global
#### [amount]% Strength for cities
Example: "[20]% Strength for cities"
Applicable to: Global
Applicable to: Global, FollowerBelief
#### New [baseUnitFilter] units start with [amount] Experience [cityFilter]
Example: "New [Melee] units start with [20] Experience [in all cities]"
@ -383,6 +398,11 @@ Example: "[20]% Strength decreasing with distance from the capital"
Applicable to: Global, Unit
#### [amount]% to Flank Attack bonuses
Example: "[20]% to Flank Attack bonuses"
Applicable to: Global, Unit
#### [amount] Movement
Example: "[20] Movement"
@ -447,13 +467,18 @@ Example: "[greatPerson] is earned [20]% faster"
Applicable to: Global, Unit
#### Upon capturing a city, receive [amount] times its [stat] production as [stat] immediately
Example: "Upon capturing a city, receive [20] times its [Culture] production as [Culture] immediately"
#### Earn [amount]% of the damage done to [mapUnitFilter] units as [plunderableStat]
Example: "Earn [20]% of the damage done to [Wounded] units as [Gold]"
Applicable to: Global, Unit
#### Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat]
Example: "Earn [20]% of killed [Wounded] unit's [costOrStrength] as [Culture]"
#### Upon capturing a city, receive [amount] times its [stat] production as [plunderableStat] immediately
Example: "Upon capturing a city, receive [20] times its [Culture] production as [Gold] immediately"
Applicable to: Global, Unit
#### Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [plunderableStat]
Example: "Earn [20]% of killed [Wounded] unit's [Cost] as [Gold]"
Applicable to: Global, Unit
@ -514,12 +539,12 @@ Applicable to: Global
Applicable to: Global
#### This Unit gains the [promotion] promotion
Example: "This Unit gains the [promotion] promotion"
Example: "This Unit gains the [Shock I] promotion"
Applicable to: Global
#### [mapUnitFilter] units gain the [promotion] promotion
Example: "[Wounded] units gain the [promotion] promotion"
Example: "[Wounded] units gain the [Shock I] promotion"
Applicable to: Global
@ -547,6 +572,11 @@ Applicable to: Global
#### Will not be chosen for new games
Applicable to: Nation
#### Starts with [tech]
Example: "Starts with [Agriculture]"
Applicable to: Nation
## Tech uniques
#### Incompatible with [policy/tech/promotion]
Example: "Incompatible with [policy/tech/promotion]"
@ -562,8 +592,8 @@ Example: "[20]% [Culture] from every follower, up to [20]%"
Applicable to: FollowerBelief
#### Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [stat] when killed within 4 tiles of a city following this religion
Example: "Earn [20]% of [Wounded] unit's [costOrStrength] as [Culture] when killed within 4 tiles of a city following this religion"
#### Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [plunderableStat] when killed within 4 tiles of a city following this religion
Example: "Earn [20]% of [Wounded] unit's [Cost] as [Gold] when killed within 4 tiles of a city following this religion"
Applicable to: FollowerBelief
@ -638,22 +668,22 @@ Example: "Requires a [Library] in all cities"
Applicable to: Building
#### Must be on [terrainFilter]
Example: "Must be on [Grassland]"
Example: "Must be on [Forest]"
Applicable to: Building
#### Must not be on [terrainFilter]
Example: "Must not be on [Grassland]"
Example: "Must not be on [Forest]"
Applicable to: Building
#### Must be next to [terrainFilter]
Example: "Must be next to [Grassland]"
Example: "Must be next to [Forest]"
Applicable to: Building
#### Must not be next to [terrainFilter]
Example: "Must not be next to [Grassland]"
Example: "Must not be next to [Forest]"
Applicable to: Building
@ -670,7 +700,7 @@ Applicable to: Building, Unit
Applicable to: Building, Unit, Ruins
#### Hidden when [victoryType] Victory is disabled
Example: "Hidden when [victoryType] Victory is disabled"
Example: "Hidden when [Domination] Victory is disabled"
Applicable to: Building, Unit
@ -679,7 +709,7 @@ Applicable to: Building, Unit
Applicable to: Unit
#### Can construct [improvementName]
Example: "Can construct [improvementName]"
Example: "Can construct [Trading Post]"
Applicable to: Unit
@ -698,7 +728,12 @@ Applicable to: Unit
Applicable to: Unit
#### Can only attack [combatantFilter] units
Example: "Can only attack [combatantFilter] units"
Example: "Can only attack [City] units"
Applicable to: Unit
#### Can only attack [tileFilter] tiles
Example: "Can only attack [Farm] tiles"
Applicable to: Unit
@ -708,13 +743,20 @@ Applicable to: Unit
#### Must set up to ranged attack
Applicable to: Unit
#### Uncapturable
Applicable to: Unit
#### Self-destructs when attacking
Applicable to: Unit
#### Unit will heal every turn, even if it performs an action
#### Blast radius [amount]
Example: "Blast radius [20]"
Applicable to: Unit
#### Uncapturable
Applicable to: Unit
#### May withdraw before melee ([amount]%)
Example: "May withdraw before melee ([20]%)"
Applicable to: Unit
#### Can move after attacking
@ -723,9 +765,7 @@ Applicable to: Unit
#### Can move immediately once bought
Applicable to: Unit
#### Blast radius [amount]
Example: "Blast radius [20]"
#### Unit will heal every turn, even if it performs an action
Applicable to: Unit
#### 6 tiles in every direction always visible
@ -746,6 +786,11 @@ Example: "Cannot be carried by [Wounded] units"
Applicable to: Unit
#### May capture killed [mapUnitFilter] units
Example: "May capture killed [Wounded] units"
Applicable to: Unit
#### Invisible to others
Applicable to: Unit
@ -763,7 +808,7 @@ Example: "May upgrade to [Melee] through ruins-like effects"
Applicable to: Unit
#### Double movement in [terrainFilter]
Example: "Double movement in [Grassland]"
Example: "Double movement in [Forest]"
Applicable to: Unit
@ -788,9 +833,6 @@ Applicable to: Unit
#### Cannot enter ocean tiles
Applicable to: Unit
#### Never appears as a Barbarian unit
Applicable to: Unit
#### May enter foreign tiles without open borders
Applicable to: Unit
@ -799,6 +841,9 @@ Example: "May enter foreign tiles without open borders, but loses [20] religious
Applicable to: Unit
#### Never appears as a Barbarian unit
Applicable to: Unit
#### Religious Unit
Applicable to: Unit
@ -810,12 +855,12 @@ Applicable to: Promotion
## Terrain uniques
#### Must be adjacent to [amount] [simpleTerrain] tiles
Example: "Must be adjacent to [20] [simpleTerrain] tiles"
Example: "Must be adjacent to [20] [Elevated] tiles"
Applicable to: Terrain
#### Must be adjacent to [amount] to [amount] [simpleTerrain] tiles
Example: "Must be adjacent to [20] to [20] [simpleTerrain] tiles"
Example: "Must be adjacent to [20] to [20] [Elevated] tiles"
Applicable to: Terrain
@ -840,12 +885,12 @@ Example: "Occurs in groups of [20] to [20] tiles"
Applicable to: Terrain
#### Neighboring tiles will convert to [baseTerrain]
Example: "Neighboring tiles will convert to [baseTerrain]"
Example: "Neighboring tiles will convert to [Grassland]"
Applicable to: Terrain
#### Neighboring tiles except [baseTerrain] will convert to [baseTerrain]
Example: "Neighboring tiles except [baseTerrain] will convert to [baseTerrain]"
Example: "Neighboring tiles except [Grassland] will convert to [Grassland]"
Applicable to: Terrain
@ -858,7 +903,7 @@ Example: "Units ending their turn on this terrain take [20] damage"
Applicable to: Terrain
#### Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game
Example: "Grants [promotion] ([comment]) to adjacent [Wounded] units for the rest of the game"
Example: "Grants [Shock I] ([comment]) to adjacent [Wounded] units for the rest of the game"
Applicable to: Terrain
@ -877,7 +922,7 @@ Applicable to: Terrain, Improvement
Applicable to: Terrain
#### Only [improvementFilter] improvements may be built on this tile
Example: "Only [improvementFilter] improvements may be built on this tile"
Example: "Only [All Road] improvements may be built on this tile"
Applicable to: Terrain
@ -900,17 +945,17 @@ Example: "[20] to Fertility for Map Generation"
Applicable to: Terrain
#### A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount]
Example: "A Region is formed with at least [20]% [simpleTerrain] tiles, with priority [20]"
Example: "A Region is formed with at least [20]% [Elevated] tiles, with priority [20]"
Applicable to: Terrain
#### A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount]
Example: "A Region is formed with at least [20]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [20]"
Example: "A Region is formed with at least [20]% [Elevated] tiles and [Elevated] tiles, with priority [20]"
Applicable to: Terrain
#### A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles
Example: "A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles"
Example: "A Region can not contain more [Elevated] tiles than [Elevated] tiles"
Applicable to: Terrain
@ -926,7 +971,7 @@ Applicable to: Terrain
Applicable to: Terrain
#### Considered [terrainQuality] when determining start locations
Example: "Considered [terrainQuality] when determining start locations"
Example: "Considered [Undesirable] when determining start locations"
Applicable to: Terrain
@ -1068,7 +1113,7 @@ Example: "[20] population in a random city"
Applicable to: Ruins
#### [amount] free random researchable Tech(s) from the [era]
Example: "[20] free random researchable Tech(s) from the [era]"
Example: "[20] free random researchable Tech(s) from the [Ancient era]"
Applicable to: Ruins
@ -1160,37 +1205,37 @@ Applicable to: Conditional
Applicable to: Conditional
#### <during the [era]>
Example: "<during the [era]>"
Example: "<during the [Ancient era]>"
Applicable to: Conditional
#### <before the [era]>
Example: "<before the [era]>"
Example: "<before the [Ancient era]>"
Applicable to: Conditional
#### <starting from the [era]>
Example: "<starting from the [era]>"
Example: "<starting from the [Ancient era]>"
Applicable to: Conditional
#### <after discovering [tech]>
Example: "<after discovering [tech]>"
Example: "<after discovering [Agriculture]>"
Applicable to: Conditional
#### <before discovering [tech]>
Example: "<before discovering [tech]>"
Example: "<before discovering [Agriculture]>"
Applicable to: Conditional
#### <after adopting [policy]>
Example: "<after adopting [policy]>"
Example: "<after adopting [Oligarchy]>"
Applicable to: Conditional
#### <before adopting [policy]>
Example: "<before adopting [policy]>"
Example: "<before adopting [Oligarchy]>"
Applicable to: Conditional
@ -1199,6 +1244,9 @@ Example: "<if this city has at least [20] specialists>"
Applicable to: Conditional
#### <with a garrison>
Applicable to: Conditional
#### <for [mapUnitFilter] units>
Example: "<for [Wounded] units>"
@ -1273,12 +1321,12 @@ Applicable to: Conditional
Applicable to: Conditional
#### <in [regionType] Regions>
Example: "<in [regionType] Regions>"
Example: "<in [Hybrid] Regions>"
Applicable to: Conditional
#### <in all except [regionType] Regions>
Example: "<in all except [regionType] Regions>"
Example: "<in all except [Hybrid] Regions>"
Applicable to: Conditional
@ -1291,6 +1339,9 @@ Applicable to: Conditional
- "Happiness from Luxury Resources gifted by City-States increased by [amount]%" - Deprecated As of 3.18.17, replace with "[+amount]% Happiness from luxury resources gifted by City-States"
- "-[amount]% food consumption by specialists [cityFilter]" - Deprecated As of 3.18.2, replace with "[-amount]% Food consumption by specialists [cityFilter]"
- "50% of excess happiness added to culture towards policies" - Deprecated As of 3.18.2, replace with "[50]% of excess happiness converted to [Culture]"
- "-[amount]% Culture cost of acquiring tiles [cityFilter]" - Deprecated As of 3.19.1, replace with "[-amount] Culture cost of natural border growth [cityFilter]"
- "[amount]% cost of natural border growth" - Deprecated As of 3.19.1, replace with "[amount] Culture cost of natural border growth [cityFilter]"
- "-[amount]% Gold cost of acquiring tiles [cityFilter]" - Deprecated As of 3.19.1, replace with "[-amount]% Gold cost of acquiring tiles [cityFilter]"
- "May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] starting from the [era] at an increasing price ([amount])" - Deprecated As of 3.17.9, replace with "May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount]) <starting from the [era]>"
- "Maintenance on roads & railroads reduced by [amount]%" - Deprecated As of 3.18.17, replace with "[-amount]% maintenance on road & railroads"
- "-[amount]% maintenance cost for buildings [cityFilter]" - Deprecated As of 3.18.17, replace with "[-amount]% maintenace cost for buildings [cityFilter]"
@ -1303,6 +1354,7 @@ Applicable to: Conditional
- "Golden Age length increased by [amount]%" - Deprecated As of 3.18.17, replace with "[+amount]% Golden Age length"
- "+[amount]% Defensive Strength for cities" - Deprecated As of 3.18.17, replace with "[+amount]% Strength for cities <when defending>"
- "[amount]% Attacking Strength for cities" - Deprecated As of 3.18.17, replace with "[amount]% Strength for cities <when attacking>"
- "+[amount]% attacking strength for cities with garrisoned units" - Deprecated As of 3.19.1, replace with "[amount]% Strength for cities <with a garrison> <when attacking>"
- "Melee units pay no movement cost to pillage" - Deprecated As of 3.18.17, replace with "No movement cost to pillage <for [Melee] units>"
- "[mapUnitFilter] units gain [amount]% more Experience from combat" - Deprecated As of 3.18.12, replace with "[amount]% XP gained from combat <for [mapUnitFilter] units>"
- "[amount]% maintenance costs for [mapUnitFilter] units" - Deprecated As of 3.18.14, replace with "[amount]% maintenance costs <for [mapUnitFilter] units>"