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", "name": "Religious Settlements",
"type": "Pantheon", "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", "name": "Sacred Path",

View File

@ -197,7 +197,7 @@
"uniqueTo": "Russia", "uniqueTo": "Russia",
"hurryCostModifier": 25, "hurryCostModifier": 25,
"maintenance": 1, "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"], "New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"],
"requiredTech": "Bronze Working" "requiredTech": "Bronze Working"
}, },
@ -580,7 +580,7 @@
"culture": 1, "culture": 1,
"greatPersonPoints": {"Great Engineer": 1}, "greatPersonPoints": {"Great Engineer": 1},
"isWonder": true, "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", "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" "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], "innerColor": [255,255,255],
"favoredReligion": "Christianity", "favoredReligion": "Christianity",
"uniqueName": "Manifest Destiny", "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", "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", "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", "Cleveland","Kansas City","San Diego","Richmond","Las Vegas","Phoenix","Albuquerque","Minneapolis","Pittsburgh",

View File

@ -2,7 +2,7 @@
{ {
"name": "Tradition", "name": "Tradition",
"era": "Ancient era", "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": [ "policies": [
{ {
"name": "Aristocracy", "name": "Aristocracy",
@ -18,7 +18,7 @@
}, },
{ {
"name": "Oligarchy", "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, "row": 1,
"column": 5 "column": 5
}, },

View File

@ -178,7 +178,7 @@
"uniqueTo": "Russia", "uniqueTo": "Russia",
"hurryCostModifier": 25, "hurryCostModifier": 25,
"maintenance": 1, "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"], "New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"],
"requiredTech": "Bronze Working" "requiredTech": "Bronze Working"
}, },
@ -405,7 +405,7 @@
"culture": 1, "culture": 1,
"greatPersonPoints": {"Great Engineer": 1}, "greatPersonPoints": {"Great Engineer": 1},
"isWonder": true, "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", "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" "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], "outerColor": [ 28,51,119],
"innerColor": [255,255,255], "innerColor": [255,255,255],
"uniqueName": "Manifest Destiny", "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", "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", "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", "Cleveland","Kansas City","San Diego","Richmond","Las Vegas","Phoenix","Albuquerque","Minneapolis","Pittsburgh",

View File

