Every adjacent [mapUnitFilter] unit

This commit is contained in:
Rob Loach 2025-09-13 14:37:32 -04:00
parent e0ef4acb3f
commit 0a69cd0a0e
No known key found for this signature in database
GPG Key ID: 627C60834A74A21A
7 changed files with 42 additions and 40 deletions

View File

@ -1663,7 +1663,7 @@
"unitType": "Civilian Water", "unitType": "Civilian Water",
"uniques": [ "uniques": [
"[+15]% Strength bonus for [{Military} {Water}] units within [2] tiles", "[+15]% Strength bonus for [{Military} {Water}] units within [2] tiles",
"[Target Unit] heals [100] HP <targeting [Water] units within [1] tiles> <by consuming this unit>", "[Every adjacent [Water] unit] heals [100] HP <by consuming this unit>",
"Can be earned through combat", "Can be earned through combat",
"Is part of Great Person group [Admiral]", "Is part of Great Person group [Admiral]",
"Great Person - [War]", "Unbuildable", "Uncapturable"], "Great Person - [War]", "Unbuildable", "Uncapturable"],

View File

@ -19,7 +19,7 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
override fun isDefeated(): Boolean = unit.health <= 0 override fun isDefeated(): Boolean = unit.health <= 0
override fun isInvisible(to: Civilization): Boolean = unit.isInvisible(to) override fun isInvisible(to: Civilization): Boolean = unit.isInvisible(to)
override fun canAttack(): Boolean = unit.canAttack() 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 { override fun getAttackSound() = unit.baseUnit.attackSound.let {
if (it == null) UncivSound.Click else UncivSound(it) if (it == null) UncivSound.Click else UncivSound(it)
} }

View File

