diff --git a/android/assets/jsons/Civ V - Gods & Kings/Units.json b/android/assets/jsons/Civ V - Gods & Kings/Units.json index a0fb842dc2..1e6b13c183 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Units.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Units.json @@ -1663,7 +1663,7 @@ "unitType": "Civilian Water", "uniques": [ "[+15]% Strength bonus for [{Military} {Water}] units within [2] tiles", - "[Target Unit] heals [100] HP ", + "[Every adjacent [Water] unit] heals [100] HP ", "Can be earned through combat", "Is part of Great Person group [Admiral]", "Great Person - [War]", "Unbuildable", "Uncapturable"], diff --git a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt index fd6fefdce0..bdf7647eb7 100644 --- a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt +++ b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt @@ -19,7 +19,7 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant { override fun isDefeated(): Boolean = unit.health <= 0 override fun isInvisible(to: Civilization): Boolean = unit.isInvisible(to) override fun canAttack(): Boolean = unit.canAttack() - override fun matchesFilter(filter: String, multiFilter: Boolean) = unit.matchesFilter(filter, multiFilter) + override fun matchesFilter(filter: String, multiFilter: Boolean) = unit.matchesFilter(filter, multiFilter=multiFilter) override fun getAttackSound() = unit.baseUnit.attackSound.let { if (it == null) UncivSound.Click else UncivSound(it) } diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index b9fd2a85a0..e1d74bd4cb 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -607,12 +607,12 @@ class MapUnit : IsPartOfGameInfoSerialization { /** Implements [UniqueParameterType.MapUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.MapUnitFilter] */ @Readonly - fun matchesFilter(filter: String, multiFilter: Boolean = true): Boolean { - return if (multiFilter) MultiFilter.multiFilter(filter, ::matchesSingleFilter) else matchesSingleFilter(filter) - } + fun matchesFilter(filter: String, state: GameContext? = cache.state, multiFilter: Boolean = true): Boolean = + if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleFilter(it, state) }) + else matchesSingleFilter(filter, state) @Readonly - private fun matchesSingleFilter(filter: String): Boolean { + private fun matchesSingleFilter(filter: String, state: GameContext? = cache.state): Boolean { return when (filter) { Constants.wounded, "wounded units" -> health < 100 Constants.barbarians, "Barbarian" -> civ.isBarbarian @@ -620,9 +620,9 @@ class MapUnit : IsPartOfGameInfoSerialization { Constants.embarked -> isEmbarked() "Non-City" -> true else -> { - if (baseUnit.matchesFilter(filter, cache.state, false)) return true - if (civ.matchesFilter(filter, cache.state, false)) return true - if (nonUnitUniquesMap.hasUnique(filter, cache.state)) return true + if (baseUnit.matchesFilter(filter, state, false)) return true + if (civ.matchesFilter(filter, state, false)) return true + if (nonUnitUniquesMap.hasUnique(filter, if (state == null) cache.state else state)) return true if (promotions.promotions.contains(filter)) return true if (hasStatus(filter)) return true return false diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index 70ba5580b5..5a8d9e3b17 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -12,6 +12,7 @@ import com.unciv.models.ruleset.unique.UniqueParameterType.Companion.guessTypeFo import com.unciv.models.ruleset.validation.Suppression import com.unciv.models.stats.Stat import com.unciv.models.stats.SubStat +import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.TranslationFileWriter import yairm210.purity.annotations.Pure import yairm210.purity.annotations.Readonly @@ -86,7 +87,7 @@ enum class UniqueParameterType( // todo potentially remove if OneTimeRevealSpecificMapTiles changes KeywordAll("'all'", "All", severityDefault = UniqueType.UniqueParameterErrorSeverity.RulesetInvariant) { - override val staticKnownValues = Constants.all + override val staticKnownValues = Constants.all }, /** Implemented by [ICombatant.matchesCategory][com.unciv.logic.battle.ICombatant.matchesFilter] */ @@ -596,8 +597,18 @@ enum class UniqueParameterType( }, - UnitTriggerTarget("unitTriggerTarget", Constants.thisUnit, "`${Constants.thisUnit}` or `${Constants.targetUnit}`") { + UnitTriggerTarget("unitTriggerTarget", Constants.thisUnit, "`${Constants.thisUnit}`, `${Constants.targetUnit}`, or `Every adjacent [mapUnitFilter] unit`") { override val staticKnownValues = setOf(Constants.thisUnit, Constants.targetUnit) + + override fun isKnownValue(parameterText: String, ruleset: Ruleset): Boolean { + if (parameterText in staticKnownValues) return true + // Every adjacent [mapUnitFilter] unit + if (parameterText.startsWith("Every adjacent [") && parameterText.endsWith("] unit")) { + val params = parameterText.getPlaceholderParameters() + return MapUnitFilter.isKnownValue(params[0], ruleset) + } + return false + } }, /** Mod declarative compatibility: Define Mod relations by their name. */ diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 58b59bea70..153186e23d 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -29,8 +29,10 @@ import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.Event import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TileResource +import com.unciv.models.ruleset.unique.UniqueParameterType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats +import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.UncivSound import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.hasPlaceholderParameters @@ -91,7 +93,7 @@ object UniqueTriggerActivation { tile: Tile? = city?.getCenterTile() ?: unit?.currentTile, notification: String? = null, triggerNotificationText: String? = null, - ignoreMultiModifiers: Boolean = false + unitTriggerableIteration: Boolean = false ): (()->Boolean)? { val relevantCity by lazy { @@ -121,25 +123,24 @@ object UniqueTriggerActivation { else Random(-550) // Very random indeed val ruleset = civInfo.gameInfo.ruleset - // Modifier trigger to iterate through multiple units - if (unique.hasModifier(UniqueType.TargetingUnitsWithinTiles) && !ignoreMultiModifiers) { - val modifiers = unique.getModifiers(UniqueType.TargetingUnitsWithinTiles) - if (tile != null) { - val triggerFunctions = modifiers.flatMap { - val mapUnitFilter = it.params[0] - tile.getTilesInDistance(it.params[1].toInt()) - .flatMap { it.getUnits() } - .filter { it.matchesFilter(mapUnitFilter) } - .mapNotNull { getTriggerFunction(unique, civInfo, city, it, it.getTile(), notification, triggerNotificationText, true) } + // Allow iterating through all unit targets, if needed. + if (unique.type?.targetTypes?.contains(UniqueTarget.UnitTriggerable) == true && !unitTriggerableIteration && tile != null + !UniqueParameterType.UnitTriggerTarget.staticKnownValues.contains(unique.params[0])) { + // Every adjacent [mapUnitFilter] unit + val triggerFunctions = tile.getTilesInDistance(1) // Adjacent + .flatMap { it.getUnits() } + .filter { + val mapUnitFilter = unique.params[0]?.getPlaceholderParameters()?.firstOrNull() + if (mapUnitFilter != null) it.matchesFilter(filterParam, gameContext) else false } - if (triggerFunctions.none()) return null - return { - for (triggerFunction in triggerFunctions) { - triggerFunction.invoke() - } - true + .mapNotNull { getTriggerFunction(unique, civInfo, city, it, it.getTile(), notification, triggerNotificationText, true) } + if (triggerFunctions.none()) return null + return { + for (triggerFunction in triggerFunctions) { + triggerFunction.invoke() } - } else return null + true + } } when (unique.type) { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 28d859a90b..2e19b02fba 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -959,9 +959,6 @@ enum class UniqueType( ///////////////////////////////////////////// region 90 META ///////////////////////////////////////////// - TargetingUnitsWithinTiles("targeting [mapUnitFilter] units within [positiveAmount] tiles", UniqueTarget.MetaModifier, - docDescription = "Modifies this unique to activate on each of the given units within the certain amount of tiles"), - ConditionalTimedUnique("for [nonNegativeAmount] turns", UniqueTarget.MetaModifier, docDescription = "Turns this unique into a trigger, activating this unique as a *global* unique for a number of turns"), diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 5f9a5e7ffd..e0af336578 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -3874,13 +3874,6 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Modifiers that can be added to other uniques changing user experience, not their behavior -??? example "<targeting [mapUnitFilter] units within [positiveAmount] tiles>" - Modifies this unique to activate on each of the given units within the certain amount of tiles - - Example: "<targeting [Wounded] units within [3] tiles>" - - Applicable to: MetaModifier - ??? example "<for [nonNegativeAmount] turns>" Turns this unique into a trigger, activating this unique as a *global* unique for a number of turns @@ -3971,7 +3964,7 @@ There is a conversion affecting dashes and leading/trailing blanks. Please make *[tech]: The name of any tech. *[terrainFeature]: The name of any terrain that is a terrain feature according to the json file. *[tileFilter]: Anything that can be used either in an improvementFilter or in a terrainFilter can be used here, plus 'unimproved' -*[unitTriggerTarget]: `This Unit` or `Target Unit`. +*[unitTriggerTarget]: `This Unit`, `Target Unit`, or `Every adjacent [mapUnitFilter] unit`. *[unitType]: Can be 'Land', 'Water', 'Air', any unit type, a filtering Unique on a unit type, or a multi-filter of these. *[validationWarning]: Suppresses one specific Ruleset validation warning. This can specify the full text verbatim including correct upper/lower case, or it can be a wildcard case-insensitive simple pattern starting and ending in an asterisk ('*'). If the suppression unique is used within an object or as modifier (not ModOptions), the wildcard symbols can be omitted, as selectivity is better due to the limited scope. *[victoryType]: The name of any victory type: 'Cultural', 'Diplomatic', 'Domination', 'Scientific', 'Time' or one of your mod's VictoryTypes.json names. \ No newline at end of file