@ -2,7 +2,7 @@
{ {
"name": "Tradition", "name": "Tradition",
"era": "Ancient era", "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": [ "policies": [
{ {
"name": "Aristocracy", "name": "Aristocracy",
@ -18,7 +18,7 @@
}, },
{ {
"name": "Oligarchy", "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, "row": 1,
"column": 5 "column": 5
}, },

View File

@ -149,7 +149,7 @@ object GameStarter {
civInfo.tech.addTechnology(tech) civInfo.tech.addTechnology(tech)
// generic start with technology unique // 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 // get the parameter from the unique
val techName = unique.params[0] val techName = unique.params[0]

View File

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

View File

@ -73,7 +73,7 @@ object ChooseBeliefsAutomation {
// If obsoleted, continue // If obsoleted, continue
score += modifier * when (unique.placeholderText) { score += modifier * when (unique.placeholderText) {
UniqueType.GrowthPercentBonus.placeholderText -> unique.params[0].toFloat() / 3f 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 "[]% 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 "[] Units adjacent to this city heal [] HP per turn when healing" -> unique.params[1].toFloat() / 10f
"+[]% Production when constructing []" -> unique.params[0].toFloat() / 3f "+[]% Production when constructing []" -> unique.params[0].toFloat() / 3f

View File

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

View File

@ -76,7 +76,7 @@ object Battle {
// Withdraw from melee ability // Withdraw from melee ability
if (attacker is MapUnitCombatant && attacker.isMelee() && defender is MapUnitCombatant) { 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 .maxByOrNull{ it.params[0] } // If a mod allows multiple withdraw properties, ensure the best is used
if (withdraw != null && doWithdrawFromMeleeAbility(attacker, defender, withdraw)) return 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://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines // 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) val captureChance = min(0.8f, 0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength().toFloat() * 0.4f)
if (Random().nextFloat() > captureChance) return false if (Random().nextFloat() > captureChance) return false
@ -247,9 +249,6 @@ object Battle {
plunderFromDamage(attacker, defender, defenderHealthBefore - defender.getHealth()) plunderFromDamage(attacker, defender, defenderHealthBefore - defender.getHealth())
} }
private object PlunderableStats {
val stats = setOf (Stat.Gold, Stat.Science, Stat.Culture, Stat.Faith)
}
private fun plunderFromDamage( private fun plunderFromDamage(
plunderingUnit: ICombatant, plunderingUnit: ICombatant,
plunderedUnit: ICombatant, plunderedUnit: ICombatant,
@ -259,16 +258,10 @@ object Battle {
if (plunderingUnit !is MapUnitCombatant) return if (plunderingUnit !is MapUnitCombatant) return
val plunderedGoods = Stats() 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])) { if (plunderedUnit.matchesCategory(unique.params[1])) {
// silently ignore bad mods here - or test in checkModLinks val percentage = unique.params[0].toFloat()
val stat = Stat.values().firstOrNull { it.name == unique.params[2] } plunderedGoods.add(Stat.valueOf(unique.params[2]), percentage / 100f * damageDealt)
?: 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)
} }
} }
@ -327,7 +320,7 @@ object Battle {
private fun tryHealAfterKilling(attacker: ICombatant) { private fun tryHealAfterKilling(attacker: ICombatant) {
if (attacker is MapUnitCombatant) 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() val amountToHeal = unique.params[0].toInt()
attacker.unit.healBy(amountToHeal) attacker.unit.healBy(amountToHeal)
} }

View File

@ -35,12 +35,13 @@ object BattleDamage {
val attackedTile = val attackedTile =
if (combatAction == CombatAction.Attack) enemy.getTile() if (combatAction == CombatAction.Attack) enemy.getTile()
else combatant.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) attackedTile = attackedTile, combatAction = combatAction)
if (combatant is MapUnitCombatant) { if (combatant is MapUnitCombatant) {
for (unique in combatant.getMatchingUniques(UniqueType.Strength, conditionalState, true)) { for (unique in combatant.getMatchingUniques(UniqueType.Strength, conditionalState, true)) {
modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt()) modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt())
} }
@ -118,7 +119,7 @@ object BattleDamage {
) )
modifiers["vs [City-States]"] = 30 modifiers["vs [City-States]"] = 30
} else if (combatant is CityCombatant) { } 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()) modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt())
} }
} }
@ -153,7 +154,7 @@ object BattleDamage {
} }
if (numberOfAttackersSurroundingDefender > 1) { if (numberOfAttackersSurroundingDefender > 1) {
var flankingBonus = 10f //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php 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() flankingBonus *= unique.params[0].toPercent()
modifiers["Flanking"] = modifiers["Flanking"] =
(flankingBonus * (numberOfAttackersSurroundingDefender - 1)).toInt() (flankingBonus * (numberOfAttackersSurroundingDefender - 1)).toInt()
@ -180,15 +181,17 @@ object BattleDamage {
} }
} else if (attacker is CityCombatant) { } else if (attacker is CityCombatant) {
if (attacker.city.getCenterTile().militaryUnit != null) { // Deprecated since 3.19.1
val garrisonBonus = attacker.city.getMatchingUniques("+[]% attacking strength for cities with garrisoned units") if (attacker.city.getCenterTile().militaryUnit != null) {
.sumOf { it.params[0].toInt() } val garrisonBonus = attacker.city.getMatchingUniques(UniqueType.StrengthForGarrisonedCitiesAttacking)
if (garrisonBonus != 0) .sumOf { it.params[0].toInt() }
modifiers["Garrisoned unit"] = garrisonBonus if (garrisonBonus != 0)
} modifiers["Garrisoned unit"] = garrisonBonus
for (unique in attacker.city.getMatchingUniques(UniqueType.StrengthForCitiesAttacking)) { }
modifiers.add("Attacking Bonus", unique.params[0].toInt()) for (unique in attacker.city.getMatchingUniques(UniqueType.StrengthForCitiesAttacking)) {
} modifiers.add("Attacking Bonus", unique.params[0].toInt())
}
//
} }
return modifiers return modifiers
@ -202,7 +205,7 @@ object BattleDamage {
if (defender.unit.isEmbarked()) { if (defender.unit.isEmbarked()) {
// embarked units get no defensive modifiers apart from this unique // 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) defender.getCivInfo().hasUnique(UniqueType.DefenceBonusWhenEmbarkedCivwide)
) )
modifiers["Embarked"] = 100 modifiers["Embarked"] = 100
@ -213,8 +216,8 @@ object BattleDamage {
modifiers.putAll(getTileSpecificModifiers(defender, tile)) modifiers.putAll(getTileSpecificModifiers(defender, tile))
val tileDefenceBonus = tile.getDefensiveBonus() val tileDefenceBonus = tile.getDefensiveBonus()
if (!defender.unit.hasUnique(UniqueType.NoDefensiveTerrainBonus) && tileDefenceBonus > 0 if (!defender.unit.hasUnique(UniqueType.NoDefensiveTerrainBonus, checkCivInfoUniques = true) && tileDefenceBonus > 0
|| !defender.unit.hasUnique(UniqueType.NoDefensiveTerrainPenalty) && tileDefenceBonus < 0 || !defender.unit.hasUnique(UniqueType.NoDefensiveTerrainPenalty, checkCivInfoUniques = true) && tileDefenceBonus < 0
) )
modifiers["Tile"] = (tileDefenceBonus * 100).toInt() 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.LocationAction
import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.ui.utils.toPercent import com.unciv.ui.utils.toPercent
import com.unciv.ui.utils.withItem import com.unciv.ui.utils.withItem
import com.unciv.ui.utils.withoutItem 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 // 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 // (per game XML files) at 6*(t+0.4813)^1.3
// The second seems to be more based, so I'll go with that // 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 { fun getCultureToNextTile(): Int {
var cultureToNextTile = 6 * (max(0, tilesClaimed()) + 1.4813).pow(1.3) var cultureToNextTile = 6 * (max(0, tilesClaimed()) + 1.4813).pow(1.3)
if (cityInfo.civInfo.isCityState()) if (cityInfo.civInfo.isCityState())
cultureToNextTile *= 1.5f // City states grow slower, perhaps 150% cost? cultureToNextTile *= 1.5f // City states grow slower, perhaps 150% cost?
for (unique in cityInfo.getMatchingUniques("-[]% Culture cost of acquiring tiles []")) { // Deprecated since 3.19.1
if (cityInfo.matchesFilter(unique.params[1])) for (unique in cityInfo.getMatchingUniques(UniqueType.DecreasedAcquiringTilesCost)) {
cultureToNextTile /= unique.params[0].toPercent() if (cityInfo.matchesFilter(unique.params[1]))
} cultureToNextTile /= unique.params[0].toPercent()
}
for (unique in cityInfo.getMatchingUniques(UniqueType.CostOfNaturalBorderGrowth))
cultureToNextTile *= unique.params[0].toPercent()
//
for (unique in cityInfo.getMatchingUniques("[]% cost of natural border growth")) for (unique in cityInfo.getMatchingUniquesWithNonLocalEffects(UniqueType.BorderGrowthPercentage))
cultureToNextTile *= unique.params[0].toPercent() if (cityInfo.matchesFilter(unique.params[1]))
cultureToNextTile *= unique.params[0].toPercent()
return cultureToNextTile.roundToInt() return cultureToNextTile.roundToInt()
} }
@ -66,10 +74,18 @@ class CityExpansionManager {
val distanceFromCenter = tileInfo.aerialDistanceTo(cityInfo.getCenterTile()) val distanceFromCenter = tileInfo.aerialDistanceTo(cityInfo.getCenterTile())
var cost = baseCost * (distanceFromCenter - 1) + tilesClaimed() * 5.0 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])) if (cityInfo.matchesFilter(unique.params[1]))
cost *= (100 - unique.params[0].toFloat()) / 100 cost *= unique.params[0].toPercent()
} }
return cost.roundToInt() return cost.roundToInt()
} }

