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
This commit is contained in:
SomeTroglodyte 2021-10-04 20:47:03 +02:00 committed by GitHub
parent 088e35ff13
commit 2884cbb469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 89 deletions

View File

@ -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())
}

View File

@ -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
@ -107,7 +108,7 @@ class CivConstructions() {
}
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() } }
@ -138,7 +139,7 @@ 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")
//

View File

@ -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()
}

View File

@ -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,7 +443,7 @@ 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)
}

View File

@ -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
}
}

View File

@ -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<Vector2> = 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)

View File

@ -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
@ -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)
}

View File

@ -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)