@ -607,12 +607,12 @@ class MapUnit : IsPartOfGameInfoSerialization {
/** Implements [UniqueParameterType.MapUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.MapUnitFilter] */ /** Implements [UniqueParameterType.MapUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.MapUnitFilter] */
@Readonly @Readonly
fun matchesFilter(filter: String, multiFilter: Boolean = true): Boolean { fun matchesFilter(filter: String, state: GameContext? = cache.state, multiFilter: Boolean = true): Boolean =
return if (multiFilter) MultiFilter.multiFilter(filter, ::matchesSingleFilter) else matchesSingleFilter(filter) if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleFilter(it, state) })
} else matchesSingleFilter(filter, state)
@Readonly @Readonly
private fun matchesSingleFilter(filter: String): Boolean { private fun matchesSingleFilter(filter: String, state: GameContext? = cache.state): Boolean {
return when (filter) { return when (filter) {
Constants.wounded, "wounded units" -> health < 100 Constants.wounded, "wounded units" -> health < 100
Constants.barbarians, "Barbarian" -> civ.isBarbarian Constants.barbarians, "Barbarian" -> civ.isBarbarian
@ -620,9 +620,9 @@ class MapUnit : IsPartOfGameInfoSerialization {
Constants.embarked -> isEmbarked() Constants.embarked -> isEmbarked()
"Non-City" -> true "Non-City" -> true
else -> { else -> {
if (baseUnit.matchesFilter(filter, cache.state, false)) return true if (baseUnit.matchesFilter(filter, state, false)) return true
if (civ.matchesFilter(filter, cache.state, false)) return true if (civ.matchesFilter(filter, state, false)) return true
if (nonUnitUniquesMap.hasUnique(filter, cache.state)) return true if (nonUnitUniquesMap.hasUnique(filter, if (state == null) cache.state else state)) return true
if (promotions.promotions.contains(filter)) return true if (promotions.promotions.contains(filter)) return true
if (hasStatus(filter)) return true if (hasStatus(filter)) return true
return false return false

View File

@ -12,6 +12,7 @@ import com.unciv.models.ruleset.unique.UniqueParameterType.Companion.guessTypeFo
import com.unciv.models.ruleset.validation.Suppression import com.unciv.models.ruleset.validation.Suppression
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.SubStat import com.unciv.models.stats.SubStat
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.TranslationFileWriter import com.unciv.models.translations.TranslationFileWriter
import yairm210.purity.annotations.Pure import yairm210.purity.annotations.Pure
import yairm210.purity.annotations.Readonly import yairm210.purity.annotations.Readonly
@ -86,7 +87,7 @@ enum class UniqueParameterType(
// todo potentially remove if OneTimeRevealSpecificMapTiles changes // todo potentially remove if OneTimeRevealSpecificMapTiles changes
KeywordAll("'all'", "All", severityDefault = UniqueType.UniqueParameterErrorSeverity.RulesetInvariant) { 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] */ /** 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 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. */ /** Mod declarative compatibility: Define Mod relations by their name. */

View File

@ -29,8 +29,10 @@ import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Event import com.unciv.models.ruleset.Event
import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TerrainType
import com.unciv.models.ruleset.tile.TileResource 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.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.hasPlaceholderParameters import com.unciv.models.translations.hasPlaceholderParameters
@ -91,7 +93,7 @@ object UniqueTriggerActivation {
tile: Tile? = city?.getCenterTile() ?: unit?.currentTile, tile: Tile? = city?.getCenterTile() ?: unit?.currentTile,
notification: String? = null, notification: String? = null,
triggerNotificationText: String? = null, triggerNotificationText: String? = null,
ignoreMultiModifiers: Boolean = false unitTriggerableIteration: Boolean = false
): (()->Boolean)? { ): (()->Boolean)? {
val relevantCity by lazy { val relevantCity by lazy {
@ -121,25 +123,24 @@ object UniqueTriggerActivation {
else Random(-550) // Very random indeed else Random(-550) // Very random indeed
val ruleset = civInfo.gameInfo.ruleset val ruleset = civInfo.gameInfo.ruleset
// Modifier trigger to iterate through multiple units // Allow iterating through all unit targets, if needed.
if (unique.hasModifier(UniqueType.TargetingUnitsWithinTiles) && !ignoreMultiModifiers) { if (unique.type?.targetTypes?.contains(UniqueTarget.UnitTriggerable) == true && !unitTriggerableIteration && tile != null
val modifiers = unique.getModifiers(UniqueType.TargetingUnitsWithinTiles) !UniqueParameterType.UnitTriggerTarget.staticKnownValues.contains(unique.params[0])) {
if (tile != null) { // Every adjacent [mapUnitFilter] unit
val triggerFunctions = modifiers.flatMap { val triggerFunctions = tile.getTilesInDistance(1) // Adjacent
val mapUnitFilter = it.params[0] .flatMap { it.getUnits() }
tile.getTilesInDistance(it.params[1].toInt()) .filter {
.flatMap { it.getUnits() } val mapUnitFilter = unique.params[0]?.getPlaceholderParameters()?.firstOrNull()
.filter { it.matchesFilter(mapUnitFilter) } if (mapUnitFilter != null) it.matchesFilter(filterParam, gameContext) else false
.mapNotNull { getTriggerFunction(unique, civInfo, city, it, it.getTile(), notification, triggerNotificationText, true) }
} }
if (triggerFunctions.none()) return null .mapNotNull { getTriggerFunction(unique, civInfo, city, it, it.getTile(), notification, triggerNotificationText, true) }
return { if (triggerFunctions.none()) return null
for (triggerFunction in triggerFunctions) { return {
triggerFunction.invoke() for (triggerFunction in triggerFunctions) {
} triggerFunction.invoke()
true
} }
} else return null true
}
} }
when (unique.type) { when (unique.type) {

View File

@ -959,9 +959,6 @@ enum class UniqueType(
///////////////////////////////////////////// region 90 META ///////////////////////////////////////////// ///////////////////////////////////////////// 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, 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"), docDescription = "Turns this unique into a trigger, activating this unique as a *global* unique for a number of turns"),

View File

@ -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 Modifiers that can be added to other uniques changing user experience, not their behavior
??? example "&lt;targeting [mapUnitFilter] units within [positiveAmount] tiles&gt;"
Modifies this unique to activate on each of the given units within the certain amount of tiles
Example: "&lt;targeting [Wounded] units within [3] tiles&gt;"
Applicable to: MetaModifier
??? example "&lt;for [nonNegativeAmount] turns&gt;" ??? example "&lt;for [nonNegativeAmount] turns&gt;"
Turns this unique into a trigger, activating this unique as a *global* unique for a number of 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. *[tech]: The name of any tech.
*[terrainFeature]: The name of any terrain that is a terrain feature according to the json file. *[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' *[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. *[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. *[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. *[victoryType]: The name of any victory type: 'Cultural', 'Diplomatic', 'Domination', 'Scientific', 'Time' or one of your mod's VictoryTypes.json names.