View File

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

View File

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

View File

@ -77,6 +77,8 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
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()
UniqueType.ConditionalWhenGarrisoned ->
state.cityInfo != null && state.cityInfo.getCenterTile().militaryUnit != null && state.cityInfo.getCenterTile().militaryUnit!!.canGarrison()
UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesCategory("City") == true UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesCategory("City") == true
UniqueType.ConditionalVsUnits -> state.theirCombatant?.matchesCategory(condition.params[0]) == 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 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") { CityFilter("cityFilter") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? { UniqueType.UniqueComplianceErrorSeverity? {
@ -129,7 +139,7 @@ enum class UniqueParameterType(val parameterName:String) {
private val knownValues = setOf("All", private val knownValues = setOf("All",
"Coastal", "River", "Open terrain", "Rough terrain", "Water resource", "Coastal", "River", "Open terrain", "Rough terrain", "Water resource",
"Foreign Land", "Foreign", "Friendly Land", "Friendly", "Enemy Land", "Enemy", "Foreign Land", "Foreign", "Friendly Land", "Friendly", "Enemy Land", "Enemy",
"Featureless", "Fresh Water") "Featureless", "Fresh Water", "Natural Wonder")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? { UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null if (parameterText in knownValues) return null

View File

@ -137,7 +137,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
// endregion // endregion
/////// Other global uniques /////// region Other global uniques
FreeUnits("[amount] units cost no maintenance", UniqueTarget.Global), 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), ProvidesResources("Provides [amount] [resource]", UniqueTarget.Improvement, UniqueTarget.Building),
GrowthPercentBonus("[amount]% growth [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), 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), 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) @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), 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 // There is potential to merge these
BuyUnitsIncreasingCost("May buy [baseUnitFilter] units for [amount] [stat] [cityFilter] at an increasing price ([amount])", UniqueTarget.Global, UniqueTarget.FollowerBelief), 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")) @Deprecated("As of 3.18.17", ReplaceWith("[+amount]% Golden Age length"))
GoldenAgeLengthIncreased("Golden Age length increased by [amount]%", UniqueTarget.Global), 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>")) @Deprecated("As of 3.18.17", ReplaceWith("[+amount]% Strength for cities <when defending>"))
StrengthForCitiesDefending("+[amount]% Defensive Strength for cities", UniqueTarget.Global), StrengthForCitiesDefending("+[amount]% Defensive Strength for cities", UniqueTarget.Global),
@Deprecated("As of 3.18.17", ReplaceWith("[amount]% Strength for cities <when attacking>")) @Deprecated("As of 3.18.17", ReplaceWith("[amount]% Strength for cities <when attacking>"))
StrengthForCitiesAttacking("[amount]% Attacking Strength for cities", UniqueTarget.Global), 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), UnitStartingExperience("New [baseUnitFilter] units start with [amount] Experience [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
// ToDo: make per unit and use unit filters? // 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), IncompatibleWith("Incompatible with [policy/tech/promotion]", UniqueTarget.Policy, UniqueTarget.Tech, UniqueTarget.Promotion),
StartingTech("Starting tech", UniqueTarget.Tech), StartingTech("Starting tech", UniqueTarget.Tech),
StartsWithTech("Starts with [tech]", UniqueTarget.Nation),
ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global), ResearchableMultipleTimes("Can be continually researched", UniqueTarget.Global),
//endregion
//endregion Global uniques //endregion Global uniques
///////////////////////////////////////// region CONSTRUCTION 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), Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global),
StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", 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), Movement("[amount] Movement", UniqueTarget.Unit, UniqueTarget.Global),
Sight("[amount] Sight", UniqueTarget.Unit, UniqueTarget.Global, UniqueTarget.Terrain), Sight("[amount] Sight", UniqueTarget.Unit, UniqueTarget.Global, UniqueTarget.Terrain),
Range("[amount] Range", UniqueTarget.Unit, UniqueTarget.Global), Range("[amount] Range", UniqueTarget.Unit, UniqueTarget.Global),
SpreadReligionStrength("[amount]% Spread Religion Strength", UniqueTarget.Unit, UniqueTarget.Global), SpreadReligionStrength("[amount]% Spread Religion Strength", UniqueTarget.Unit, UniqueTarget.Global),
MayFoundReligion("May found a religion", UniqueTarget.Unit), MayFoundReligion("May found a religion", UniqueTarget.Unit),
MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit), MayEnhanceReligion("May enhance a religion", UniqueTarget.Unit),
CanOnlyAttackUnits("Can only attack [combatantFilter] units", UniqueTarget.Unit), CanOnlyAttackUnits("Can only attack [combatantFilter] units", UniqueTarget.Unit),
CanOnlyAttackTiles("Can only attack [tileFilter] tiles", UniqueTarget.Unit),
CannotAttack("Cannot attack", UniqueTarget.Unit), CannotAttack("Cannot attack", UniqueTarget.Unit),
MustSetUp("Must set up to ranged 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), NoDefensiveTerrainBonus("No defensive terrain bonus", UniqueTarget.Unit, UniqueTarget.Global),
NoDefensiveTerrainPenalty("No defensive terrain penalty", UniqueTarget.Unit, UniqueTarget.Global), NoDefensiveTerrainPenalty("No defensive terrain penalty", UniqueTarget.Unit, UniqueTarget.Global),
Uncapturable("Uncapturable", UniqueTarget.Unit), Uncapturable("Uncapturable", UniqueTarget.Unit),
SelfDestructs("Self-destructs when attacking", UniqueTarget.Unit), MayWithdraw("May withdraw before melee ([amount]%)", UniqueTarget.Unit),
HealsEvenAfterAction("Unit will heal every turn, even if it performs an action", UniqueTarget.Unit),
NoMovementToPillage("No movement cost to pillage", UniqueTarget.Unit, UniqueTarget.Global), 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) @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), NoMovementToPillageMelee("Melee units pay no movement cost to pillage", UniqueTarget.Unit, UniqueTarget.Global),
CanMoveAfterAttacking("Can move after attacking", UniqueTarget.Unit), CanMoveAfterAttacking("Can move after attacking", UniqueTarget.Unit),
MoveImmediatelyOnceBought("Can move immediately once bought", 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), HealsOutsideFriendlyTerritory("May heal outside of friendly territory", UniqueTarget.Unit, UniqueTarget.Global),
HealingEffectsDoubled("All healing effects doubled", 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), 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), NormalVisionWhenEmbarked("Normal vision when embarked", UniqueTarget.Unit, UniqueTarget.Global),
DefenceBonusWhenEmbarked("Defense bonus when embarked", UniqueTarget.Unit, UniqueTarget.Global), DefenceBonusWhenEmbarked("Defense bonus when embarked", UniqueTarget.Unit, UniqueTarget.Global),
@ -346,10 +366,12 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
UnitMaintenanceDiscount("[amount]% maintenance costs", UniqueTarget.Unit, UniqueTarget.Global), UnitMaintenanceDiscount("[amount]% maintenance costs", UniqueTarget.Unit, UniqueTarget.Global),
UnitUpgradeCost("[amount]% Gold cost of upgrading", UniqueTarget.Unit, UniqueTarget.Global), UnitUpgradeCost("[amount]% Gold cost of upgrading", UniqueTarget.Unit, UniqueTarget.Global),
GreatPersonEarnedFaster("[greatPerson] is earned [amount]% faster", 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), DamageUnitsPlunder("Earn [amount]% of the damage done to [mapUnitFilter] units as [plunderableStat]", UniqueTarget.Unit, UniqueTarget.Global),
KillUnitPlunder("Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat]", UniqueTarget.Unit, UniqueTarget.Global), CaptureCityPlunder("Upon capturing a city, receive [amount] times its [stat] production as [plunderableStat] immediately", 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), 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), FlatXPGain("[amount] XP gained from combat", UniqueTarget.Unit, UniqueTarget.Global),
PercentageXPGain("[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), CannotEnterOcean("Cannot enter ocean tiles", UniqueTarget.Unit),
@Deprecated("As of 3.18.6", ReplaceWith("Cannot enter ocean tiles <before discovering [Astronomy]>")) @Deprecated("As of 3.18.6", ReplaceWith("Cannot enter ocean tiles <before discovering [Astronomy]>"))
CannotEnterOceanUntilAstronomy("Cannot enter ocean tiles until Astronomy", UniqueTarget.Unit), 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), 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), 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), ReligiousUnit("Religious Unit", UniqueTarget.Unit),
SpaceshipPart("Spaceship part", UniqueTarget.Building, UniqueTarget.Unit), SpaceshipPart("Spaceship part", UniqueTarget.Building, UniqueTarget.Unit),
@ -386,13 +409,13 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
@Deprecated("As of 3.18.14", ReplaceWith("[amount]% maintenance costs <for [mapUnitFilter] units>"), DeprecationLevel.WARNING) @Deprecated("As of 3.18.14", ReplaceWith("[amount]% maintenance costs <for [mapUnitFilter] units>"), DeprecationLevel.WARNING)
UnitMaintenanceDiscountGlobal("[amount]% maintenance costs for [mapUnitFilter] units", UniqueTarget.Global), UnitMaintenanceDiscountGlobal("[amount]% maintenance costs for [mapUnitFilter] units", UniqueTarget.Global),
//endregion //endregion
///////////////////////////////////////// region TILE UNIQUES ///////////////////////////////////////// ///////////////////////////////////////// region TILE UNIQUES /////////////////////////////////////////
// region natural wonders // Natural wonders
NaturalWonderNeighborCount("Must be adjacent to [amount] [simpleTerrain] tiles", UniqueTarget.Terrain, flags = listOf(UniqueFlag.HiddenToUsers)), 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)), 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)), 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 // 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)), 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), 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), 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), 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), 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), ResourceAmountOnTiles("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource),
CityStateOnlyResource("Can only be created by Mercantile City-States", 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), ImprovementBuildableByFreshWater("Can also be built on tiles adjacent to fresh water", UniqueTarget.Improvement),
ImprovementStatsOnTile("[stats] from [tileFilter] tiles", UniqueTarget.Improvement), ImprovementStatsOnTile("[stats] from [tileFilter] tiles", UniqueTarget.Improvement),
ImprovementStatsForAdjacencies("[stats] for each adjacent [tileFilter]", 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 /////// city conditionals
ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional), ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional),
ConditionalWhenGarrisoned("with a garrison", UniqueTarget.Conditional),
/////// unit conditionals /////// unit conditionals
ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional), ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional),
@ -574,7 +597,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
//endregion //endregion
///////////////////////////////////////////// META ///////////////////////////////////////////// ///////////////////////////////////////////// region META /////////////////////////////////////////////
AvailableAfterCertainTurns("Only available after [amount] turns", UniqueTarget.Ruins), AvailableAfterCertainTurns("Only available after [amount] turns", UniqueTarget.Ruins),
HiddenWithoutReligion("Hidden when religion is disabled", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Ruins, flags = listOf(UniqueFlag.HiddenToUsers)), 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)), 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)), HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.values(), flags = listOf(UniqueFlag.HiddenToUsers)),
// endregion
// region DEPRECATED AND REMOVED // region DEPRECATED AND REMOVED
@ -656,7 +680,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
PercentProductionBuildingName("+[amount]% Production when constructing a [buildingName]", UniqueTarget.Global), PercentProductionBuildingName("+[amount]% Production when constructing a [buildingName]", UniqueTarget.Global),
@Deprecated("As of 3.17.10 - removed 3.18.5", ReplaceWith("[amount]% Production when constructing [buildingFilter] buildings [cityFilter]"), DeprecationLevel.ERROR) @Deprecated("As of 3.17.10 - removed 3.18.5", ReplaceWith("[amount]% Production when constructing [buildingFilter] buildings [cityFilter]"), DeprecationLevel.ERROR)
PercentProductionConstructionsCities("+[amount]% Production when constructing [constructionFilter] [cityFilter]", UniqueTarget.Global), PercentProductionConstructionsCities("+[amount]% Production when constructing [constructionFilter] [cityFilter]", UniqueTarget.Global),
// endregion // endregion
; ;
@ -711,3 +735,4 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
return errorList 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! // 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> <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 conditionalUniqueOrderString = "ConditionalsPlacement"
const val shouldCapitalizeString = "StartWithCapitalLetter" const val shouldCapitalizeString = "StartWithCapitalLetter"
} }

