From 39ed8bd269c61113e9566d3069d62673dc5e06c1 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Mon, 24 Jan 2022 10:03:40 +0100 Subject: [PATCH] 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 --- .../jsons/Civ V - Gods & Kings/Beliefs.json | 2 +- .../jsons/Civ V - Gods & Kings/Buildings.json | 4 +- .../jsons/Civ V - Gods & Kings/Nations.json | 2 +- .../jsons/Civ V - Gods & Kings/Policies.json | 4 +- .../jsons/Civ V - Vanilla/Buildings.json | 4 +- .../assets/jsons/Civ V - Vanilla/Nations.json | 2 +- .../jsons/Civ V - Vanilla/Policies.json | 4 +- core/src/com/unciv/logic/GameStarter.kt | 2 +- .../unciv/logic/automation/BattleHelper.kt | 4 +- .../automation/ChooseBeliefsAutomation.kt | 2 +- .../automation/ConstructionAutomation.kt | 9 +- core/src/com/unciv/logic/battle/Battle.kt | 23 +-- .../com/unciv/logic/battle/BattleDamage.kt | 37 ++-- .../unciv/logic/city/CityExpansionManager.kt | 32 +++- core/src/com/unciv/logic/map/MapUnit.kt | 19 +- core/src/com/unciv/models/ruleset/Building.kt | 5 +- .../com/unciv/models/ruleset/unique/Unique.kt | 2 + .../ruleset/unique/UniqueParameterType.kt | 12 +- .../unciv/models/ruleset/unique/UniqueType.kt | 65 ++++--- .../unciv/models/translations/Translations.kt | 2 +- .../unciv/ui/worldscreen/unit/UnitActions.kt | 8 +- .../com/unciv/app/desktop/UniqueDocsWriter.kt | 34 +++- docs/uniques.md | 168 ++++++++++++------ 23 files changed, 288 insertions(+), 158 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/Beliefs.json b/android/assets/jsons/Civ V - Gods & Kings/Beliefs.json index eb6ad43a30..90e50ecbec 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Beliefs.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Beliefs.json @@ -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", diff --git a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json index 1206b131f0..cd7d5b5f6b 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json @@ -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" }, diff --git a/android/assets/jsons/Civ V - Gods & Kings/Nations.json b/android/assets/jsons/Civ V - Gods & Kings/Nations.json index 2985dd9948..fcc1bc9c15 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Nations.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Nations.json @@ -272,7 +272,7 @@ "innerColor": [255,255,255], "favoredReligion": "Christianity", "uniqueName": "Manifest Destiny", - "uniques": ["[+1] Sight ", "-[50]% Gold cost of acquiring tiles [in all cities]"], + "uniques": ["[+1] Sight ", "[-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", diff --git a/android/assets/jsons/Civ V - Gods & Kings/Policies.json b/android/assets/jsons/Civ V - Gods & Kings/Policies.json index 9f1ded2dfd..6e4686cb07 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Policies.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Policies.json @@ -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 "], "row": 1, "column": 5 }, diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index a37d846943..037242b9f9 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -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" }, diff --git a/android/assets/jsons/Civ V - Vanilla/Nations.json b/android/assets/jsons/Civ V - Vanilla/Nations.json index 3eb3a02de5..0098846636 100644 --- a/android/assets/jsons/Civ V - Vanilla/Nations.json +++ b/android/assets/jsons/Civ V - Vanilla/Nations.json @@ -262,7 +262,7 @@ "outerColor": [ 28,51,119], "innerColor": [255,255,255], "uniqueName": "Manifest Destiny", - "uniques": ["[+1] Sight ", "-[50]% Gold cost of acquiring tiles [in all cities]"], + "uniques": ["[+1] Sight ", "[-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", diff --git a/android/assets/jsons/Civ V - Vanilla/Policies.json b/android/assets/jsons/Civ V - Vanilla/Policies.json index 65985fbb59..e2405db7e1 100644 --- a/android/assets/jsons/Civ V - Vanilla/Policies.json +++ b/android/assets/jsons/Civ V - Vanilla/Policies.json @@ -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 "], "row": 1, "column": 5 }, diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index bc756a771c..7cb0ea6271 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -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] diff --git a/core/src/com/unciv/logic/automation/BattleHelper.kt b/core/src/com/unciv/logic/automation/BattleHelper.kt index eda457e6ef..dbc790c6a8 100644 --- a/core/src/com/unciv/logic/automation/BattleHelper.kt +++ b/core/src/com/unciv/logic/automation/BattleHelper.kt @@ -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 diff --git a/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt index 7382bbdb51..4551d9d03b 100644 --- a/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt +++ b/core/src/com/unciv/logic/automation/ChooseBeliefsAutomation.kt @@ -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 diff --git a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt index be4377c06b..60a7be6a99 100644 --- a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt @@ -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 diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 2dc2567a0c..365872caaf 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -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) } diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index f8389340cc..6355641376 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -35,12 +35,13 @@ object BattleDamage { val attackedTile = 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)) { modifiers.add(getModifierStringFromUnique(unique), unique.params[0].toInt()) } @@ -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() diff --git a/core/src/com/unciv/logic/city/CityExpansionManager.kt b/core/src/com/unciv/logic/city/CityExpansionManager.kt index 271814cf29..8cd0202e97 100644 --- a/core/src/com/unciv/logic/city/CityExpansionManager.kt +++ b/core/src/com/unciv/logic/city/CityExpansionManager.kt @@ -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(UniqueType.CostOfNaturalBorderGrowth)) + cultureToNextTile *= unique.params[0].toPercent() + // - for (unique in cityInfo.getMatchingUniques("[]% cost of natural border growth")) - 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() } diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 754b608da0..44dcf8aa40 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -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 diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 90c17cf1fc..0dc36f131e 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -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 } diff --git a/core/src/com/unciv/models/ruleset/unique/Unique.kt b/core/src/com/unciv/models/ruleset/unique/Unique.kt index d43feb3dcd..f262a0d5d7 100644 --- a/core/src/com/unciv/models/ruleset/unique/Unique.kt +++ b/core/src/com/unciv/models/ruleset/unique/Unique.kt @@ -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 diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index bf3224eebe..ea814d967d 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -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 diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 80772363c7..ac3de2abe0 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -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 ")) StrengthForCitiesDefending("+[amount]% Defensive Strength for cities", UniqueTarget.Global), @Deprecated("As of 3.18.17", ReplaceWith("[amount]% Strength for cities ")) StrengthForCitiesAttacking("[amount]% Attacking Strength for cities", UniqueTarget.Global), + @Deprecated("As of 3.19.1", ReplaceWith("[amount]% Strength for cities ")) + 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 "), 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), @@ -346,10 +366,12 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: UnitMaintenanceDiscount("[amount]% maintenance costs", UniqueTarget.Unit, UniqueTarget.Global), 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 ")) 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), @@ -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 "), DeprecationLevel.WARNING) UnitMaintenanceDiscountGlobal("[amount]% maintenance costs for [mapUnitFilter] units", UniqueTarget.Global), - - + + //endregion ///////////////////////////////////////// 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 @@ -656,7 +680,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: 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) PercentProductionConstructionsCities("+[amount]% Production when constructing [constructionFilter] [cityFilter]", UniqueTarget.Global), - + // endregion ; @@ -711,3 +735,4 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: return errorList } } + diff --git a/core/src/com/unciv/models/translations/Translations.kt b/core/src/com/unciv/models/translations/Translations.kt index 809b6080c7..4a168b96fb 100644 --- a/core/src/com/unciv/models/translations/Translations.kt +++ b/core/src/com/unciv/models/translations/Translations.kt @@ -214,7 +214,7 @@ class Translations : LinkedHashMap(){ // 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 = - " " + " " const val conditionalUniqueOrderString = "ConditionalsPlacement" const val shouldCapitalizeString = "StartWithCapitalLetter" } diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 64fd404b73..b0ebf3494b 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -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) diff --git a/desktop/src/com/unciv/app/desktop/UniqueDocsWriter.kt b/desktop/src/com/unciv/app/desktop/UniqueDocsWriter.kt index 38fc7c08db..6a0cb9b0c7 100644 --- a/desktop/src/com/unciv/app/desktop/UniqueDocsWriter.kt +++ b/desktop/src/com/unciv/app/desktop/UniqueDocsWriter.kt @@ -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" @@ -37,8 +58,7 @@ class UniqueDocsWriter { lines += " - [Deprecated uniques](#deprecated-uniques)" lines += "" - - + val deprecatedUniques = ArrayList() for (targetType in targetTypesToUniques) { lines += "## " + targetType.key.name + " uniques" diff --git a/docs/uniques.md b/docs/uniques.md index 1739a7b397..0b98ec58e0 100644 --- a/docs/uniques.md +++ b/docs/uniques.md @@ -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 #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional @@ -1199,6 +1244,9 @@ Example: "" Applicable to: Conditional +#### +Applicable to: Conditional + #### Example: "" @@ -1273,12 +1321,12 @@ Applicable to: Conditional Applicable to: Conditional #### -Example: "" +Example: "" Applicable to: Conditional #### -Example: "" +Example: "" 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]) " - "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 " - "[amount]% Attacking Strength for cities" - Deprecated As of 3.18.17, replace with "[amount]% Strength for cities " + - "+[amount]% attacking strength for cities with garrisoned units" - Deprecated As of 3.19.1, replace with "[amount]% Strength for cities " - "Melee units pay no movement cost to pillage" - Deprecated As of 3.18.17, replace with "No movement cost to pillage " - "[mapUnitFilter] units gain [amount]% more Experience from combat" - Deprecated As of 3.18.12, replace with "[amount]% XP gained from combat " - "[amount]% maintenance costs for [mapUnitFilter] units" - Deprecated As of 3.18.14, replace with "[amount]% maintenance costs "