From 27465425b046f97220124f6ecccc76429c3986ac Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Tue, 20 Feb 2024 10:52:28 +0200 Subject: [PATCH] Allow determining *if* a unique can trigger a triggerable effect, *without* actually doing so --- .../ruleset/unique/UniqueTriggerActivation.kt | 968 ++++++++++-------- 1 file changed, 543 insertions(+), 425 deletions(-) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 0f05c97cdb..dac09e30a7 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -61,6 +61,20 @@ object UniqueTriggerActivation { notification: String? = null, triggerNotificationText: String? = null ): Boolean { + val function = getTriggerFunction(unique, civInfo, city, unit, tile, notification, triggerNotificationText) ?: return false + return function.invoke() + } + + /** @return The action to be performed if possible, else null */ + fun getTriggerFunction( + unique: Unique, + civInfo: Civilization, + city: City? = null, + unit: MapUnit? = null, + tile: Tile? = city?.getCenterTile() ?: unit?.currentTile, + notification: String? = null, + triggerNotificationText: String? = null + ): (()->Boolean)? { val relevantCity by lazy { city?: tile?.getCity() @@ -68,11 +82,10 @@ object UniqueTriggerActivation { val timingConditional = unique.conditionals.firstOrNull { it.type == UniqueType.ConditionalTimedUnique } if (timingConditional != null) { - civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) - return true + return { civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) } } - if (!unique.conditionalsApply(StateForConditionals(civInfo, city, unit, tile))) return false + if (!unique.conditionalsApply(StateForConditionals(civInfo, city, unit, tile))) return null val chosenCity = relevantCity ?: civInfo.cities.firstOrNull { it.isCapital() } @@ -85,45 +98,51 @@ object UniqueTriggerActivation { when (unique.type) { UniqueType.OneTimeFreeUnit -> { val unitName = unique.params[0] - val baseUnit = ruleSet.units[unitName] ?: return false + val baseUnit = ruleSet.units[unitName] ?: return null val civUnit = civInfo.getEquivalentUnit(baseUnit) if (civUnit.isCityFounder() && civInfo.isOneCityChallenger()) - return false + return null val limit = civUnit.getMatchingUniques(UniqueType.MaxNumberBuildable) .map { it.params[0].toInt() }.minOrNull() if (limit != null && limit <= civInfo.units.getCivUnits().count { it.name == civUnit.name }) - return false + return null - val placedUnit = when { - // Set unit at city if there's an explict city or if there's no tile to set at - relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> - civInfo.units.addUnit(civUnit, chosenCity) ?: return false - // Else set the unit at the given tile - tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) ?: return false - // Else set unit unit near other units if we have no cities - civInfo.units.getCivUnits().any() -> - civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) ?: return false - else -> return false + fun placeUnit(): Boolean { + val placedUnit = when { + // Set unit at city if there's an explict city or if there's no tile to set at + relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> + civInfo.units.addUnit(civUnit, chosenCity) ?: return false + // Else set the unit at the given tile + tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) ?: return false + // Else set unit unit near other units if we have no cities + civInfo.units.getCivUnits().any() -> + civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) ?: return false + + else -> return false + } + val notificationText = getNotificationText( + notification, triggerNotificationText, + "Gained [1] [${civUnit.name}] unit(s)" + ) + if (notificationText != null) + civInfo.addNotification( + notificationText, + MapUnitAction(placedUnit), + NotificationCategory.Units, + placedUnit.name + ) + return true } - val notificationText = getNotificationText(notification, triggerNotificationText, - "Gained [1] [${civUnit.name}] unit(s)") - ?: return true - - civInfo.addNotification( - notificationText, - MapUnitAction(placedUnit), - NotificationCategory.Units, - placedUnit.name - ) - return true + return { placeUnit() } } + UniqueType.OneTimeAmountFreeUnits -> { val unitName = unique.params[1] - val baseUnit = ruleSet.units[unitName] ?: return false + val baseUnit = ruleSet.units[unitName] ?: return null val civUnit = civInfo.getEquivalentUnit(baseUnit) if (civUnit.isCityFounder() && civInfo.isOneCityChallenger()) - return false + return null val limit = civUnit.getMatchingUniques(UniqueType.MaxNumberBuildable) .map { it.params[0].toInt() }.minOrNull() @@ -135,37 +154,43 @@ object UniqueTriggerActivation { else -> amountFromTriggerable } - if (actualAmount <= 0) return false + if (actualAmount <= 0) return null - val tilesUnitsWerePlacedOn: MutableList = mutableListOf() - repeat(actualAmount) { - val placedUnit = when { - // Set unit at city if there's an explict city or if there's no tile to set at - relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> - civInfo.units.addUnit(civUnit, chosenCity) - // Else set the unit at the given tile - tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) - // Else set unit unit near other units if we have no cities - civInfo.units.getCivUnits().any() -> - civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) - else -> null + fun placeUnits():Boolean { + val tilesUnitsWerePlacedOn: MutableList = mutableListOf() + repeat(actualAmount) { + val placedUnit = when { + // Set unit at city if there's an explict city or if there's no tile to set at + relevantCity != null || (tile == null && civInfo.cities.isNotEmpty()) -> + civInfo.units.addUnit(civUnit, chosenCity) + // Else set the unit at the given tile + tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) + // Else set unit unit near other units if we have no cities + civInfo.units.getCivUnits().any() -> + civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) + + else -> null + } + if (placedUnit != null) + tilesUnitsWerePlacedOn.add(placedUnit.getTile().position) } - if (placedUnit != null) - tilesUnitsWerePlacedOn.add(placedUnit.getTile().position) + if (tilesUnitsWerePlacedOn.isEmpty()) return false + + val notificationText = getNotificationText( + notification, triggerNotificationText, + "Gained [${tilesUnitsWerePlacedOn.size}] [${civUnit.name}] unit(s)" + ) + + if (notificationText != null) + civInfo.addNotification( + notificationText, + MapUnitAction(tilesUnitsWerePlacedOn), + NotificationCategory.Units, + civUnit.name + ) + return true } - if (tilesUnitsWerePlacedOn.isEmpty()) return false - - val notificationText = getNotificationText(notification, triggerNotificationText, - "Gained [${tilesUnitsWerePlacedOn.size}] [${civUnit.name}] unit(s)") - ?: return true - - civInfo.addNotification( - notificationText, - MapUnitAction(tilesUnitsWerePlacedOn), - NotificationCategory.Units, - civUnit.name - ) - return true + return { placeUnits() } } UniqueType.OneTimeFreeUnitRuins -> { var civUnit = civInfo.getEquivalentUnit(unique.params[0]) @@ -174,147 +199,171 @@ object UniqueTriggerActivation { .firstOrNull { it.getMatchingUniques(UniqueType.BuildImprovements) .any { unique -> unique.params[0] == "Land" } - } ?: return false + } ?: return null civUnit = civInfo.getEquivalentUnit(replacementUnit.name) } val placingTile = tile ?: civInfo.cities.random().getCenterTile() - val placedUnit = civInfo.units.placeUnitNearTile(placingTile.position, civUnit.name) - if (notification != null && placedUnit != null) { - val notificationText = - if (notification.hasPlaceholderParameters()) - notification.fillPlaceholders(unique.params[0]) - else notification - civInfo.addNotification( - notificationText, - sequence { - yield(MapUnitAction(placedUnit)) - yieldAll(LocationAction(tile?.position)) - }, - NotificationCategory.Units, - placedUnit.name - ) + fun placeUnit():Boolean { + val placedUnit = civInfo.units.placeUnitNearTile(placingTile.position, civUnit.name) + if (notification != null && placedUnit != null) { + val notificationText = + if (notification.hasPlaceholderParameters()) + notification.fillPlaceholders(unique.params[0]) + else notification + civInfo.addNotification( + notificationText, + sequence { + yield(MapUnitAction(placedUnit)) + yieldAll(LocationAction(tile?.position)) + }, + NotificationCategory.Units, + placedUnit.name + ) + } + return placedUnit != null } - return placedUnit != null + return {placeUnit()} } UniqueType.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 (civInfo.isSpectator()) return null - val notificationText = getNotificationText(notification, triggerNotificationText, - "You may choose a free Policy") - ?: return true + return { + civInfo.policies.freePolicies++ - civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Culture) - return true + val notificationText = getNotificationText(notification, triggerNotificationText, + "You may choose a free Policy") + if (notificationText != null) + civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Culture) + true + } } UniqueType.OneTimeAmountFreePolicies -> { - if (civInfo.isSpectator()) return false + if (civInfo.isSpectator()) return null val newFreePolicies = unique.params[0].toInt() - civInfo.policies.freePolicies += newFreePolicies - val notificationText = getNotificationText(notification, triggerNotificationText, - "You may choose [$newFreePolicies] free Policies") - ?: return true + return { + civInfo.policies.freePolicies += newFreePolicies - civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Culture) - return true + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You may choose [$newFreePolicies] free Policies" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Culture) + true + } } UniqueType.OneTimeAdoptPolicy -> { val policyName = unique.params[0] - if (civInfo.policies.isAdopted(policyName)) return false - val policy = civInfo.gameInfo.ruleset.policies[policyName] ?: return false - civInfo.policies.freePolicies++ - civInfo.policies.adopt(policy) + if (civInfo.policies.isAdopted(policyName)) return null + val policy = civInfo.gameInfo.ruleset.policies[policyName] ?: return null - val notificationText = getNotificationText(notification, triggerNotificationText, - "You gain the [$policyName] Policy") - ?: return true + return { + civInfo.policies.freePolicies++ + civInfo.policies.adopt(policy) - civInfo.addNotification(notificationText, PolicyAction(policyName), NotificationCategory.General, NotificationIcon.Culture) - return true + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You gain the [$policyName] Policy" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, PolicyAction(policyName), NotificationCategory.General, NotificationIcon.Culture) + true + } } UniqueType.OneTimeEnterGoldenAge, UniqueType.OneTimeEnterGoldenAgeTurns -> { - if (unique.type == UniqueType.OneTimeEnterGoldenAgeTurns) civInfo.goldenAges.enterGoldenAge(unique.params[0].toInt()) - else civInfo.goldenAges.enterGoldenAge() + return { + if (unique.type == UniqueType.OneTimeEnterGoldenAgeTurns) civInfo.goldenAges.enterGoldenAge(unique.params[0].toInt()) + else civInfo.goldenAges.enterGoldenAge() - val notificationText = getNotificationText(notification, triggerNotificationText, - "You enter a Golden Age") - ?: return true - - civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Happiness) - return true + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You enter a Golden Age" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Happiness) + true + } } UniqueType.OneTimeFreeGreatPerson -> { - if (civInfo.isSpectator()) return false - civInfo.greatPeople.freeGreatPeople++ - // Anyone an idea for a good icon? - if (notification != null) - civInfo.addNotification(notification, NotificationCategory.General) + if (civInfo.isSpectator()) return null + return { + civInfo.greatPeople.freeGreatPeople++ + // Anyone an idea for a good icon? + if (notification != null) + civInfo.addNotification(notification, NotificationCategory.General) - if (civInfo.isAI() || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) { - NextTurnAutomation.chooseGreatPerson(civInfo) + if (civInfo.isAI() || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) { + NextTurnAutomation.chooseGreatPerson(civInfo) + } + true } - - return true } UniqueType.OneTimeGainPopulation -> { val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } - for (applicableCity in applicableCities) { - applicableCity.population.addPopulation(unique.params[0].toInt()) + if (applicableCities.none()) return null + return { + for (applicableCity in applicableCities) { + applicableCity.population.addPopulation(unique.params[0].toInt()) + } + if (notification != null) + civInfo.addNotification( + notification, + LocationAction(applicableCities.map { it.location }), + NotificationCategory.Cities, + NotificationIcon.Population + ) + true } - if (notification != null && applicableCities.any()) - civInfo.addNotification( - notification, - LocationAction(applicableCities.map { it.location }), - NotificationCategory.Cities, - NotificationIcon.Population - ) - return applicableCities.any() } UniqueType.OneTimeGainPopulationRandomCity -> { - if (civInfo.cities.isEmpty()) return false - val randomCity = civInfo.cities.random(tileBasedRandom) - randomCity.population.addPopulation(unique.params[0].toInt()) - if (notification != null) { - val notificationText = - if (notification.hasPlaceholderParameters()) - notification.fillPlaceholders(randomCity.name) - else notification - civInfo.addNotification( - notificationText, - LocationAction(randomCity.location, tile?.position), - NotificationCategory.Cities, - NotificationIcon.Population - ) + if (civInfo.cities.isEmpty()) return null + return { + val randomCity = civInfo.cities.random(tileBasedRandom) + randomCity.population.addPopulation(unique.params[0].toInt()) + if (notification != null) { + val notificationText = + if (notification.hasPlaceholderParameters()) + notification.fillPlaceholders(randomCity.name) + else notification + civInfo.addNotification( + notificationText, + LocationAction(randomCity.location, tile?.position), + NotificationCategory.Cities, + NotificationIcon.Population + ) + } + true } - return true } UniqueType.OneTimeFreeTech -> { - if (civInfo.isSpectator()) return false - civInfo.tech.freeTechs += 1 - if (notification != null) { - civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Science) + if (civInfo.isSpectator()) return null + return { + civInfo.tech.freeTechs += 1 + if (notification != null) + civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Science) + true } - return true } UniqueType.OneTimeAmountFreeTechs -> { - if (civInfo.isSpectator()) return false - civInfo.tech.freeTechs += unique.params[0].toInt() - if (notification != null) { - civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Science) + if (civInfo.isSpectator()) return null + return { + civInfo.tech.freeTechs += unique.params[0].toInt() + if (notification != null) + civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Science) + true } - return true } UniqueType.OneTimeFreeTechRuins -> { val researchableTechsFromThatEra = ruleSet.technologies.values @@ -322,120 +371,139 @@ object UniqueTriggerActivation { (it.column!!.era == unique.params[1] || unique.params[1] == "any era") && civInfo.tech.canBeResearched(it.name) } - if (researchableTechsFromThatEra.isEmpty()) return false + if (researchableTechsFromThatEra.isEmpty()) return null - val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom) - .take(unique.params[0].toInt()) - for (tech in techsToResearch) - civInfo.tech.addTechnology(tech.name) + return { + val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom) + .take(unique.params[0].toInt()) + for (tech in techsToResearch) + civInfo.tech.addTechnology(tech.name) - if (notification != null) { - val notificationText = - if (notification.hasPlaceholderParameters()) - notification.fillPlaceholders(*(techsToResearch.map { it.name } - .toTypedArray())) - else notification - // Notification click for first tech only, supporting multiple adds little value. - // Relies on RulesetValidator catching <= 0! - val notificationActions: Sequence = - LocationAction(tile?.position) + TechAction(techsToResearch.first().name) - civInfo.addNotification(notificationText, notificationActions, - NotificationCategory.General, NotificationIcon.Science) + if (notification != null) { + val notificationText = + if (notification.hasPlaceholderParameters()) + notification.fillPlaceholders(*(techsToResearch.map { it.name } + .toTypedArray())) + else notification + // Notification click for first tech only, supporting multiple adds little value. + // Relies on RulesetValidator catching <= 0! + val notificationActions: Sequence = + LocationAction(tile?.position) + TechAction(techsToResearch.first().name) + civInfo.addNotification( + notificationText, notificationActions, + NotificationCategory.General, NotificationIcon.Science + ) + } + true } - - return true } UniqueType.OneTimeDiscoverTech -> { val techName = unique.params[0] - if (civInfo.tech.isResearched(techName)) return false - civInfo.tech.addTechnology(techName) + if (civInfo.tech.isResearched(techName)) return null - val notificationText = getNotificationText(notification, triggerNotificationText, - "You have discovered the secrets of [$techName]") - ?: return true - - civInfo.addNotification(notificationText, TechAction(techName), NotificationCategory.General, NotificationIcon.Science) - return true + return { + civInfo.tech.addTechnology(techName) + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You have discovered the secrets of [$techName]" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, TechAction(techName), NotificationCategory.General, NotificationIcon.Science) + true + } } UniqueType.StrategicResourcesIncrease -> { - civInfo.cache.updateCivResources() - if (notification != null) { - civInfo.addNotification( - notification, - NotificationCategory.General, - NotificationIcon.Construction - ) + return { + civInfo.cache.updateCivResources() + if (notification != null) + civInfo.addNotification( + notification, + NotificationCategory.General, + NotificationIcon.Construction + ) + true } - return true } UniqueType.OneTimeProvideResources -> { val resourceName = unique.params[1] - val resource = ruleSet.tileResources[resourceName] ?: return false - if (!resource.isStockpiled()) return false + val resource = ruleSet.tileResources[resourceName] ?: return null + if (!resource.isStockpiled()) return null - val amount = unique.params[0].toInt() - civInfo.resourceStockpiles.add(resourceName, amount) + return { + val amount = unique.params[0].toInt() + civInfo.resourceStockpiles.add(resourceName, amount) - val notificationText = getNotificationText(notification, triggerNotificationText, - "You have gained [$amount] [$resourceName]") - ?: return true - - civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName") - return true + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You have gained [$amount] [$resourceName]" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName") + true + } } UniqueType.OneTimeConsumeResources -> { val resourceName = unique.params[1] - val resource = ruleSet.tileResources[resourceName] ?: return false - if (!resource.isStockpiled()) return false + val resource = ruleSet.tileResources[resourceName] ?: return null + if (!resource.isStockpiled()) return null - val amount = unique.params[0].toInt() - civInfo.resourceStockpiles.add(resourceName, -amount) + return { + val amount = unique.params[0].toInt() + civInfo.resourceStockpiles.add(resourceName, -amount) - val notificationText = getNotificationText(notification, triggerNotificationText, - "You have lost [$amount] [$resourceName]") - ?: return true - - civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName") - return true + val notificationText = getNotificationText( + notification, triggerNotificationText, + "You have lost [$amount] [$resourceName]" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, NotificationCategory.General, NotificationIcon.Science, "ResourceIcons/$resourceName") + true + } } UniqueType.OneTimeRevealEntireMap -> { - if (notification != null) { - civInfo.addNotification(notification, LocationAction(tile?.position), NotificationCategory.General, NotificationIcon.Scout) + return { + if (notification != null) { + civInfo.addNotification(notification, LocationAction(tile?.position), NotificationCategory.General, NotificationIcon.Scout) + } + civInfo.gameInfo.tileMap.values.asSequence() + .forEach { it.setExplored(civInfo, true) } + true } - civInfo.gameInfo.tileMap.values.asSequence() - .forEach { it.setExplored(civInfo, true) } - return true } UniqueType.UnitsGainPromotion -> { val filter = unique.params[0] val promotion = unique.params[1] - val promotedUnitLocations: MutableList = mutableListOf() - for (civUnit in civInfo.units.getCivUnits()) { - if (civUnit.matchesFilter(filter) - && ruleSet.unitPromotions.values.any { - it.name == promotion && civUnit.type.name in it.unitTypes + val unitsToPromote = civInfo.units.getCivUnits().filter { it.matchesFilter(filter) } + .filter { unitToPromote -> + ruleSet.unitPromotions.values.any { + it.name == promotion && unitToPromote.type.name in it.unitTypes } - ) { + }.toList() + if (unitsToPromote.isEmpty()) return null + + return { + val promotedUnitLocations: MutableList = mutableListOf() + for (civUnit in unitsToPromote) { civUnit.promotions.addPromotion(promotion, isFree = true) promotedUnitLocations.add(civUnit.getTile().position) } - } - if (notification != null) { - civInfo.addNotification( - notification, - MapUnitAction(promotedUnitLocations), - NotificationCategory.Units, - "unitPromotionIcons/${unique.params[1]}" - ) + if (notification != null) { + civInfo.addNotification( + notification, + MapUnitAction(promotedUnitLocations), + NotificationCategory.Units, + "unitPromotionIcons/${unique.params[1]}" + ) + } + true } - return promotedUnitLocations.isNotEmpty() } /** @@ -454,144 +522,158 @@ object UniqueTriggerActivation { * I could parametrize the 'Allied' of the Unique text, but eh. */ UniqueType.CityStateCanGiftGreatPeople -> { - civInfo.addFlag( - CivFlags.CityStateGreatPersonGift.name, - civInfo.cityStateFunctions.turnsForGreatPersonFromCityState() / 2 - ) - if (notification != null) { - civInfo.addNotification(notification, NotificationCategory.Diplomacy, NotificationIcon.CityState) + return { + civInfo.addFlag( + CivFlags.CityStateGreatPersonGift.name, + civInfo.cityStateFunctions.turnsForGreatPersonFromCityState() / 2 + ) + if (notification != null) + civInfo.addNotification(notification, NotificationCategory.Diplomacy, NotificationIcon.CityState) + true } - return true } UniqueType.OneTimeGainStat -> { - val stat = Stat.safeValueOf(unique.params[1]) ?: return false + val stat = Stat.safeValueOf(unique.params[1]) ?: return null if (stat !in Stat.statsWithCivWideField || unique.params[0].toIntOrNull() == null - ) return false + ) return null - val statAmount = unique.params[0].toInt() - val stats = Stats().add(stat, statAmount.toFloat()) - civInfo.addStats(stats) + return { + val statAmount = unique.params[0].toInt() + val stats = Stats().add(stat, statAmount.toFloat()) + civInfo.addStats(stats) - val filledNotification = if(notification!=null && notification.hasPlaceholderParameters()) - notification.fillPlaceholders(statAmount.toString()) - else notification + val filledNotification = if (notification != null && notification.hasPlaceholderParameters()) + notification.fillPlaceholders(statAmount.toString()) + else notification - val notificationText = getNotificationText(filledNotification, triggerNotificationText, - "Gained [${stats.toStringForNotifications()}]") - ?: return true - - civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) - return true + val notificationText = getNotificationText( + filledNotification, triggerNotificationText, + "Gained [${stats.toStringForNotifications()}]" + ) + if (notificationText != null) + civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) + true + } } UniqueType.OneTimeGainStatSpeed -> { - val stat = Stat.safeValueOf(unique.params[1]) ?: return false + val stat = Stat.safeValueOf(unique.params[1]) ?: return null if (stat !in Stat.statsWithCivWideField || unique.params[0].toIntOrNull() == null - ) return false + ) return null - val statAmount = (unique.params[0].toInt() * (civInfo.gameInfo.speed.statCostModifiers[stat]!!)).roundToInt() - val stats = Stats().add(stat, statAmount.toFloat()) - civInfo.addStats(stats) + return { + val statAmount = (unique.params[0].toInt() * (civInfo.gameInfo.speed.statCostModifiers[stat]!!)).roundToInt() + val stats = Stats().add(stat, statAmount.toFloat()) + civInfo.addStats(stats) - val filledNotification = if(notification!=null && notification.hasPlaceholderParameters()) - notification.fillPlaceholders(statAmount.toString()) - else notification + val filledNotification = if (notification != null && notification.hasPlaceholderParameters()) + notification.fillPlaceholders(statAmount.toString()) + else notification - val notificationText = getNotificationText(filledNotification, triggerNotificationText, - "Gained [${stats.toStringForNotifications()}]") - ?: return true + val notificationText = getNotificationText( + filledNotification, triggerNotificationText, + "Gained [${stats.toStringForNotifications()}]" + ) - civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) - return true + if (notificationText != null) + civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) + true + } } UniqueType.OneTimeGainStatRange -> { - val stat = Stat.safeValueOf(unique.params[2]) ?: return false + val stat = Stat.safeValueOf(unique.params[2]) ?: return null if (stat !in Stat.statsWithCivWideField || unique.params[0].toIntOrNull() == null || unique.params[1].toIntOrNull() == null - ) return false + ) return null + val finalStatAmount = (tileBasedRandom.nextInt(unique.params[0].toInt(), unique.params[1].toInt()) * civInfo.gameInfo.speed.statCostModifiers[stat]!!).roundToInt() - val stats = Stats().add(stat, finalStatAmount.toFloat()) - civInfo.addStats(stats) + return { + val stats = Stats().add(stat, finalStatAmount.toFloat()) + civInfo.addStats(stats) - val filledNotification = if (notification!=null && notification.hasPlaceholderParameters()) - notification.fillPlaceholders(finalStatAmount.toString()) - else notification + val filledNotification = if (notification != null && notification.hasPlaceholderParameters()) + notification.fillPlaceholders(finalStatAmount.toString()) + else notification - val notificationText = getNotificationText(filledNotification, triggerNotificationText, - "Gained [${stats.toStringForNotifications()}]") - ?: return true + val notificationText = getNotificationText( + filledNotification, triggerNotificationText, + "Gained [${stats.toStringForNotifications()}]" + ) - civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) - - return true + if (notificationText != null) + civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.General, stat.notificationIcon) + true + } } UniqueType.OneTimeGainPantheon -> { - if (civInfo.religionManager.religionState != ReligionState.None) return false + if (civInfo.religionManager.religionState != ReligionState.None) return null val gainedFaith = civInfo.religionManager.faithForPantheon(2) - if (gainedFaith == 0) return false + if (gainedFaith == 0) return null - civInfo.addStat(Stat.Faith, gainedFaith) + return { + civInfo.addStat(Stat.Faith, gainedFaith) - if (notification != null) { - val notificationText = - if (notification.hasPlaceholderParameters()) - notification.fillPlaceholders(gainedFaith.toString()) - else notification - civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.Religion, NotificationIcon.Faith) + if (notification != null) { + val notificationText = + if (notification.hasPlaceholderParameters()) + notification.fillPlaceholders(gainedFaith.toString()) + else notification + civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.Religion, NotificationIcon.Faith) + } + true } - - return true } UniqueType.OneTimeGainProphet -> { - if (civInfo.religionManager.getGreatProphetEquivalent() == null) return false + if (civInfo.religionManager.getGreatProphetEquivalent() == null) return null val gainedFaith = (civInfo.religionManager.faithForNextGreatProphet() * (unique.params[0].toFloat() / 100f)).toInt() - if (gainedFaith == 0) return false + if (gainedFaith == 0) return null - civInfo.addStat(Stat.Faith, gainedFaith) + return { + civInfo.addStat(Stat.Faith, gainedFaith) - if (notification != null) { - val notificationText = - if (notification.hasPlaceholderParameters()) - notification.fillPlaceholders(gainedFaith.toString()) - else notification - civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.Religion, NotificationIcon.Faith) + if (notification != null) { + val notificationText = + if (notification.hasPlaceholderParameters()) + notification.fillPlaceholders(gainedFaith.toString()) + else notification + civInfo.addNotification(notificationText, LocationAction(tile?.position), NotificationCategory.Religion, NotificationIcon.Faith) + } + true } - - return true } UniqueType.OneTimeFreeBelief -> { - if (!civInfo.isMajorCiv()) return false + if (!civInfo.isMajorCiv()) return null val beliefType = BeliefType.valueOf(unique.params[0]) val religionManager = civInfo.religionManager if ((beliefType != BeliefType.Pantheon && beliefType != BeliefType.Any) && religionManager.religionState <= ReligionState.Pantheon) - return false // situation where we're trying to add a formal religion belief to a civ that hasn't founded a religion + return null // situation where we're trying to add a formal religion belief to a civ that hasn't founded a religion if (religionManager.numberOfBeliefsAvailable(beliefType) == 0) - return false // no more available beliefs of this type + return null // no more available beliefs of this type - if (beliefType == BeliefType.Any && religionManager.religionState <= ReligionState.Pantheon) - religionManager.freeBeliefs.add(BeliefType.Pantheon.name, 1) // add pantheon instead of any type - else - religionManager.freeBeliefs.add(beliefType.name, 1) - return true + return { + if (beliefType == BeliefType.Any && religionManager.religionState <= ReligionState.Pantheon) + religionManager.freeBeliefs.add(BeliefType.Pantheon.name, 1) // add pantheon instead of any type + else + religionManager.freeBeliefs.add(beliefType.name, 1) + true + } } UniqueType.OneTimeRevealSpecificMapTiles -> { - - if (tile == null) - return false + if (tile == null) return null // "Reveal up to [amount/'all'] [tileFilter] within a [amount] tile radius" val amount = unique.params[0] @@ -605,41 +687,40 @@ object UniqueTriggerActivation { .filter { !it.isExplored(civInfo) && it.matchesFilter(filter) } if (explorableTiles.none()) - return false + return null if (!isAll) { explorableTiles.shuffled(tileBasedRandom) explorableTiles = explorableTiles.take(amount.toInt()) } - for (explorableTile in explorableTiles) { - explorableTile.setExplored(civInfo, true) - positions += explorableTile.position - if (explorableTile.improvement == null) - civInfo.lastSeenImprovement.remove(explorableTile.position) - else - civInfo.lastSeenImprovement[explorableTile.position] = explorableTile.improvement!! - } + return { + for (explorableTile in explorableTiles) { + explorableTile.setExplored(civInfo, true) + positions += explorableTile.position + if (explorableTile.improvement == null) + civInfo.lastSeenImprovement.remove(explorableTile.position) + else + civInfo.lastSeenImprovement[explorableTile.position] = explorableTile.improvement!! + } - if (notification != null) { - civInfo.addNotification( - notification, - LocationAction(positions), - NotificationCategory.War, - if (unique.params[1] == Constants.barbarianEncampment) - NotificationIcon.Barbarians else NotificationIcon.Scout - ) + if (notification != null) { + civInfo.addNotification( + notification, + LocationAction(positions), + NotificationCategory.War, + if (unique.params[1] == Constants.barbarianEncampment) + NotificationIcon.Barbarians else NotificationIcon.Scout + ) + } + true } - - return true } UniqueType.OneTimeRevealCrudeMap -> { - if (tile == null) - return false + if (tile == null) return null // "From a randomly chosen tile [amount] tiles away from the ruins, // reveal tiles up to [amount] tiles away with [amount]% chance" - val distance = unique.params[0].toInt() val radius = unique.params[1].toInt() val chance = unique.params[2].toFloat() / 100f @@ -648,49 +729,61 @@ object UniqueTriggerActivation { .filter { !it.isExplored(civInfo) } .toList() .randomOrNull(tileBasedRandom) - ?: return false - revealCenter.getTilesInDistance(radius) - .filter { tileBasedRandom.nextFloat() < chance } - .forEach { it.setExplored(civInfo, true) } - civInfo.cache.updateViewableTiles() - if (notification != null) - civInfo.addNotification( - notification, - tile.position, - NotificationCategory.General, - NotificationIcon.Ruins - ) - return true + ?: return null + + return { + revealCenter.getTilesInDistance(radius) + .filter { tileBasedRandom.nextFloat() < chance } + .forEach { it.setExplored(civInfo, true) } + civInfo.cache.updateViewableTiles() + if (notification != null) + civInfo.addNotification( + notification, + tile.position, + NotificationCategory.General, + NotificationIcon.Ruins + ) + true + } } UniqueType.OneTimeTriggerVoting -> { - for (civ in civInfo.gameInfo.civilizations) - if (!civ.isBarbarian() && !civ.isSpectator()) - civ.addFlag( - CivFlags.TurnsTillNextDiplomaticVote.name, - civInfo.getTurnsBetweenDiplomaticVotes() - ) - if (notification != null) - civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Diplomacy) - return true + return { + for (civ in civInfo.gameInfo.civilizations) + if (!civ.isBarbarian() && !civ.isSpectator()) + civ.addFlag( + CivFlags.TurnsTillNextDiplomaticVote.name, + civInfo.getTurnsBetweenDiplomaticVotes() + ) + if (notification != null) + civInfo.addNotification(notification, NotificationCategory.General, NotificationIcon.Diplomacy) + true + } } UniqueType.OneTimeGlobalSpiesWhenEnteringEra -> { - if (!civInfo.isMajorCiv()) return false - if (!civInfo.gameInfo.isEspionageEnabled()) return false - val currentEra = civInfo.getEra().name - for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) { - if (currentEra !in otherCiv.espionageManager.erasSpyEarnedFor) { - val spyName = otherCiv.espionageManager.addSpy() - otherCiv.espionageManager.erasSpyEarnedFor.add(currentEra) - if (otherCiv == civInfo || otherCiv.knows(civInfo)) + if (!civInfo.isMajorCiv()) return null + if (!civInfo.gameInfo.isEspionageEnabled()) return null + + return { + val currentEra = civInfo.getEra().name + for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) { + if (currentEra !in otherCiv.espionageManager.erasSpyEarnedFor) { + val spyName = otherCiv.espionageManager.addSpy() + otherCiv.espionageManager.erasSpyEarnedFor.add(currentEra) + if (otherCiv == civInfo || otherCiv.knows(civInfo)) // We don't tell which civilization entered the new era, as that is done in the notification directly above this one - otherCiv.addNotification("We have recruited [${spyName}] as a spy!", NotificationCategory.Espionage, NotificationIcon.Spy) - else - otherCiv.addNotification("After an unknown civilization entered the [${currentEra}], we have recruited [${spyName}] as a spy!", NotificationCategory.Espionage, NotificationIcon.Spy) + otherCiv.addNotification("We have recruited [${spyName}] as a spy!", NotificationCategory.Espionage, NotificationIcon.Spy) + else + otherCiv.addNotification( + "After an unknown civilization entered the [${currentEra}], we have recruited [${spyName}] as a spy!", + NotificationCategory.Espionage, + NotificationIcon.Spy + ) + } } + true } - return true } UniqueType.GainFreeBuildings -> { @@ -698,119 +791,144 @@ object UniqueTriggerActivation { val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } - for (applicableCity in applicableCities) { - applicableCity.cityConstructions.freeBuildingsProvidedFromThisCity.addToMapOfSets(applicableCity.id, freeBuilding.name) + if (applicableCities.none()) return null - if (applicableCity.cityConstructions.containsBuildingOrEquivalent(freeBuilding.name)) continue - applicableCity.cityConstructions.constructionComplete(freeBuilding) + return { + for (applicableCity in applicableCities) { + applicableCity.cityConstructions.freeBuildingsProvidedFromThisCity.addToMapOfSets(applicableCity.id, freeBuilding.name) + + if (applicableCity.cityConstructions.containsBuildingOrEquivalent(freeBuilding.name)) continue + applicableCity.cityConstructions.constructionComplete(freeBuilding) + } + true } - return true } UniqueType.FreeStatBuildings -> { - val stat = Stat.safeValueOf(unique.params[0]) ?: return false - civInfo.civConstructions.addFreeStatBuildings(stat, unique.params[1].toInt()) - return true + val stat = Stat.safeValueOf(unique.params[0]) ?: return null + return { + civInfo.civConstructions.addFreeStatBuildings(stat, unique.params[1].toInt()) + true + } } UniqueType.FreeSpecificBuildings ->{ - val building = ruleSet.buildings[unique.params[0]] ?: return false - civInfo.civConstructions.addFreeBuildings(building, unique.params[1].toInt()) - return true + val building = ruleSet.buildings[unique.params[0]] ?: return null + return { + civInfo.civConstructions.addFreeBuildings(building, unique.params[1].toInt()) + true + } } UniqueType.RemoveBuilding -> { val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } + if (applicableCities.none()) return null - for (applicableCity in applicableCities) { - val buildingsToRemove = applicableCity.cityConstructions.getBuiltBuildings().filter { - it.matchesFilter(unique.params[0]) - }.toSet() - applicableCity.cityConstructions.removeBuildings(buildingsToRemove) + return { + for (applicableCity in applicableCities) { + val buildingsToRemove = applicableCity.cityConstructions.getBuiltBuildings().filter { + it.matchesFilter(unique.params[0]) + }.toSet() + applicableCity.cityConstructions.removeBuildings(buildingsToRemove) + } + true } - - return true } UniqueType.SellBuilding -> { val applicableCities = if (unique.params[1] == "in this city") sequenceOf(relevantCity!!) else civInfo.cities.asSequence().filter { it.matchesFilter(unique.params[1]) } + if (applicableCities.none()) return null - for (applicableCity in applicableCities) { - val buildingsToSell = applicableCity.cityConstructions.getBuiltBuildings().filter { - it.matchesFilter(unique.params[0]) && it.isSellable() - } + return { + for (applicableCity in applicableCities) { + val buildingsToSell = applicableCity.cityConstructions.getBuiltBuildings().filter { + it.matchesFilter(unique.params[0]) && it.isSellable() + } - for (building in buildingsToSell) { - applicableCity.sellBuilding(building) + for (building in buildingsToSell) applicableCity.sellBuilding(building) } + true } - - return true } UniqueType.OneTimeUnitHeal -> { - if (unit == null) return false - unit.healBy(unique.params[0].toInt()) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? - return true + if (unit == null) return null + if (unit.health == 100) + return { + unit.healBy(unique.params[0].toInt()) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? + true + } } UniqueType.OneTimeUnitDamage -> { - if (unit == null) return false - MapUnitCombatant(unit).takeDamage(unique.params[0].toInt()) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? - return true + if (unit == null) return null + return { + MapUnitCombatant(unit).takeDamage(unique.params[0].toInt()) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) // Do we have a heal icon? + true + } } UniqueType.OneTimeUnitGainXP -> { - if (unit == null) return false - if (!unit.baseUnit.isMilitary()) return false - unit.promotions.XP += unique.params[0].toInt() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true + if (unit == null) return null + if (!unit.baseUnit.isMilitary()) return null + return { + unit.promotions.XP += unique.params[0].toInt() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + true + } } UniqueType.OneTimeUnitUpgrade -> { - if (unit == null) return false + if (unit == null) return null val upgradeAction = UnitActionsUpgrade.getFreeUpgradeAction(unit) - if (upgradeAction.none()) return false - (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true + if (upgradeAction.none()) return null + return { + (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + true + } } UniqueType.OneTimeUnitSpecialUpgrade -> { - if (unit == null) return false + if (unit == null) return null val upgradeAction = UnitActionsUpgrade.getAncientRuinsUpgradeAction(unit) - if (upgradeAction.none()) return false - (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) - return true + if (upgradeAction.none()) return null + return { + (upgradeAction.minBy { (it as UpgradeUnitAction).unitToUpgradeTo.cost }).action!!() + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units) + true + } } UniqueType.OneTimeUnitGainPromotion -> { - if (unit == null) return false + if (unit == null) return null val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys .firstOrNull { it == unique.params[0] } - ?: return false - unit.promotions.addPromotion(promotion, true) - if (notification != null) - unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units, unit.name) - return true + ?: return null + return { + unit.promotions.addPromotion(promotion, true) + if (notification != null) + unit.civ.addNotification(notification, unit.getTile().position, NotificationCategory.Units, unit.name) + true + } } UniqueType.OneTimeUnitRemovePromotion -> { - if (unit == null) return false + if (unit == null) return null val promotion = unit.civ.gameInfo.ruleset.unitPromotions.keys .firstOrNull { it == unique.params[0]} - ?: return false - unit.promotions.removePromotion(promotion) - return true + ?: return null + return { + unit.promotions.removePromotion(promotion) + true + } } else -> {} } - return false + return null } private fun getNotificationText(notification: String?, triggerNotificationText: String?, effectNotificationText: String): String? {