View File

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

View File

@ -16,17 +16,38 @@ class UniqueDocsWriter {
.toSortedMap() .toSortedMap()
fun replaceExamples(text:String):String { fun replaceExamples(text:String):String {
return text.replace("[amount]", "[20]") return text
.replace("[stat]", "[Culture]") .replace("[amount]", "[20]")
.replace("[combatantFilter]", "[City]")
.replace("[mapUnitFilter]", "[Wounded]")
.replace("[baseUnitFilter]", "[Melee]")
.replace("[great person]", "[Great Scientist]")
.replace("[stats]", "[+1 Gold, +2 Production]") .replace("[stats]", "[+1 Gold, +2 Production]")
.replace("[stat]", "[Culture]")
.replace("[plunderableStat]", "[Gold]")
.replace("[cityFilter]", "[in all cities]") .replace("[cityFilter]", "[in all cities]")
.replace("[buildingName]", "[Library]") .replace("[buildingName]", "[Library]")
.replace("[buildingFilter]", "[Culture]")
.replace("[constructionFilter]", "[Spaceship Part]")
.replace("[terrainFilter]", "[Forest]")
.replace("[tileFilter]", "[Farm]") .replace("[tileFilter]", "[Farm]")
.replace("[terrainFilter]", "[Grassland]") .replace("[simpleTerrain]", "[Elevated]")
.replace("[baseUnitFilter]", "[Melee]") .replace("[baseTerrain]", "[Grassland]")
.replace("[mapUnitFilter]", "[Wounded]") .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("[resource]", "[Iron]")
.replace("[beliefType]", "[Follower]") .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" lines += "## Table of Contents\n"
@ -37,8 +58,7 @@ class UniqueDocsWriter {
lines += " - [Deprecated uniques](#deprecated-uniques)" lines += " - [Deprecated uniques](#deprecated-uniques)"
lines += "" lines += ""
val deprecatedUniques = ArrayList<UniqueType>() val deprecatedUniques = ArrayList<UniqueType>()
for (targetType in targetTypesToUniques) { for (targetType in targetTypesToUniques) {
lines += "## " + targetType.key.name + " uniques" 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 Applicable to: Global, FollowerBelief
#### [stats] in cities on [terrainFilter] tiles #### [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 Applicable to: Global, FollowerBelief
@ -97,12 +97,12 @@ Example: "[20]% [Culture] [in all cities]"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### [amount]% Production when constructing [buildingFilter] wonders [cityFilter] #### [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 Applicable to: Global, FollowerBelief, Resource
#### [amount]% Production when constructing [buildingFilter] buildings [cityFilter] #### [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 Applicable to: Global, FollowerBelief
@ -172,18 +172,23 @@ Example: "[20]% growth [in all cities]"
Applicable to: Global, FollowerBelief 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] #### Gain a free [buildingName] [cityFilter]
Example: "Gain a free [Library] [in all cities]" Example: "Gain a free [Library] [in all cities]"
Applicable to: Global Applicable to: Global
#### May choose [amount] additional [beliefType] beliefs when [foundingOrEnhancing] a religion #### 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 Applicable to: Global
#### May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion #### 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 Applicable to: Global
@ -207,13 +212,23 @@ Example: "[20]% of excess happiness converted to [Culture]"
Applicable to: Global 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]) #### 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])" Example: "May buy [Melee] units for [20] [Culture] [in all cities] at an increasing price ([20])"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] at an increasing price ([amount]) #### 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 Applicable to: Global, FollowerBelief
@ -223,7 +238,7 @@ Example: "May buy [Melee] units for [20] [Culture] [in all cities]"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings for [amount] [stat] [cityFilter] #### 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 Applicable to: Global, FollowerBelief
@ -233,7 +248,7 @@ Example: "May buy [Melee] units with [Culture] [in all cities]"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings with [stat] [cityFilter] #### 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 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 Applicable to: Global, FollowerBelief
#### May buy [buildingFilter] buildings with [stat] for [amount] times their normal Production cost #### 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 Applicable to: Global, FollowerBelief
@ -259,7 +274,7 @@ Example: "[Culture] cost of purchasing items in cities [20]%"
Applicable to: Global, FollowerBelief Applicable to: Global, FollowerBelief
#### [stat] cost of purchasing [buildingFilter] buildings [amount]% #### [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 Applicable to: Global, FollowerBelief
@ -285,7 +300,7 @@ Example: "[20]% maintenance cost for buildings [in all cities]"
Applicable to: Global, FollowerBelief 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. #### 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 Applicable to: Global
@ -357,7 +372,7 @@ Applicable to: Global
#### [amount]% Strength for cities #### [amount]% Strength for cities
Example: "[20]% Strength for cities" Example: "[20]% Strength for cities"
Applicable to: Global Applicable to: Global, FollowerBelief
#### New [baseUnitFilter] units start with [amount] Experience [cityFilter] #### New [baseUnitFilter] units start with [amount] Experience [cityFilter]
Example: "New [Melee] units start with [20] Experience [in all cities]" 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 Applicable to: Global, Unit
#### [amount]% to Flank Attack bonuses
Example: "[20]% to Flank Attack bonuses"
Applicable to: Global, Unit
#### [amount] Movement #### [amount] Movement
Example: "[20] Movement" Example: "[20] Movement"
@ -447,13 +467,18 @@ Example: "[greatPerson] is earned [20]% faster"
Applicable to: Global, Unit Applicable to: Global, Unit
#### Upon capturing a city, receive [amount] times its [stat] production as [stat] immediately #### Earn [amount]% of the damage done to [mapUnitFilter] units as [plunderableStat]
Example: "Upon capturing a city, receive [20] times its [Culture] production as [Culture] immediately" Example: "Earn [20]% of the damage done to [Wounded] units as [Gold]"
Applicable to: Global, Unit Applicable to: Global, Unit
#### Earn [amount]% of killed [mapUnitFilter] unit's [costOrStrength] as [stat] #### Upon capturing a city, receive [amount] times its [stat] production as [plunderableStat] immediately
Example: "Earn [20]% of killed [Wounded] unit's [costOrStrength] as [Culture]" 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 Applicable to: Global, Unit
@ -514,12 +539,12 @@ Applicable to: Global
Applicable to: Global Applicable to: Global
#### This Unit gains the [promotion] promotion #### This Unit gains the [promotion] promotion
Example: "This Unit gains the [promotion] promotion" Example: "This Unit gains the [Shock I] promotion"
Applicable to: Global Applicable to: Global
#### [mapUnitFilter] units gain the [promotion] promotion #### [mapUnitFilter] units gain the [promotion] promotion
Example: "[Wounded] units gain the [promotion] promotion" Example: "[Wounded] units gain the [Shock I] promotion"
Applicable to: Global Applicable to: Global
@ -547,6 +572,11 @@ Applicable to: Global
#### Will not be chosen for new games #### Will not be chosen for new games
Applicable to: Nation Applicable to: Nation
#### Starts with [tech]
Example: "Starts with [Agriculture]"
Applicable to: Nation
## Tech uniques ## Tech uniques
#### Incompatible with [policy/tech/promotion] #### Incompatible with [policy/tech/promotion]
Example: "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 Applicable to: FollowerBelief
#### Earn [amount]% of [mapUnitFilter] unit's [costOrStrength] as [stat] 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 [costOrStrength] as [Culture] 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 Applicable to: FollowerBelief
@ -638,22 +668,22 @@ Example: "Requires a [Library] in all cities"
Applicable to: Building Applicable to: Building
#### Must be on [terrainFilter] #### Must be on [terrainFilter]
Example: "Must be on [Grassland]" Example: "Must be on [Forest]"
Applicable to: Building Applicable to: Building
#### Must not be on [terrainFilter] #### Must not be on [terrainFilter]
Example: "Must not be on [Grassland]" Example: "Must not be on [Forest]"
Applicable to: Building Applicable to: Building
#### Must be next to [terrainFilter] #### Must be next to [terrainFilter]
Example: "Must be next to [Grassland]" Example: "Must be next to [Forest]"
Applicable to: Building Applicable to: Building
#### Must not be next to [terrainFilter] #### Must not be next to [terrainFilter]
Example: "Must not be next to [Grassland]" Example: "Must not be next to [Forest]"
Applicable to: Building Applicable to: Building
@ -670,7 +700,7 @@ Applicable to: Building, Unit
Applicable to: Building, Unit, Ruins Applicable to: Building, Unit, Ruins
#### Hidden when [victoryType] Victory is disabled #### Hidden when [victoryType] Victory is disabled
Example: "Hidden when [victoryType] Victory is disabled" Example: "Hidden when [Domination] Victory is disabled"
Applicable to: Building, Unit Applicable to: Building, Unit
@ -679,7 +709,7 @@ Applicable to: Building, Unit
Applicable to: Unit Applicable to: Unit
#### Can construct [improvementName] #### Can construct [improvementName]
Example: "Can construct [improvementName]" Example: "Can construct [Trading Post]"
Applicable to: Unit Applicable to: Unit
@ -698,7 +728,12 @@ Applicable to: Unit
Applicable to: Unit Applicable to: Unit
#### Can only attack [combatantFilter] units #### 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 Applicable to: Unit
@ -708,13 +743,20 @@ Applicable to: Unit
#### Must set up to ranged attack #### Must set up to ranged attack
Applicable to: Unit Applicable to: Unit
#### Uncapturable
Applicable to: Unit
#### Self-destructs when attacking #### Self-destructs when attacking
Applicable to: Unit 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 Applicable to: Unit
#### Can move after attacking #### Can move after attacking
@ -723,9 +765,7 @@ Applicable to: Unit
#### Can move immediately once bought #### Can move immediately once bought
Applicable to: Unit Applicable to: Unit
#### Blast radius [amount] #### Unit will heal every turn, even if it performs an action
Example: "Blast radius [20]"
Applicable to: Unit Applicable to: Unit
#### 6 tiles in every direction always visible #### 6 tiles in every direction always visible
@ -746,6 +786,11 @@ Example: "Cannot be carried by [Wounded] units"
Applicable to: Unit Applicable to: Unit
#### May capture killed [mapUnitFilter] units
Example: "May capture killed [Wounded] units"
Applicable to: Unit
#### Invisible to others #### Invisible to others
Applicable to: Unit Applicable to: Unit
@ -763,7 +808,7 @@ Example: "May upgrade to [Melee] through ruins-like effects"
Applicable to: Unit Applicable to: Unit
#### Double movement in [terrainFilter] #### Double movement in [terrainFilter]
Example: "Double movement in [Grassland]" Example: "Double movement in [Forest]"
Applicable to: Unit Applicable to: Unit
@ -788,9 +833,6 @@ Applicable to: Unit
#### Cannot enter ocean tiles #### Cannot enter ocean tiles
Applicable to: Unit Applicable to: Unit
#### Never appears as a Barbarian unit
Applicable to: Unit
#### May enter foreign tiles without open borders #### May enter foreign tiles without open borders
Applicable to: Unit Applicable to: Unit
@ -799,6 +841,9 @@ Example: "May enter foreign tiles without open borders, but loses [20] religious
Applicable to: Unit Applicable to: Unit
#### Never appears as a Barbarian unit
Applicable to: Unit
#### Religious Unit #### Religious Unit
Applicable to: Unit Applicable to: Unit
@ -810,12 +855,12 @@ Applicable to: Promotion
## Terrain uniques ## Terrain uniques
#### Must be adjacent to [amount] [simpleTerrain] tiles #### 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 Applicable to: Terrain
#### Must be adjacent to [amount] to [amount] [simpleTerrain] tiles #### 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 Applicable to: Terrain
@ -840,12 +885,12 @@ Example: "Occurs in groups of [20] to [20] tiles"
Applicable to: Terrain Applicable to: Terrain
#### Neighboring tiles will convert to [baseTerrain] #### Neighboring tiles will convert to [baseTerrain]
Example: "Neighboring tiles will convert to [baseTerrain]" Example: "Neighboring tiles will convert to [Grassland]"
Applicable to: Terrain Applicable to: Terrain
#### Neighboring tiles except [baseTerrain] will convert to [baseTerrain] #### 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 Applicable to: Terrain
@ -858,7 +903,7 @@ Example: "Units ending their turn on this terrain take [20] damage"
Applicable to: Terrain Applicable to: Terrain
#### Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game #### 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 Applicable to: Terrain
@ -877,7 +922,7 @@ Applicable to: Terrain, Improvement
Applicable to: Terrain Applicable to: Terrain
#### Only [improvementFilter] improvements may be built on this tile #### 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 Applicable to: Terrain
@ -900,17 +945,17 @@ Example: "[20] to Fertility for Map Generation"
Applicable to: Terrain Applicable to: Terrain
#### A Region is formed with at least [amount]% [simpleTerrain] tiles, with priority [amount] #### 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 Applicable to: Terrain
#### A Region is formed with at least [amount]% [simpleTerrain] tiles and [simpleTerrain] tiles, with priority [amount] #### 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 Applicable to: Terrain
#### A Region can not contain more [simpleTerrain] tiles than [simpleTerrain] tiles #### 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 Applicable to: Terrain
@ -926,7 +971,7 @@ Applicable to: Terrain
Applicable to: Terrain Applicable to: Terrain
#### Considered [terrainQuality] when determining start locations #### Considered [terrainQuality] when determining start locations
Example: "Considered [terrainQuality] when determining start locations" Example: "Considered [Undesirable] when determining start locations"
Applicable to: Terrain Applicable to: Terrain
@ -1068,7 +1113,7 @@ Example: "[20] population in a random city"
Applicable to: Ruins Applicable to: Ruins
#### [amount] free random researchable Tech(s) from the [era] #### [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 Applicable to: Ruins
@ -1160,37 +1205,37 @@ Applicable to: Conditional
Applicable to: Conditional Applicable to: Conditional
#### <during the [era]> #### <during the [era]>
Example: "<during the [era]>" Example: "<during the [Ancient era]>"
Applicable to: Conditional Applicable to: Conditional
#### <before the [era]> #### <before the [era]>
Example: "<before the [era]>" Example: "<before the [Ancient era]>"
Applicable to: Conditional Applicable to: Conditional
#### <starting from the [era]> #### <starting from the [era]>
Example: "<starting from the [era]>" Example: "<starting from the [Ancient era]>"
Applicable to: Conditional Applicable to: Conditional
#### <after discovering [tech]> #### <after discovering [tech]>
Example: "<after discovering [tech]>" Example: "<after discovering [Agriculture]>"
Applicable to: Conditional Applicable to: Conditional
#### <before discovering [tech]> #### <before discovering [tech]>
Example: "<before discovering [tech]>" Example: "<before discovering [Agriculture]>"
Applicable to: Conditional Applicable to: Conditional
#### <after adopting [policy]> #### <after adopting [policy]>
Example: "<after adopting [policy]>" Example: "<after adopting [Oligarchy]>"
Applicable to: Conditional Applicable to: Conditional
#### <before adopting [policy]> #### <before adopting [policy]>
Example: "<before adopting [policy]>" Example: "<before adopting [Oligarchy]>"
Applicable to: Conditional Applicable to: Conditional
@ -1199,6 +1244,9 @@ Example: "<if this city has at least [20] specialists>"
Applicable to: Conditional Applicable to: Conditional
#### <with a garrison>
Applicable to: Conditional
#### <for [mapUnitFilter] units> #### <for [mapUnitFilter] units>
Example: "<for [Wounded] units>" Example: "<for [Wounded] units>"
@ -1273,12 +1321,12 @@ Applicable to: Conditional
Applicable to: Conditional Applicable to: Conditional
#### <in [regionType] Regions> #### <in [regionType] Regions>
Example: "<in [regionType] Regions>" Example: "<in [Hybrid] Regions>"
Applicable to: Conditional Applicable to: Conditional
#### <in all except [regionType] Regions> #### <in all except [regionType] Regions>
Example: "<in all except [regionType] Regions>" Example: "<in all except [Hybrid] Regions>"
Applicable to: Conditional 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" - "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]" - "-[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]" - "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]>" - "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" - "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]" - "-[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" - "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]% 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" - 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>" - "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>" - "[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>" - "[amount]% maintenance costs for [mapUnitFilter] units" - Deprecated As of 3.18.14, replace with "[amount]% maintenance costs <for [mapUnitFilter] units>"