Merge e520134d248827cb34010ddb785b65baec7bf927 into d51ef24c205b6b05330b3c4d7ce79c402db44447

This commit is contained in:
Rob Loach 2025-09-18 13:09:14 +00:00 committed by GitHub
commit 81258e56b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 402 additions and 348 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 KiB

After

Width:  |  Height:  |  Size: 904 KiB

View File

@ -9,6 +9,7 @@
"Cannot build [Settler] units <when below [-10] [Happiness]>",
"Rebel units may spawn <when below [-20] [Happiness]>",
"[-1] Sight <for [Embarked] units>",
"Enables [Great Admiral] units to enter ocean tiles",
// TODO: Implement the uniques below
// "[+20]% [Culture] [in all cities] <during a golden age>",

View File

@ -1658,6 +1658,17 @@
"Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 5
},
{
"name": "Great Admiral",
"unitType": "Civilian Water",
"uniques": [
"[Every adjacent [{Friendly} {Water}] unit] heals [100] HP <by consuming this unit>",
"[+15]% Strength bonus for [{Military} {Water}] units within [2] tiles",
"Can be earned through combat",
"Is part of Great Person group [Admiral]",
"Great Person - [War]", "Unbuildable", "Uncapturable"],
"movement": 4
},
/* Religious units */

View File

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

View File

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

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.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] */
@ -595,9 +596,19 @@ enum class UniqueParameterType(
override val staticKnownValues = setOf("Cost", "Strength")
},
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 MultiFilter.multiFilter(params[0], { MapUnitFilter.isKnownValue(it, ruleset) }, true)
}
return false
}
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset) = getErrorSeverityForFilter(parameterText, ruleset)
},
/** 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.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,8 @@ object UniqueTriggerActivation {
unit: MapUnit? = null,
tile: Tile? = city?.getCenterTile() ?: unit?.currentTile,
notification: String? = null,
triggerNotificationText: String? = null
triggerNotificationText: String? = null,
unitTriggerableIteration: Boolean = false
): (()->Boolean)? {
val relevantCity by lazy {
@ -121,6 +124,26 @@ object UniqueTriggerActivation {
else Random(-550) // Very random indeed
val ruleset = civInfo.gameInfo.ruleset
// 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(mapUnitFilter, gameContext) else false
}
.mapNotNull { getTriggerFunction(unique, civInfo, city, it, it.getTile(), notification, triggerNotificationText, true) }
if (triggerFunctions.none()) return null
return {
for (triggerFunction in triggerFunctions) {
triggerFunction.invoke()
}
true
}
}
when (unique.type) {
UniqueType.TriggerEvent -> {
val event = ruleset.events[unique.params[0]] ?: return null

View File

@ -258,7 +258,7 @@ object UnitActionsFromUniques {
)
}
UniqueType.TriggerEvent -> unique.params[0]
else -> unique.text.removeConditionals()
else -> unique.text.removeConditionals().tr()
}
val title = UnitActionModifiers.actionTextWithSideEffects(baseTitle, unique, unit)

View File

@ -160,6 +160,7 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
- [General](https://thenounproject.com/icon/general-933566) By anbileru adaleru for Great General
- [Religion](https://thenounproject.com/icon/preach-53064) by Bruno Gätjens González adapted for Missionary
- [invisibility cloak ](https://thenounproject.com/term/invisibility-cloak/1419648/) by Locad for Inquisitor
- [Anchor](https://thenounproject.com/icon/anchor-7764100/) by M. Adebadal for Great Admiral
### Units - AbsoluteUnits unitset images

View File

@ -3964,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.