From 2884cbb4695cedd8c2fe51bca3f2593ae2639cfe Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Mon, 4 Oct 2021 20:47:03 +0200 Subject: [PATCH] UniqueType-i-fying UniqueTriggerActivation (#5397) * UniqueType-i-fying UniqueTriggerActivation * UniqueType-i-fying UniqueTriggerActivation - fix missing param types * UniqueType-i-fying UniqueTriggerActivation - alternate and all params --- .../com/unciv/logic/battle/BattleDamage.kt | 2 +- .../logic/civilization/CivConstructions.kt | 49 +++++----- .../logic/civilization/CivilizationInfo.kt | 2 +- core/src/com/unciv/logic/map/TileMap.kt | 5 +- .../ruleset/unique/UniqueParameterType.kt | 14 +++ .../ruleset/unique/UniqueTriggerActivation.kt | 92 ++++++++++--------- .../unciv/models/ruleset/unique/UniqueType.kt | 68 +++++++++++--- .../ui/pickerscreens/PromotionPickerScreen.kt | 3 +- 8 files changed, 146 insertions(+), 89 deletions(-) diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 6813cd96a2..2bcc8c47df 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -189,7 +189,7 @@ object BattleDamage { } } - for (unique in attacker.getCivInfo().getMatchingUniques("+[]% attack strength to all [] units for [] turns")) { + for (unique in attacker.getCivInfo().getMatchingUniques(UniqueType.TimedAttackStrength)) { if (attacker.matchesCategory(unique.params[1])) { modifiers.add("Temporary Bonus", unique.params[0].toInt()) } diff --git a/core/src/com/unciv/logic/civilization/CivConstructions.kt b/core/src/com/unciv/logic/civilization/CivConstructions.kt index 8130318471..c9ec4505d0 100644 --- a/core/src/com/unciv/logic/civilization/CivConstructions.kt +++ b/core/src/com/unciv/logic/civilization/CivConstructions.kt @@ -2,11 +2,12 @@ package com.unciv.logic.civilization import com.unciv.logic.city.INonPerpetualConstruction import com.unciv.models.Counter +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat import java.util.* import kotlin.collections.HashMap -class CivConstructions() { +class CivConstructions { @Transient lateinit var civInfo: CivilizationInfo @@ -22,16 +23,16 @@ class CivConstructions() { // to function properly and forcing this to be an `HashMap>` // when loading, even if this wasn't the original type, leading to run-time errors. private val freeStatBuildingsProvided: HashMap> = hashMapOf() - + // Maps buildings to the cities that have received that building private val freeSpecificBuildingsProvided: HashMap> = hashMapOf() - + init { for (stat in Stat.values()) { freeStatBuildingsProvided[stat.name] = hashSetOf() } } - + fun clone(): CivConstructions { val toReturn = CivConstructions() toReturn.civInfo = civInfo @@ -41,7 +42,7 @@ class CivConstructions() { toReturn.boughtItemsWithIncreasingPrice.add(boughtItemsWithIncreasingPrice.clone()) return toReturn } - + fun setTransients(civInfo: CivilizationInfo) { this.civInfo = civInfo @@ -53,7 +54,7 @@ class CivConstructions() { civInfo.boughtConstructionsWithGloballyIncreasingPrice.clear() } // - + // Deprecated variables in civ.policies since 3.16.15, this is replacement code if (civInfo.policies.specificBuildingsAdded.isNotEmpty()) { for ((building, cities) in civInfo.policies.specificBuildingsAdded) { @@ -69,11 +70,11 @@ class CivConstructions() { } civInfo.policies.specificBuildingsAdded.clear() } - + if (civInfo.policies.cultureBuildingsAdded.isNotEmpty()) { for ((cityId, building) in civInfo.policies.cultureBuildingsAdded) { freeStatBuildingsProvided[Stat.Culture.name]!!.add(cityId) - + if (cityId !in freeBuildings) freeBuildings[cityId] = hashSetOf() freeBuildings[cityId]!!.add(building) @@ -82,16 +83,16 @@ class CivConstructions() { } // } - + fun startTurn() { tryAddFreeBuildings() } - + fun tryAddFreeBuildings() { addFreeStatsBuildings() addFreeSpecificBuildings() } - + fun getFreeBuildings(cityId: String): HashSet { val toReturn = freeBuildings[cityId] ?: hashSetOf() for (city in civInfo.cities) { @@ -99,36 +100,36 @@ class CivConstructions() { } return toReturn } - + private fun addFreeBuilding(cityId: String, building: String) { if (!freeBuildings.containsKey(cityId)) freeBuildings[cityId] = hashSetOf() freeBuildings[cityId]!!.add(building) } - + private fun addFreeStatsBuildings() { - val statUniquesData = civInfo.getMatchingUniques("Provides the cheapest [] building in your first [] cities for free") + val statUniquesData = civInfo.getMatchingUniques(UniqueType.FreeStatBuildings) .groupBy { it.params[0] } .mapKeys { Stat.valueOf(it.key) } .mapValues { unique -> unique.value.sumOf { it.params[1].toInt() } } .toMutableMap() - + // Deprecated since 3.16.15 statUniquesData[Stat.Culture] = (statUniquesData[Stat.Culture] ?: 0) + civInfo.getMatchingUniques("Immediately creates the cheapest available cultural building in each of your first [] cities for free") .sumOf { it.params[0].toInt() } // - - + + for ((stat, amount) in statUniquesData) { addFreeStatBuildings(stat, amount) } } - + private fun addFreeStatBuildings(stat: Stat, amount: Int) { for (city in civInfo.cities.take(amount)) { if (freeStatBuildingsProvided[stat.name]!!.contains(city.id) || !city.cityConstructions.hasBuildableStatBuildings(stat)) continue - + val builtBuilding = city.cityConstructions.addCheapestBuildableStatBuilding(stat) if (builtBuilding != null) { freeStatBuildingsProvided[stat.name]!!.add(city.id) @@ -136,9 +137,9 @@ class CivConstructions() { } } } - + private fun addFreeSpecificBuildings() { - val buildingsUniquesData = (civInfo.getMatchingUniques("Provides a [] in your first [] cities for free") + val buildingsUniquesData = (civInfo.getMatchingUniques(UniqueType.FreeSpecificBuildings) // Deprecated since 3.16.15 + civInfo.getMatchingUniques("Immediately creates a [] in each of your first [] cities for free") // @@ -149,13 +150,13 @@ class CivConstructions() { addFreeBuildings(building, amount) } } - + private fun addFreeBuildings(building: String, amount: Int) { for (city in civInfo.cities.take(amount)) { if (freeSpecificBuildingsProvided[building]?.contains(city.id) == true || city.cityConstructions.containsBuildingOrEquivalent(building)) continue (city.cityConstructions.getConstruction(building) as INonPerpetualConstruction).postBuildEvent(city.cityConstructions) - + if (!freeSpecificBuildingsProvided.containsKey(building)) freeSpecificBuildingsProvided[building] = hashSetOf() freeSpecificBuildingsProvided[building]!!.add(city.id) @@ -163,4 +164,4 @@ class CivConstructions() { addFreeBuilding(city.id, building) } } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index cdba47263e..34c7e411bd 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -319,7 +319,7 @@ class CivilizationInfo { if (unique.params[0] == resource.name) resourceModifier *= 2f if (resource.resourceType == ResourceType.Strategic) { - resourceModifier *= 1f + getMatchingUniques("Quantity of strategic resources produced by the empire +[]%") + resourceModifier *= 1f + getMatchingUniques(UniqueType.StrategicResourcesIncrease) .map { it.params[0].toFloat() / 100f }.sum() } diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index d6db27141f..b7e82748ee 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -8,6 +8,7 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.metadata.Player import com.unciv.models.ruleset.Nation import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.unique.UniqueType import kotlin.math.abs /** An Unciv map with all properties as produced by the [map editor][com.unciv.ui.mapeditor.MapEditorScreen] @@ -442,12 +443,12 @@ class TileMap { for (promotion in unit.baseUnit.promotions) unit.promotions.addPromotion(promotion, true) - for (unique in civInfo.getMatchingUniques("[] units gain the [] promotion")) { + for (unique in civInfo.getMatchingUniques(UniqueType.UnitsGainPromotion)) { if (unit.matchesFilter(unique.params[0])) { unit.promotions.addPromotion(unique.params[1], true) } } - + // And update civ stats, since the new unit changes both unit upkeep and resource consumption civInfo.updateStatsForNextTurn() civInfo.updateDetailedCivResources() diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index e5332252db..9eef0b6107 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -15,6 +15,11 @@ enum class UniqueParameterType(val parameterName:String) { else null } }, + // todo potentially remove if OneTimeRevealSpecificMapTiles changes + KeywordAll("'all'") { + override fun getErrorSeverity(parameterText: String, ruleset: Ruleset) = + if (parameterText == "All") null else UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant + }, MapUnitFilter("mapUnitFilter"){ private val knownValues = setOf("Wounded", "Barbarians", "City-State", "Embarked", "Non-City") override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): @@ -116,6 +121,13 @@ enum class UniqueParameterType(val parameterName:String) { else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific } }, + Era("era") { + override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): + UniqueType.UniqueComplianceErrorSeverity? = when (parameterText) { + in ruleset.eras -> null + else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific + } + }, /** Behaves like [Unknown], but states explicitly the parameter is OK and its contents are ignored */ Comment("comment") { override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): @@ -166,6 +178,8 @@ enum class UniqueParameterType(val parameterName:String) { "in City-State cities", "in cities following this religion", ) + + fun safeValueOf(param: String) = values().firstOrNull { it.parameterName == param } ?: Unknown } } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 0d3fe7ccc7..2a57bde296 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -5,6 +5,7 @@ import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.* import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo +import com.unciv.models.ruleset.unique.UniqueType.* import com.unciv.models.ruleset.VictoryType import com.unciv.models.stats.Stat import com.unciv.models.translations.fillPlaceholders @@ -22,14 +23,14 @@ object UniqueTriggerActivation { tile: TileInfo? = null, notification: String? = null ): Boolean { - val chosenCity = - if (cityInfo != null) cityInfo - else civInfo.cities.firstOrNull { it.isCapital() } + val chosenCity = cityInfo ?: civInfo.cities.firstOrNull { it.isCapital() } val tileBasedRandom = if (tile != null) Random(tile.position.toString().hashCode()) else Random(-550) // Very random indeed - when (unique.placeholderText) { - "Free [] appears" -> { + + @Suppress("NON_EXHAUSTIVE_WHEN") // Yes we're not treating all types here + when (unique.type) { + OneTimeFreeUnit -> { val unitName = unique.params[0] val unit = civInfo.gameInfo.ruleSet.units[unitName] if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger())) @@ -45,7 +46,7 @@ object UniqueTriggerActivation { } return true } - "[] free [] units appear" -> { + OneTimeAmountFreeUnits -> { val unitName = unique.params[1] val unit = civInfo.gameInfo.ruleSet.units[unitName] if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger())) @@ -66,8 +67,7 @@ object UniqueTriggerActivation { } return true } - // Differs from "Free [] appears" in that it spawns near the ruins instead of in a city - "Free [] found in the ruins" -> { + OneTimeFreeUnitRuins -> { val unit = civInfo.getEquivalentUnit(unique.params[0]) val placingTile = tile ?: civInfo.cities.random().getCenterTile() @@ -88,8 +88,8 @@ object UniqueTriggerActivation { return placedUnit != null } - // spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen - "Free Social Policy" -> { + OneTimeFreePolicy -> { + // spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen if (civInfo.isSpectator()) return false civInfo.policies.freePolicies++ if (notification != null) { @@ -97,7 +97,7 @@ object UniqueTriggerActivation { } return true } - "[] Free Social Policies" -> { + OneTimeAmountFreePolicies -> { if (civInfo.isSpectator()) return false civInfo.policies.freePolicies += unique.params[0].toInt() if (notification != null) { @@ -105,14 +105,14 @@ object UniqueTriggerActivation { } return true } - "Empire enters golden age" -> { + OneTimeEnterGoldenAge -> { civInfo.goldenAges.enterGoldenAge() if (notification != null) { civInfo.addNotification(notification, NotificationIcon.Happiness) } return true } - "Free Great Person" -> { + OneTimeFreeGreatPerson -> { if (civInfo.isSpectator()) return false if (civInfo.isPlayerCivilization()) { civInfo.greatPeople.freeGreatPeople++ @@ -139,7 +139,7 @@ object UniqueTriggerActivation { return civInfo.addUnit(greatPerson.name, chosenCity) != null } } - "[] population []" -> { + OneTimeGainPopulation -> { val citiesWithPopulationChanged: MutableList = mutableListOf() for (city in civInfo.cities) { if (city.matchesFilter(unique.params[1])) { @@ -155,7 +155,7 @@ object UniqueTriggerActivation { ) return citiesWithPopulationChanged.isNotEmpty() } - "[] population in a random city" -> { + OneTimeGainPopulationRandomCity -> { if (civInfo.cities.isEmpty()) return false val randomCity = civInfo.cities.random(tileBasedRandom) randomCity.population.addPopulation(unique.params[0].toInt()) @@ -173,7 +173,7 @@ object UniqueTriggerActivation { return true } - "Free Technology" -> { + OneTimeFreeTech -> { if (civInfo.isSpectator()) return false civInfo.tech.freeTechs += 1 if (notification != null) { @@ -181,7 +181,7 @@ object UniqueTriggerActivation { } return true } - "[] Free Technologies" -> { + OneTimeAmountFreeTechs -> { if (civInfo.isSpectator()) return false civInfo.tech.freeTechs += unique.params[0].toInt() if (notification != null) { @@ -189,7 +189,7 @@ object UniqueTriggerActivation { } return true } - "[] free random researchable Tech(s) from the []" -> { + OneTimeFreeTechRuins -> { val researchableTechsFromThatEra = civInfo.gameInfo.ruleSet.technologies.values .filter { (it.column!!.era == unique.params[1] || unique.params[1] == "any era") @@ -214,7 +214,7 @@ object UniqueTriggerActivation { return true } - "Quantity of strategic resources produced by the empire increased by 100%" -> { + StrategicResourcesIncrease -> { civInfo.updateDetailedCivResources() if (notification != null) { civInfo.addNotification( @@ -224,7 +224,8 @@ object UniqueTriggerActivation { } return true } - "+[]% attack strength to all [] Units for [] turns" -> { + + TimedAttackStrength -> { civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt())) if (notification != null) { civInfo.addNotification(notification, NotificationIcon.War) @@ -232,7 +233,7 @@ object UniqueTriggerActivation { return true } - "Reveals the entire map" -> { + OneTimeRevealEntireMap -> { if (notification != null) { civInfo.addNotification(notification, "UnitIcons/Scout") } @@ -240,7 +241,7 @@ object UniqueTriggerActivation { civInfo.gameInfo.tileMap.values.asSequence().map { it.position }) } - "[] units gain the [] promotion" -> { + UnitsGainPromotion -> { val filter = unique.params[0] val promotion = unique.params[1] @@ -266,7 +267,7 @@ object UniqueTriggerActivation { return promotedUnitLocations.isNotEmpty() } - "Allied City-States will occasionally gift Great People" -> { + CityStateCanGiftGreatPeople -> { civInfo.addFlag( CivFlags.CityStateGreatPersonGift.name, civInfo.turnsForGreatPersonFromCityState() / 2 @@ -292,7 +293,7 @@ object UniqueTriggerActivation { // Note that the way this is implemented now, this unique does NOT stack // I could parametrize the [Allied], but eh. - "Gain [] []" -> { + OneTimeGainStat -> { if (Stat.values().none { it.name == unique.params[1] }) return false val stat = Stat.valueOf(unique.params[1]) @@ -305,7 +306,7 @@ object UniqueTriggerActivation { civInfo.addNotification(notification, stat.notificationIcon) return true } - "Gain []-[] []" -> { + OneTimeGainStatRange -> { if (Stat.values().none { it.name == unique.params[2] }) return false val stat = Stat.valueOf(unique.params[2]) @@ -334,7 +335,7 @@ object UniqueTriggerActivation { return true } - "Gain enough Faith for a Pantheon" -> { + OneTimeGainPantheon -> { if (civInfo.religionManager.religionState != ReligionState.None) return false val gainedFaith = civInfo.religionManager.faithForPantheon(2) if (gainedFaith == 0) return false @@ -351,7 +352,7 @@ object UniqueTriggerActivation { return true } - "Gain enough Faith for []% of a Great Prophet" -> { + OneTimeGainProphet -> { if (civInfo.religionManager.getGreatProphetEquivalent() == null) return false val gainedFaith = (civInfo.religionManager.faithForNextGreatProphet() * (unique.params[0].toFloat() / 100f)).toInt() @@ -370,14 +371,13 @@ object UniqueTriggerActivation { return true } - "Reveal up to [] [] within a [] tile radius" -> { + OneTimeRevealSpecificMapTiles -> { if (tile == null) return false val nearbyRevealableTiles = tile .getTilesInDistance(unique.params[2].toInt()) .filter { - !civInfo.exploredTiles.contains(it.position) && it.matchesFilter( - unique.params[1] - ) + !civInfo.exploredTiles.contains(it.position) && + it.matchesFilter(unique.params[1]) } .map { it.position } if (nearbyRevealableTiles.none()) return false @@ -397,13 +397,13 @@ object UniqueTriggerActivation { return true } - "From a randomly chosen tile [] tiles away from the ruins, reveal tiles up to [] tiles away with []% chance" -> { + OneTimeRevealCrudeMap -> { if (tile == null) return false val revealCenter = tile.getTilesAtDistance(unique.params[0].toInt()) .filter { it.position !in civInfo.exploredTiles } .toList() .randomOrNull(tileBasedRandom) - if (revealCenter == null) return false + ?: return false val tilesToReveal = revealCenter .getTilesInDistance(unique.params[1].toInt()) .map { it.position } @@ -417,7 +417,8 @@ object UniqueTriggerActivation { "ImprovementIcons/Ancient ruins" ) } - "Triggers voting for the Diplomatic Victory" -> { + + OneTimeTriggerVoting -> { for (civ in civInfo.gameInfo.civilizations) if (!civ.isBarbarian() && !civ.isSpectator()) civ.addFlag( @@ -429,8 +430,7 @@ object UniqueTriggerActivation { return true } - "Provides the cheapest [] building in your first [] cities for free", - "Provides a [] in your first [] cities for free" -> + FreeStatBuildings, FreeSpecificBuildings -> civInfo.civConstructions.tryAddFreeBuildings() } return false @@ -442,21 +442,22 @@ object UniqueTriggerActivation { unit: MapUnit, notification: String? = null ): Boolean { - when (unique.placeholderText) { - "Heal this unit by [] HP" -> { + @Suppress("NON_EXHAUSTIVE_WHEN") // Yes we're not treating all types here + when (unique.type) { + OneTimeUnitHeal -> { unit.healBy(unique.params[0].toInt()) if (notification != null) unit.civInfo.addNotification(notification, unit.getTile().position) // Do we have a heal icon? return true } - "This Unit gains [] XP" -> { + OneTimeUnitGainXP -> { if (!unit.baseUnit.isMilitary()) return false unit.promotions.XP += unique.params[0].toInt() if (notification != null) unit.civInfo.addNotification(notification, unit.getTile().position) return true } - "This Unit upgrades for free" -> { + OneTimeUnitUpgrade -> { val upgradeAction = UnitActions.getUpgradeAction(unit, true) ?: return false upgradeAction.action!!() @@ -464,7 +465,7 @@ object UniqueTriggerActivation { unit.civInfo.addNotification(notification, unit.getTile().position) return true } - "This Unit upgrades for free including special upgrades" -> { + OneTimeUnitSpecialUpgrade -> { val upgradeAction = UnitActions.getAncientRuinsUpgradeAction(unit) ?: return false upgradeAction.action!!() @@ -472,9 +473,10 @@ object UniqueTriggerActivation { unit.civInfo.addNotification(notification, unit.getTile().position) return true } - "This Unit gains the [] promotion" -> { - val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions.keys.firstOrNull { it == unique.params[0] } - if (promotion == null) return false + OneTimeUnitGainPromotion -> { + val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions.keys + .firstOrNull { it == unique.params[0] } + ?: return false unit.promotions.addPromotion(promotion, true) if (notification != null) unit.civInfo.addNotification(notification, unit.name) @@ -483,4 +485,4 @@ object UniqueTriggerActivation { } return false } -} \ No newline at end of file +} diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index d91a0d4ebd..c613a274fd 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -80,12 +80,12 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { CityStateHappiness("Provides [amount] Happiness", UniqueTarget.CityState), CityStateMilitaryUnits("Provides military units every ≈[amount] turns", UniqueTarget.CityState), // No conditional support as of yet CityStateUniqueLuxury("Provides a unique luxury", UniqueTarget.CityState), // No conditional support as of yet - CityStateGiftedUnitsStartWithXp("Military Units gifted from City-States start with [amount] XP", UniqueTarget.Global), CityStateGoldGiftsProvideMoreInfluence("Gifts of Gold to City-States generate [amount]% more Influence", UniqueTarget.Global), CityStateCanBeBoughtForGold("Can spend Gold to annex or puppet a City-State that has been your ally for [amount] turns.", UniqueTarget.Global), CityStateTerritoryAlwaysFriendly("City-State territory always counts as friendly territory", UniqueTarget.Global), + CityStateCanGiftGreatPeople("Allied City-States will occasionally gift Great People", UniqueTarget.Global), // used in Policy CityStateDeprecated("Will not be chosen for new games", UniqueTarget.Nation), // implemented for CS only for now /////// Other global uniques @@ -115,7 +115,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { FreeExtraBeliefs("May choose [amount] additional [beliefType] beliefs when [foundingOrEnhancing] a religion", UniqueTarget.Global), FreeExtraAnyBeliefs("May choose [amount] additional of any type when [foundingOrEnhancing] a religion", UniqueTarget.Global), - + ///////////////////////////////////////// UNIT UNIQUES ///////////////////////////////////////// Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global), @@ -141,25 +141,25 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { DamageForUnits("[mapUnitFilter] units deal +[amount]% damage", UniqueTarget.Global), @Deprecated("As of 3.17.5", ReplaceWith("[+10]% Strength "), DeprecationLevel.WARNING) StrengthGoldenAge("+10% Strength for all units during Golden Age", UniqueTarget.Global), - + Movement("[amount] Movement", UniqueTarget.Unit, UniqueTarget.Global), Sight("[amount] Sight", UniqueTarget.Unit, UniqueTarget.Global), SpreadReligionStrength("[amount]% Spread Religion Strength", UniqueTarget.Unit, UniqueTarget.Global), - + @Deprecated("As of 3.17.5", ReplaceWith("[amount] Movement "), DeprecationLevel.WARNING) MovementUnits("+[amount] Movement for all [mapUnitFilter] units", UniqueTarget.Global), @Deprecated("As of 3.17.5", ReplaceWith("[amount] Movement "), DeprecationLevel.WARNING) MovementGoldenAge("+1 Movement for all units during Golden Age", UniqueTarget.Global), - + @Deprecated("As of 3.17.5", ReplaceWith("[amount] Sight "), DeprecationLevel.WARNING) SightUnits("[amount] Sight for all [mapUnitFilter] units", UniqueTarget.Global), @Deprecated("As of 3.17.5", ReplaceWith("[amount] Sight"), DeprecationLevel.WARNING) VisibilityRange("[amount] Visibility Range", UniqueTarget.Unit), - + @Deprecated("As of 3.17.5", ReplaceWith("[amount]% Spread Religion Strength "), DeprecationLevel.WARNING) SpreadReligionStrengthUnits("[amount]% Spread Religion Strength for [mapUnitFilter] units", UniqueTarget.Global), - - + + // The following block gets cached in MapUnit for faster getMovementCostBetweenAdjacentTiles DoubleMovementOnTerrain("Double movement in [terrainFilter]", UniqueTarget.Unit), @Deprecated("As of 3.17.1", ReplaceWith("Double movement in [terrainFilter]"), DeprecationLevel.WARNING) @@ -177,7 +177,7 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { CannotEnterOcean("Cannot enter ocean tiles", UniqueTarget.Unit), CannotEnterOceanUntilAstronomy("Cannot enter ocean tiles until Astronomy", UniqueTarget.Unit), - + //////////////////////////////////////// TERRAIN UNIQUES //////////////////////////////////////// NaturalWonderNeighborCount("Must be adjacent to [amount] [simpleTerrain] tiles", UniqueTarget.Terrain), @@ -205,10 +205,10 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { ConditionalNotWar("when not at war", UniqueTarget.Conditional), ConditionalHappy("while the empire is happy", UniqueTarget.Conditional), ConditionalGoldenAge("during a Golden Age", UniqueTarget.Conditional), - + // city conditionals ConditionalSpecialistCount("if this city has at least [amount] specialists", UniqueTarget.Conditional), - + // unit conditionals ConditionalOurUnit("for [mapUnitFilter] units", UniqueTarget.Conditional), ConditionalVsCity("vs cities", UniqueTarget.Conditional), @@ -222,6 +222,44 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { // tile conditionals ConditionalNeighborTiles("with [amount] to [amount] neighboring [tileFilter] tiles", UniqueTarget.Conditional), ConditionalNeighborTilesAnd("with [amount] to [amount] neighboring [tileFilter] [tileFilter] tiles", UniqueTarget.Conditional), + + ///////////////////////////////////////// TRIGGERED ONE-TIME ///////////////////////////////////////// + + OneTimeFreeUnit("Free [baseUnitFilter] appears", UniqueTarget.Global), // used in Policies, Buildings + OneTimeAmountFreeUnits("[amount] free [baseUnitFilter] units appear", UniqueTarget.Global), // used in Buildings + OneTimeFreeUnitRuins("Free [baseUnitFilter] found in the ruins", UniqueTarget.Ruins), // Differs from "Free [] appears" in that it spawns near the ruins instead of in a city + OneTimeFreePolicy("Free Social Policy", UniqueTarget.Global), // used in Buildings + OneTimeAmountFreePolicies("[amount] Free Social Policies", UniqueTarget.Global), // Not used in Vanilla + OneTimeEnterGoldenAge("Empire enters golden age", UniqueTarget.Global), // used in Policies, Buildings + OneTimeFreeGreatPerson("Free Great Person", UniqueTarget.Global), // used in Policies, Buildings + OneTimeGainPopulation("[amount] population [cityFilter]", UniqueTarget.Global), // used in CN tower + OneTimeGainPopulationRandomCity("[amount] population in a random city", UniqueTarget.Ruins), + OneTimeFreeTech("Free Technology", UniqueTarget.Global), // used in Buildings + OneTimeAmountFreeTechs("[amount] Free Technologies", UniqueTarget.Global), // used in Policy + OneTimeFreeTechRuins("[amount] free random researchable Tech(s) from the [era]", UniqueTarget.Ruins), // todo: Not picked up by TranslationFileWriter? + OneTimeRevealEntireMap("Reveals the entire map", UniqueTarget.Global), // used in tech + OneTimeGainStat("Gain [amount] [stat]", UniqueTarget.Ruins), + OneTimeGainStatRange("Gain [amount]-[amount] [stat]", UniqueTarget.Ruins), + OneTimeGainPantheon("Gain enough Faith for a Pantheon", UniqueTarget.Ruins), + OneTimeGainProphet("Gain enough Faith for [amount]% of a Great Prophet", UniqueTarget.Ruins), + // todo: The "up to [All]" used in vanilla json is not nice to read. Split? + OneTimeRevealSpecificMapTiles("Reveal up to [amount/'all'] [tileFilter] within a [amount] tile radius", UniqueTarget.Ruins), + OneTimeRevealCrudeMap("From a randomly chosen tile [amount] tiles away from the ruins, reveal tiles up to [amount] tiles away with [amount]% chance", UniqueTarget.Ruins), + OneTimeTriggerVoting("Triggers voting for the Diplomatic Victory", UniqueTarget.Global), // used in Building + + OneTimeUnitHeal("Heal this unit by [amount] HP", UniqueTarget.Promotion), + OneTimeUnitGainXP("This Unit gains [amount] XP", UniqueTarget.Ruins), + OneTimeUnitUpgrade("This Unit upgrades for free", UniqueTarget.Global), // Not used in Vanilla + OneTimeUnitSpecialUpgrade("This Unit upgrades for free including special upgrades", UniqueTarget.Ruins), + OneTimeUnitGainPromotion("This Unit gains the [promotion] promotion", UniqueTarget.Global), // Not used in Vanilla + + UnitsGainPromotion("[mapUnitFilter] units gain the [promotion] promotion", UniqueTarget.Global), // Not used in Vanilla + // todo: remove forced sign + StrategicResourcesIncrease("Quantity of strategic resources produced by the empire +[amount]%", UniqueTarget.Global), // used in Policy + // todo: remove forced sign + TimedAttackStrength("+[amount]% attack strength to all [mapUnitFilter] Units for [amount] turns", UniqueTarget.Global), // used in Policy + FreeStatBuildings("Provides the cheapest [stat] building in your first [amount] cities for free", UniqueTarget.Global), // used in Policy + FreeSpecificBuildings("Provides a [building] in your first [amount] cities for free", UniqueTarget.Global), // used in Policy ; /** For uniques that have "special" parameters that can accept multiple types, we can override them manually @@ -231,10 +269,10 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { init { for (placeholder in text.getPlaceholderParameters()) { - val matchingParameterType = - UniqueParameterType.values().firstOrNull { it.parameterName == placeholder } - ?: UniqueParameterType.Unknown - parameterTypeMap.add(listOf(matchingParameterType)) + val matchingParameterTypes = placeholder + .split('/') + .map { UniqueParameterType.safeValueOf(it) } + parameterTypeMap.add(matchingParameterTypes) } targetTypes.addAll(targets) } diff --git a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt index d0c467611e..dd3ab805c1 100644 --- a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt @@ -8,6 +8,7 @@ import com.unciv.UncivGame import com.unciv.logic.map.MapUnit import com.unciv.models.Tutorial import com.unciv.models.UncivSound +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.Promotion import com.unciv.models.translations.tr import com.unciv.ui.utils.* @@ -71,7 +72,7 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen() { availablePromotionsGroup.row() } for (promotion in promotionsForUnitType) { - if (promotion.uniqueObjects.any { it.placeholderText == "Heal this unit by [] HP" } && unit.health == 100) continue + if (promotion.hasUnique(UniqueType.OneTimeUnitHeal) && unit.health == 100) continue val isPromotionAvailable = promotion in unitAvailablePromotions val unitHasPromotion = unit.promotions.promotions.contains(promotion.name)