Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4a65d93
Add Great Admiral
RobLoach Jul 31, 2025
f42a732
Add diplomacy status to civFilter
RobLoach Aug 30, 2025
6fc69b0
Switch language for civFilter diplomacy
RobLoach Sep 1, 2025
20e9c2b
Civs should be Friendly with themselves
RobLoach Sep 13, 2025
e0ef4ac
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Sep 13, 2025
0a69cd0
Every adjacent [mapUnitFilter] unit
RobLoach Sep 13, 2025
13d0c3c
Merge branch 'civfilter-diplomacy' of github.com:RobLoach/Unciv into …
RobLoach Sep 13, 2025
3378f01
Add Friendly keyword
RobLoach Sep 13, 2025
5f34cb8
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Sep 14, 2025
e520134
Fix the unit action text
RobLoach Sep 14, 2025
851e951
Remove Great Admiral
RobLoach Sep 26, 2025
864e2b0
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Sep 26, 2025
640db0b
Restore
RobLoach Sep 26, 2025
99684f2
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Sep 30, 2025
d38a082
Add Repair Fleet ability
RobLoach Sep 30, 2025
cd76e61
Great Admiral: Add Port Change ability
RobLoach Sep 30, 2025
e505e9c
Add comments
RobLoach Sep 30, 2025
318ae2c
Apply suggestion from @RobLoach
RobLoach Sep 30, 2025
6d53a9c
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Oct 6, 2025
1bab3f1
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Oct 8, 2025
16d6848
Fix
RobLoach Oct 8, 2025
1296832
Update android/assets/jsons/Civ V - Gods & Kings/Units.json
RobLoach Oct 26, 2025
4a1136d
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Oct 27, 2025
1556811
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Oct 31, 2025
dd248ef
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Nov 16, 2025
817c40e
Merge branch 'master' of github.com:yairm210/Unciv into add-great-adm…
RobLoach Jan 1, 2026
b33fcbe
Update
RobLoach Jan 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/assets/jsons/Civ V - Gods & Kings/Units.json
Original file line number Diff line number Diff line change
Expand Up @@ -1662,7 +1662,7 @@
"name": "Great Admiral",
"unitType": "Civilian Water",
"uniques": [
// TODO: "[Every adjacent [{Friendly} {Water}] unit] heals [100] HP <by consuming this unit>",
"[Every adjacent [{Friendly} {Water}] unit] heals [100] HP <by consuming this unit>", // Repair Fleet
"May Paradrop to [{your} {Coastal} {City center}] tiles up to [25] tiles away <in [City center] tiles>", // Change Port
"[+15]% Strength bonus for [{Military} {Water}] units within [2] tiles",
"Can be earned through combat <for [Water] units>",
Expand Down
2 changes: 1 addition & 1 deletion core/src/com/unciv/logic/battle/MapUnitCombatant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seem unnecessary to me

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unnecessary, but is...

MapUnitCombatant.kt:22:99 Argument type mismatch: actual type is 'Boolean', but 'GameContext?' was expected.

The matchesFilter signature is...

fun matchesFilter(filter: String, state: GameContext? = cache.state, multiFilter: Boolean = true)

Just looks weird because we're skipping the state.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha 👍🏿

override fun getAttackSound() = unit.baseUnit.attackSound.let {
if (it == null) UncivSound.Click else UncivSound(it)
}
Expand Down
14 changes: 7 additions & 7 deletions core/src/com/unciv/logic/map/mapunit/MapUnit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -607,22 +607,22 @@ 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
"City-State" -> civ.isCityState
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 (state != null && nonUnitUniquesMap.hasUnique(filter, state)) return true
if (promotions.promotions.contains(filter)) return true
if (hasStatus(filter)) return true
return false
Expand Down
14 changes: 12 additions & 2 deletions core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 com.unciv.models.translations.hasPlaceholderParameters
import yairm210.purity.annotations.Pure
Expand Down Expand Up @@ -596,9 +597,18 @@ 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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ 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.UncivSound
import com.unciv.models.translations.fillPlaceholders
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.models.translations.hasPlaceholderParameters
import com.unciv.models.translations.tr
import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsUpgrade
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -121,6 +124,28 @@ object UniqueTriggerActivation {
else Random(-550) // Very random indeed
val ruleset = civInfo.gameInfo.ruleset

// Allow iterating through all unit targets, if needed.
if (!unitTriggerableIteration &&
tile != null &&
unique.type?.targetTypes?.contains(UniqueTarget.UnitTriggerable) == true &&
!UniqueParameterType.UnitTriggerTarget.staticKnownValues.contains(unique.params[0])) {
// Every adjacent [mapUnitFilter] unit
val triggerFunctions = tile.neighbors
.flatMap { it.getUnits() }
.filter {
val mapUnitFilter = unique.params.getOrNull(0)?.getPlaceholderParameters()?.firstOrNull()
mapUnitFilter != null && it.matchesFilter(mapUnitFilter, gameContext)
}
.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ object UnitActionsFromUniques {
)
}
UniqueType.TriggerEvent -> unique.params[0]
else -> unique.text.removeConditionals()
else -> unique.text.removeConditionals().tr()
}
val title = UnitActionModifiers.actionTextWithSideEffects(baseTitle, unique, unit)

Expand Down
2 changes: 1 addition & 1 deletion docs/Modders/uniques.md
Original file line number Diff line number Diff line change
Expand Up @@ -4019,7 +4019,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.