mirror of
https://github.com/yairm210/Unciv.git
synced 2024-12-22 23:44:24 +07:00
Allow filter uniques to have conditionals and work with modifiers (#12404)
* Allow filter uniques to have conditionals * Move cache to inside the multifilter * Move Civ caching to nation * Might as well remove a use of let * Forgot to remove check * Might as well edit these for consistency * Avoid checking unit filters twice where necessary * Probably the wrong way to fix this, Idk where I should put the fix instead * Remove extra parenthesis * Don't multiFilter if we know it won't do anything * Apparently, issues for initialized tiles can exist for cities too * Lower cpu cost for changes - use consistent StateForConditionals, set once when loading map and again every time there is an ownership change * Resolves #12483 - add gameInfo to tile state so game-wide conditionals will work --------- Co-authored-by: yairm210 <yairm210@hotmail.com>
This commit is contained in:
parent
06f6449c6c
commit
405655d51d
@ -39,10 +39,11 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
|
||||
private val constructionsToAvoid = personality.getMatchingUniques(UniqueType.WillNotBuild, StateForConditionals(city))
|
||||
.map{ it.params[0] }
|
||||
private fun shouldAvoidConstruction (construction: IConstruction): Boolean {
|
||||
val stateForConditionals = StateForConditionals(city)
|
||||
for (toAvoid in constructionsToAvoid) {
|
||||
if (construction is Building && construction.matchesFilter(toAvoid))
|
||||
if (construction is Building && construction.matchesFilter(toAvoid, stateForConditionals))
|
||||
return true
|
||||
if (construction is BaseUnit && construction.matchesFilter(toAvoid))
|
||||
if (construction is BaseUnit && construction.matchesFilter(toAvoid, stateForConditionals))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -539,7 +539,7 @@ object NextTurnAutomation {
|
||||
val settlerUnits = civInfo.gameInfo.ruleset.units.values
|
||||
.filter { it.isCityFounder() && it.isBuildable(civInfo) &&
|
||||
personality.getMatchingUniques(UniqueType.WillNotBuild, StateForConditionals(civInfo))
|
||||
.none { unique -> it.matchesFilter(unique.params[0]) } }
|
||||
.none { unique -> it.matchesFilter(unique.params[0], StateForConditionals(civInfo)) } }
|
||||
if (settlerUnits.isEmpty()) return
|
||||
|
||||
if (civInfo.units.getCivUnits().count { it.isMilitary() } < civInfo.cities.size) return // We need someone to defend them first
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.tile.Terrain
|
||||
import com.unciv.models.ruleset.tile.TileImprovement
|
||||
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.Stats
|
||||
@ -337,7 +338,7 @@ class WorkerAutomation(
|
||||
if (tile.improvementInProgress != null) return ruleSet.tileImprovements[tile.improvementInProgress!!]
|
||||
|
||||
val potentialTileImprovements = ruleSet.tileImprovements.filter {
|
||||
(it.value.uniqueTo == null || unit.civ.matchesFilter(it.value.uniqueTo!!))
|
||||
(it.value.uniqueTo == null || unit.civ.matchesFilter(it.value.uniqueTo!!, StateForConditionals(unit = unit, tile = tile)))
|
||||
&& unit.canBuildImprovement(it.value, tile)
|
||||
&& tile.improvementFunctions.canBuildImprovement(it.value, civInfo)
|
||||
}
|
||||
|
@ -533,7 +533,7 @@ object Battle {
|
||||
for (unit in greatGeneralUnits) {
|
||||
val greatGeneralPointsBonus = thisCombatant
|
||||
.getMatchingUniques(UniqueType.GreatPersonEarnedFaster, stateForConditionals, true)
|
||||
.filter { unit.matchesFilter(it.params[0]) }
|
||||
.filter { unit.matchesFilter(it.params[0], stateForConditionals) }
|
||||
.sumOf { it.params[1].toDouble() }
|
||||
val greatGeneralPointsModifier = 1.0 + greatGeneralPointsBonus / 100
|
||||
|
||||
|
@ -25,7 +25,9 @@ class CityCombatant(val city: City) : ICombatant {
|
||||
override fun isDefeated(): Boolean = city.health == 1
|
||||
override fun isInvisible(to: Civilization): Boolean = false
|
||||
override fun canAttack(): Boolean = city.canBombard()
|
||||
override fun matchesFilter(filter: String) = MultiFilter.multiFilter(filter, {it == "City" || it in Constants.all || city.matchesFilter(it)})
|
||||
override fun matchesFilter(filter: String, multiFilter: Boolean) =
|
||||
if (multiFilter) MultiFilter.multiFilter(filter, { it == "City" || it in Constants.all || city.matchesFilter(it, multiFilter = false) })
|
||||
else filter == "City" || filter in Constants.all || city.matchesFilter(filter, multiFilter = false)
|
||||
override fun getAttackSound() = UncivSound.Bombard
|
||||
|
||||
override fun takeDamage(damage: Int) {
|
||||
|
@ -19,7 +19,7 @@ interface ICombatant {
|
||||
fun isInvisible(to: Civilization): Boolean
|
||||
fun canAttack(): Boolean
|
||||
/** Implements [UniqueParameterType.CombatantFilter][com.unciv.models.ruleset.unique.UniqueParameterType.CombatantFilter] */
|
||||
fun matchesFilter(filter: String): Boolean
|
||||
fun matchesFilter(filter: String, multiFilter: Boolean = true): Boolean
|
||||
fun getAttackSound(): UncivSound
|
||||
|
||||
fun isMelee(): Boolean = !isRanged()
|
||||
|
@ -18,7 +18,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) = unit.matchesFilter(filter)
|
||||
override fun matchesFilter(filter: String, multiFilter: Boolean) = unit.matchesFilter(filter, multiFilter)
|
||||
override fun getAttackSound() = unit.baseUnit.attackSound.let {
|
||||
if (it == null) UncivSound.Click else UncivSound(it)
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
|
||||
fun canBombard() = !attackedThisTurn && !isInResistance()
|
||||
fun getCenterTile(): Tile = centerTile
|
||||
fun getCenterTileOrNull(): Tile? = if (::centerTile.isInitialized) centerTile else null
|
||||
fun getTiles(): Sequence<Tile> = tiles.asSequence().map { tileMap[it] }
|
||||
fun getWorkableTiles() = tilesInRange.asSequence().filter { it.getOwner() == civ }
|
||||
fun isWorked(tile: Tile) = workedTiles.contains(tile.position)
|
||||
@ -164,7 +165,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
val indicatorBuildings = getRuleset().buildings.values.asSequence()
|
||||
.filter { it.hasUnique(UniqueType.IndicatesCapital) }
|
||||
|
||||
val civSpecificBuilding = indicatorBuildings.firstOrNull { it.uniqueTo != null && civ.matchesFilter(it.uniqueTo!!) }
|
||||
val civSpecificBuilding = indicatorBuildings.firstOrNull { it.uniqueTo != null && civ.matchesFilter(it.uniqueTo!!, StateForConditionals(this)) }
|
||||
return civSpecificBuilding ?: indicatorBuildings.firstOrNull()
|
||||
}
|
||||
|
||||
@ -425,8 +426,10 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
}
|
||||
|
||||
/** Implements [UniqueParameterType.CityFilter][com.unciv.models.ruleset.unique.UniqueParameterType.CityFilter] */
|
||||
fun matchesFilter(filter: String, viewingCiv: Civilization? = civ): Boolean {
|
||||
return MultiFilter.multiFilter(filter, { matchesSingleFilter(it, viewingCiv) })
|
||||
fun matchesFilter(filter: String, viewingCiv: Civilization? = civ, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter)
|
||||
MultiFilter.multiFilter(filter, { matchesSingleFilter(it, viewingCiv) })
|
||||
else matchesSingleFilter(filter, viewingCiv)
|
||||
}
|
||||
|
||||
private fun matchesSingleFilter(filter: String, viewingCiv: Civilization? = civ): Boolean {
|
||||
@ -464,7 +467,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
// this will always be true when checked.
|
||||
"in cities following this religion" -> true
|
||||
"in cities following our religion" -> viewingCiv?.religionManager?.religion == religion.getMajorityReligion()
|
||||
else -> civ.matchesFilter(filter)
|
||||
else -> civ.matchesFilter(filter, StateForConditionals(this), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,7 +241,7 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
fun getBuiltBuildings(): Sequence<Building> = builtBuildingObjects.asSequence()
|
||||
|
||||
fun containsBuildingOrEquivalent(buildingNameOrUnique: String): Boolean =
|
||||
isBuilt(buildingNameOrUnique) || getBuiltBuildings().any { it.replaces == buildingNameOrUnique || it.hasTagUnique(buildingNameOrUnique) }
|
||||
isBuilt(buildingNameOrUnique) || getBuiltBuildings().any { it.replaces == buildingNameOrUnique || it.hasUnique(buildingNameOrUnique, StateForConditionals(city)) }
|
||||
|
||||
fun getWorkDone(constructionName: String): Int {
|
||||
return if (inProgressConstructions.containsKey(constructionName)) inProgressConstructions[constructionName]!!
|
||||
@ -565,11 +565,11 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText)
|
||||
|
||||
for (unique in city.civ.getTriggeredUniques(UniqueType.TriggerUponConstructingBuilding, stateForConditionals)
|
||||
{ building.matchesFilter(it.params[0]) })
|
||||
{ building.matchesFilter(it.params[0], stateForConditionals) })
|
||||
UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText)
|
||||
|
||||
for (unique in city.civ.getTriggeredUniques(UniqueType.TriggerUponConstructingBuildingCityFilter, stateForConditionals)
|
||||
{ building.matchesFilter(it.params[0]) && city.matchesFilter(it.params[1]) })
|
||||
{ building.matchesFilter(it.params[0], stateForConditionals) && city.matchesFilter(it.params[1]) })
|
||||
UniqueTriggerActivation.triggerUnique(unique, city, triggerNotificationText = triggerNotificationText)
|
||||
}
|
||||
|
||||
@ -674,15 +674,15 @@ class CityConstructions : IsPartOfGameInfoSerialization {
|
||||
?: return false // We should never end up here anyway, so things have already gone _way_ wrong
|
||||
city.addStat(stat, -1 * constructionCost)
|
||||
|
||||
val conditionalState = StateForConditionals(civInfo = city.civ, city = city)
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
if ((
|
||||
city.civ.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState) +
|
||||
city.civ.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost, conditionalState)
|
||||
).any {
|
||||
(
|
||||
construction is BaseUnit && construction.matchesFilter(it.params[0]) ||
|
||||
construction is Building && construction.matchesFilter(it.params[0])
|
||||
construction is BaseUnit && construction.matchesFilter(it.params[0], conditionalState) ||
|
||||
construction is Building && construction.matchesFilter(it.params[0], conditionalState)
|
||||
)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
&& it.params[2] == stat.name
|
||||
|
@ -304,8 +304,9 @@ class CityStats(val city: City) {
|
||||
}
|
||||
|
||||
private fun constructionMatchesFilter(construction: IConstruction, filter: String): Boolean {
|
||||
if (construction is Building) return construction.matchesFilter(filter)
|
||||
if (construction is BaseUnit) return construction.matchesFilter(filter)
|
||||
val state = StateForConditionals(city)
|
||||
if (construction is Building) return construction.matchesFilter(filter, state)
|
||||
if (construction is BaseUnit) return construction.matchesFilter(filter, state)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -551,19 +551,16 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
yieldAll(gameInfo.ruleset.globalUniques.uniqueMap.getTriggeredUniques(trigger, stateForConditionals, triggerFilter))
|
||||
}.toList() // Triggers can e.g. add buildings which contain triggers, causing concurrent modification errors
|
||||
|
||||
|
||||
@Transient
|
||||
private val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
/** Implements [UniqueParameterType.CivFilter][com.unciv.models.ruleset.unique.UniqueParameterType.CivFilter] */
|
||||
fun matchesFilter(filter: String): Boolean =
|
||||
cachedMatchesFilterResult.getOrPut(filter) { MultiFilter.multiFilter(filter, ::matchesSingleFilter ) }
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = StateForConditionals(this), multiFilter: Boolean = true): Boolean =
|
||||
if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleFilter(it, state) })
|
||||
else matchesSingleFilter(filter, state)
|
||||
|
||||
fun matchesSingleFilter(filter: String): Boolean {
|
||||
fun matchesSingleFilter(filter: String, state: StateForConditionals? = StateForConditionals(this)): Boolean {
|
||||
return when (filter) {
|
||||
"Human player" -> isHuman()
|
||||
"AI player" -> isAI()
|
||||
else -> nation.matchesFilter(filter)
|
||||
else -> nation.matchesFilter(filter, state, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ class TechManager : IsPartOfGameInfoSerialization {
|
||||
if (!unique.hasTriggerConditional() && unique.conditionalsApply(StateForConditionals(civInfo)))
|
||||
UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText)
|
||||
|
||||
for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponResearch) { newTech.matchesFilter(it.params[0]) })
|
||||
for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponResearch) { newTech.matchesFilter(it.params[0], StateForConditionals(civInfo)) })
|
||||
UniqueTriggerActivation.triggerUnique(unique, civInfo, triggerNotificationText = triggerNotificationText)
|
||||
|
||||
|
||||
|
@ -128,6 +128,10 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
@Transient
|
||||
private var tempUniquesMap = UniqueMap()
|
||||
|
||||
@Transient
|
||||
/** Temp map excluding unit uniques*/
|
||||
private var nonUnitUniquesMap = UniqueMap()
|
||||
|
||||
@Transient
|
||||
val movement = UnitMovement(this)
|
||||
|
||||
@ -563,8 +567,8 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
/** Implements [UniqueParameterType.MapUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.MapUnitFilter] */
|
||||
fun matchesFilter(filter: String): Boolean {
|
||||
return MultiFilter.multiFilter(filter, ::matchesSingleFilter)
|
||||
fun matchesFilter(filter: String, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, ::matchesSingleFilter) else matchesSingleFilter(filter)
|
||||
}
|
||||
|
||||
private fun matchesSingleFilter(filter: String): Boolean {
|
||||
@ -575,9 +579,10 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
Constants.embarked -> isEmbarked()
|
||||
"Non-City" -> true
|
||||
else -> {
|
||||
if (baseUnit.matchesFilter(filter)) return true
|
||||
if (civ.matchesFilter(filter)) return true
|
||||
if (tempUniquesMap.hasTagUnique(filter)) return true
|
||||
val state = StateForConditionals(this)
|
||||
if (baseUnit.matchesFilter(filter, state, false)) return true
|
||||
if (civ.matchesFilter(filter, state, false)) return true
|
||||
if (nonUnitUniquesMap.hasUnique(filter, state))
|
||||
if (promotions.promotions.contains(filter)) return true
|
||||
return false
|
||||
}
|
||||
@ -597,7 +602,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
return buildImprovementUniques.any()
|
||||
}
|
||||
return buildImprovementUniques
|
||||
.any { improvement.matchesFilter(it.params[0]) || tile.matchesTerrainFilter(it.params[0]) }
|
||||
.any { improvement.matchesFilter(it.params[0], StateForConditionals(this)) || tile.matchesTerrainFilter(it.params[0]) }
|
||||
}
|
||||
|
||||
fun getReligionDisplayName(): String? {
|
||||
@ -654,13 +659,15 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
fun updateUniques() {
|
||||
val uniqueSources =
|
||||
baseUnit.uniqueObjects.asSequence() +
|
||||
type.uniqueObjects +
|
||||
promotions.getPromotions().flatMap { it.uniqueObjects } +
|
||||
statuses.flatMap { it.uniques }
|
||||
val unitUniqueSources =
|
||||
baseUnit.uniqueObjects.asSequence() +
|
||||
type.uniqueObjects
|
||||
val otherUniqueSources = promotions.getPromotions().flatMap { it.uniqueObjects } +
|
||||
statuses.flatMap { it.uniques }
|
||||
val uniqueSources = unitUniqueSources + otherUniqueSources
|
||||
|
||||
tempUniquesMap = UniqueMap(uniqueSources)
|
||||
nonUnitUniquesMap = UniqueMap(otherUniqueSources)
|
||||
cache.updateUniques()
|
||||
}
|
||||
|
||||
|
@ -366,8 +366,8 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
|
||||
fun isRoughTerrain() = allTerrains.any { it.isRough() }
|
||||
|
||||
@delegate:Transient
|
||||
private val stateThisTile: StateForConditionals by lazy { StateForConditionals(tile = this) }
|
||||
@Transient
|
||||
private var stateThisTile: StateForConditionals = StateForConditionals.EmptyState
|
||||
/** Checks whether any of the TERRAINS of this tile has a certain unique */
|
||||
fun terrainHasUnique(uniqueType: UniqueType, state: StateForConditionals = stateThisTile) =
|
||||
terrainUniqueMap.getMatchingUniques(uniqueType, state).any()
|
||||
@ -476,13 +476,14 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
if ((improvement == null || improvementIsPillaged) && filter == "unimproved") return true
|
||||
if (improvement != null && !improvementIsPillaged && filter == "improved") return true
|
||||
if (ignoreImprovement) return false
|
||||
if (getUnpillagedTileImprovement()?.matchesFilter(filter) == true) return true
|
||||
return getUnpillagedRoadImprovement()?.matchesFilter(filter) == true
|
||||
if (getUnpillagedTileImprovement()?.matchesFilter(filter, stateThisTile, false) == true) return true
|
||||
return getUnpillagedRoadImprovement()?.matchesFilter(filter, stateThisTile, false) == true
|
||||
}
|
||||
|
||||
/** Implements [UniqueParameterType.TerrainFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TerrainFilter] */
|
||||
fun matchesTerrainFilter(filter: String, observingCiv: Civilization? = null): Boolean {
|
||||
return MultiFilter.multiFilter(filter, { matchesSingleTerrainFilter(it, observingCiv) })
|
||||
fun matchesTerrainFilter(filter: String, observingCiv: Civilization? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, { matchesSingleTerrainFilter(it, observingCiv) })
|
||||
else matchesSingleTerrainFilter(filter, observingCiv)
|
||||
}
|
||||
|
||||
private fun matchesSingleTerrainFilter(filter: String, observingCiv: Civilization? = null): Boolean {
|
||||
@ -508,8 +509,9 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
Constants.freshWaterFilter -> isAdjacentTo(Constants.freshWater, observingCiv)
|
||||
|
||||
else -> {
|
||||
if (allTerrains.any { it.matchesFilter(filter) }) return true
|
||||
if (getOwner()?.matchesFilter(filter) == true) return true
|
||||
val owner = getOwner()
|
||||
if (allTerrains.any { it.matchesFilter(filter, stateThisTile, false) }) return true
|
||||
if (owner != null && owner.matchesFilter(filter, stateThisTile, false)) return true
|
||||
|
||||
// Resource type check is last - cannot succeed if no resource here
|
||||
if (resource == null) return false
|
||||
@ -520,7 +522,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
val resourceObject = tileResource
|
||||
val hasResourceWithFilter =
|
||||
tileResource.name == filter
|
||||
|| tileResource.hasTagUnique(filter)
|
||||
|| tileResource.hasUnique(filter, stateThisTile)
|
||||
|| filter.removeSuffix(" resource") == tileResource.resourceType.name
|
||||
if (!hasResourceWithFilter) return false
|
||||
|
||||
@ -733,6 +735,10 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
}
|
||||
|
||||
fun setOwnerTransients() {
|
||||
// If it has an owning city, the state was already set in setOwningCity
|
||||
if (owningCity == null) stateThisTile = StateForConditionals(tile = this,
|
||||
// When generating maps we call this function but there's no gameinfo
|
||||
gameInfo = if (tileMap.hasGameInfo()) tileMap.gameInfo else null)
|
||||
if (owningCity == null && roadOwner != "")
|
||||
getRoadOwner()!!.neutralRoads.add(this.position)
|
||||
}
|
||||
@ -749,6 +755,7 @@ class Tile : IsPartOfGameInfoSerialization, Json.Serializable {
|
||||
roadOwner = ""
|
||||
}
|
||||
owningCity = city
|
||||
stateThisTile = StateForConditionals(tile = this, city = city, gameInfo = tileMap.gameInfo)
|
||||
isCityCenterInternal = getCity()?.location == position
|
||||
}
|
||||
|
||||
|
@ -150,7 +150,7 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
|
||||
// Can't build if any terrain specifically prevents building this improvement
|
||||
tile.getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any {
|
||||
unique -> !improvement.matchesFilter(unique.params[0])
|
||||
unique -> !improvement.matchesFilter(unique.params[0], StateForConditionals(tile = tile))
|
||||
} -> false
|
||||
|
||||
// Can't build if the improvement specifically prevents building on some present feature
|
||||
@ -260,12 +260,12 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile)
|
||||
|
||||
for (unique in civ.getTriggeredUniques(UniqueType.TriggerUponBuildingImprovement, stateForConditionals)
|
||||
{ improvement.matchesFilter(it.params[0]) })
|
||||
{ improvement.matchesFilter(it.params[0], StateForConditionals(civ, unit = unit, tile = tile)) })
|
||||
UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile)
|
||||
|
||||
if (unit == null) return
|
||||
for (unique in unit.getTriggeredUniques(UniqueType.TriggerUponBuildingImprovement, stateForConditionals)
|
||||
{ improvement.matchesFilter(it.params[0]) })
|
||||
{ improvement.matchesFilter(it.params[0], StateForConditionals(civ, unit = unit, tile = tile)) })
|
||||
UniqueTriggerActivation.triggerUnique(unique, civ, unit = unit, tile = tile)
|
||||
}
|
||||
|
||||
|
@ -78,9 +78,9 @@ class TileStatFunctions(val tile: Tile) {
|
||||
val tileType = unique.params[1]
|
||||
if (tile.matchesFilter(tileType, observingCiv, true))
|
||||
listOfStats.add("{${unique.sourceObjectName}} ({${unique.getDisplayText()}})" to unique.stats)
|
||||
else if (improvement != null && improvement.matchesFilter(tileType))
|
||||
else if (improvement != null && improvement.matchesFilter(tileType, StateForConditionals(city = city, tile = tile)))
|
||||
improvementStats.add(unique.stats)
|
||||
else if (road != null && road.matchesFilter(tileType))
|
||||
else if (road != null && road.matchesFilter(tileType, StateForConditionals(city = city, tile = tile)))
|
||||
roadStats.add(unique.stats)
|
||||
}
|
||||
}
|
||||
@ -186,9 +186,9 @@ class TileStatFunctions(val tile: Tile) {
|
||||
fun addStats(filter: String, stat: Stat, amount: Float) {
|
||||
if (tile.matchesFilter(filter, observingCiv, true))
|
||||
terrainStats.add(stat, amount)
|
||||
else if (improvement != null && improvement.matchesFilter(filter))
|
||||
else if (improvement != null && improvement.matchesFilter(filter, StateForConditionals(city = city, tile = tile)))
|
||||
improvementStats.add(stat, amount)
|
||||
else if (road != null && road.matchesFilter(filter))
|
||||
else if (road != null && road.matchesFilter(filter, StateForConditionals(city = city, tile = tile)))
|
||||
roadStats.add(stat, amount)
|
||||
}
|
||||
|
||||
|
@ -82,18 +82,20 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats {
|
||||
// Calls the clone function of the NamedStats this class is derived from, not a clone function of this class
|
||||
val stats = cloneStats()
|
||||
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
for (unique in localUniqueCache.forCityGetMatchingUniques(city, UniqueType.StatsFromObject)) {
|
||||
if (!matchesFilter(unique.params[1])) continue
|
||||
if (!matchesFilter(unique.params[1], conditionalState)) continue
|
||||
stats.add(unique.stats)
|
||||
}
|
||||
|
||||
for (unique in getMatchingUniques(UniqueType.Stats, StateForConditionals(city.civ, city)))
|
||||
for (unique in getMatchingUniques(UniqueType.Stats, conditionalState))
|
||||
stats.add(unique.stats)
|
||||
|
||||
if (!isWonder)
|
||||
for (unique in localUniqueCache.forCityGetMatchingUniques(city, UniqueType.StatsFromBuildings)) {
|
||||
if (matchesFilter(unique.params[1]))
|
||||
if (matchesFilter(unique.params[1], conditionalState))
|
||||
stats.add(unique.stats)
|
||||
}
|
||||
return stats
|
||||
@ -102,14 +104,16 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
fun getStatPercentageBonuses(city: City?, localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)): Stats {
|
||||
val stats = percentStatBonus?.clone() ?: Stats()
|
||||
val civInfo = city?.civ ?: return stats // initial stats
|
||||
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
for (unique in localUniqueCache.forCivGetMatchingUniques(civInfo, UniqueType.StatPercentFromObject)) {
|
||||
if (matchesFilter(unique.params[2]))
|
||||
if (matchesFilter(unique.params[2], conditionalState))
|
||||
stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
|
||||
}
|
||||
|
||||
for (unique in localUniqueCache.forCivGetMatchingUniques(civInfo, UniqueType.AllStatsPercentFromObject)) {
|
||||
if (!matchesFilter(unique.params[1])) continue
|
||||
if (!matchesFilter(unique.params[1], conditionalState)) continue
|
||||
for (stat in Stat.entries) {
|
||||
stats.add(stat, unique.params[0].toFloat())
|
||||
}
|
||||
@ -159,21 +163,21 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
city.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost, conditionalState)
|
||||
.any {
|
||||
it.params[2] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}
|
||||
|| city.getMatchingUniques(UniqueType.BuyBuildingsByProductionCost, conditionalState)
|
||||
.any { it.params[1] == stat.name && matchesFilter(it.params[0]) }
|
||||
.any { it.params[1] == stat.name && matchesFilter(it.params[0], conditionalState) }
|
||||
|| city.getMatchingUniques(UniqueType.BuyBuildingsWithStat, conditionalState)
|
||||
.any {
|
||||
it.params[1] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[2])
|
||||
}
|
||||
|| city.getMatchingUniques(UniqueType.BuyBuildingsForAmountStat, conditionalState)
|
||||
.any {
|
||||
it.params[2] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}
|
||||
|| super.canBePurchasedWithStat(city, stat)
|
||||
@ -190,7 +194,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyBuildingsIncreasingCost, conditionalState)
|
||||
.filter {
|
||||
it.params[2] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}.map {
|
||||
getCostForConstructionsIncreasingInPrice(
|
||||
@ -201,13 +205,13 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
}
|
||||
)
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyBuildingsByProductionCost, conditionalState)
|
||||
.filter { it.params[1] == stat.name && matchesFilter(it.params[0]) }
|
||||
.filter { it.params[1] == stat.name && matchesFilter(it.params[0], conditionalState) }
|
||||
.map { (getProductionCost(city.civ, city) * it.params[2].toInt()).toFloat() }
|
||||
)
|
||||
if (city.getMatchingUniques(UniqueType.BuyBuildingsWithStat, conditionalState)
|
||||
.any {
|
||||
it.params[1] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[2])
|
||||
}
|
||||
) {
|
||||
@ -216,7 +220,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyBuildingsForAmountStat, conditionalState)
|
||||
.filter {
|
||||
it.params[2] == stat.name
|
||||
&& matchesFilter(it.params[0])
|
||||
&& matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}.map { it.params[1].toInt() * city.civ.gameInfo.speed.statCostModifiers[stat]!! }
|
||||
)
|
||||
@ -225,13 +229,14 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
|
||||
override fun getStatBuyCost(city: City, stat: Stat): Int? {
|
||||
var cost = getBaseBuyCost(city, stat)?.toDouble() ?: return null
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
for (unique in city.getMatchingUniques(UniqueType.BuyItemsDiscount))
|
||||
if (stat.name == unique.params[0])
|
||||
cost *= unique.params[1].toPercent()
|
||||
|
||||
for (unique in city.getMatchingUniques(UniqueType.BuyBuildingsDiscount)) {
|
||||
if (stat.name == unique.params[0] && matchesFilter(unique.params[1]))
|
||||
if (stat.name == unique.params[0] && matchesFilter(unique.params[1], conditionalState))
|
||||
cost *= unique.params[2].toPercent()
|
||||
}
|
||||
|
||||
@ -348,7 +353,7 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueTo != null && !civ.matchesFilter(uniqueTo!!))
|
||||
if (uniqueTo != null && !civ.matchesFilter(uniqueTo!!, stateForConditionals))
|
||||
yield(RejectionReasonType.UniqueToOtherNation.toInstance("Unique to $uniqueTo"))
|
||||
|
||||
if (civ.cache.uniqueBuildings.any { it.replaces == name })
|
||||
@ -480,8 +485,12 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
private val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
/** Implements [UniqueParameterType.BuildingFilter] */
|
||||
fun matchesFilter(filter: String): Boolean =
|
||||
cachedMatchesFilterResult.getOrPut(filter) { MultiFilter.multiFilter(filter, ::matchesSingleFilter ) }
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null): Boolean =
|
||||
MultiFilter.multiFilter(filter, {
|
||||
cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } ||
|
||||
state != null && hasUnique(it, state) ||
|
||||
state == null && hasTagUnique(it)
|
||||
})
|
||||
|
||||
private fun matchesSingleFilter(filter: String): Boolean {
|
||||
return when (filter) {
|
||||
@ -493,10 +502,9 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
"World Wonder", "World" -> isWonder
|
||||
replaces -> true
|
||||
else -> {
|
||||
if (uniques.contains(filter)) return true
|
||||
if (::ruleset.isInitialized) // False when loading ruleset and checking buildingsToRemove
|
||||
for (requiredTech: String in requiredTechs())
|
||||
if (ruleset.technologies[requiredTech]?.matchesFilter(filter) == true) return true
|
||||
if (ruleset.technologies[requiredTech]?.matchesFilter(filter, multiFilter = false) == true) return true
|
||||
val stat = Stat.safeValueOf(filter)
|
||||
return (stat != null && isStatRelated(stat))
|
||||
}
|
||||
|
@ -35,15 +35,18 @@ open class Policy : RulesetObject() {
|
||||
fun isBranchCompleteByName(name: String) = name.endsWith(branchCompleteSuffix)
|
||||
}
|
||||
|
||||
fun matchesFilter(filter: String): Boolean {
|
||||
return MultiFilter.multiFilter(filter, ::matchesSingleFilter)
|
||||
}
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null): Boolean =
|
||||
MultiFilter.multiFilter(filter, {
|
||||
matchesSingleFilter(filter) ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
})
|
||||
|
||||
fun matchesSingleFilter(filter: String): Boolean {
|
||||
return when(filter) {
|
||||
in Constants.all -> true
|
||||
name -> true
|
||||
"[${branch.name}] branch" -> true
|
||||
in uniques -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import com.unciv.Constants
|
||||
import com.unciv.logic.MultiFilter
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetObject
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.UniqueMap
|
||||
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
@ -257,9 +258,19 @@ class Nation : RulesetObject() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun matchesFilter(filter: String): Boolean {
|
||||
return MultiFilter.multiFilter(filter, ::matchesSingleFilter)
|
||||
|
||||
@Transient
|
||||
private val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, {
|
||||
cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } ||
|
||||
state != null && hasUnique(it, state) ||
|
||||
state == null && hasTagUnique(it)
|
||||
})
|
||||
else cachedMatchesFilterResult.getOrPut(filter) { matchesSingleFilter(filter) } ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
}
|
||||
|
||||
private fun matchesSingleFilter(filter: String): Boolean {
|
||||
@ -268,7 +279,7 @@ class Nation : RulesetObject() {
|
||||
name -> true
|
||||
"Major" -> isMajorCiv
|
||||
Constants.cityStates, "City-State" -> isCityState
|
||||
else -> uniques.contains(filter)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.unciv.models.ruleset.tech
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.MultiFilter
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetObject
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
@ -35,12 +37,23 @@ class Technology: RulesetObject() {
|
||||
|
||||
override fun era(ruleset: Ruleset) = ruleset.eras[era()]
|
||||
|
||||
fun matchesFilter(filter: String): Boolean {
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, {
|
||||
matchesSingleFilter(filter) ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
})
|
||||
else matchesSingleFilter(filter) ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
}
|
||||
|
||||
fun matchesSingleFilter(filter: String): Boolean {
|
||||
return when (filter) {
|
||||
in Constants.all -> true
|
||||
name -> true
|
||||
era() -> true
|
||||
else -> uniques.contains(filter)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import com.unciv.logic.MultiFilter
|
||||
import com.unciv.models.ruleset.Belief
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetStatsObject
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.colorFromRGB
|
||||
@ -159,8 +160,16 @@ class Terrain : RulesetStatsObject() {
|
||||
/** Terrain filter matching is "pure" - input always returns same output, and it's called a bajillion times */
|
||||
val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
fun matchesFilter(filter: String): Boolean =
|
||||
cachedMatchesFilterResult.getOrPut(filter) { MultiFilter.multiFilter(filter, ::matchesSingleFilter ) }
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, {
|
||||
cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } ||
|
||||
state != null && hasUnique(it, state) ||
|
||||
state == null && hasTagUnique(it)
|
||||
})
|
||||
else cachedMatchesFilterResult.getOrPut(filter) { matchesSingleFilter(filter) } ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
}
|
||||
|
||||
/** Implements [UniqueParameterType.TerrainFilter][com.unciv.models.ruleset.unique.UniqueParameterType.TerrainFilter] */
|
||||
fun matchesSingleFilter(filter: String): Boolean {
|
||||
@ -168,14 +177,13 @@ class Terrain : RulesetStatsObject() {
|
||||
in Constants.all -> true
|
||||
name -> true
|
||||
"Terrain" -> true
|
||||
in Constants.all -> true
|
||||
"Open terrain" -> !isRough()
|
||||
"Rough terrain" -> isRough()
|
||||
type.name -> true
|
||||
"Natural Wonder" -> type == TerrainType.NaturalWonder
|
||||
"Terrain Feature" -> type == TerrainType.TerrainFeature
|
||||
|
||||
else -> uniques.contains(filter)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ class TileImprovement : RulesetStatsObject() {
|
||||
fun getTurnsToBuild(civInfo: Civilization, unit: MapUnit): Int {
|
||||
val state = StateForConditionals(civInfo, unit = unit)
|
||||
val buildSpeedUniques = unit.getMatchingUniques(UniqueType.SpecificImprovementTime, state, checkCivInfoUniques = true)
|
||||
.filter { matchesFilter(it.params[1]) }
|
||||
.filter { matchesFilter(it.params[1], state) }
|
||||
return buildSpeedUniques
|
||||
.fold(turnsToBuild.toFloat() * civInfo.gameInfo.speed.improvementBuildLengthModifier) { calculatedTurnsToBuild, unique ->
|
||||
calculatedTurnsToBuild * unique.params[0].toPercent()
|
||||
@ -72,8 +72,16 @@ class TileImprovement : RulesetStatsObject() {
|
||||
private val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
/** Implements [UniqueParameterType.ImprovementFilter][com.unciv.models.ruleset.unique.UniqueParameterType.ImprovementFilter] */
|
||||
fun matchesFilter(filter: String): Boolean =
|
||||
cachedMatchesFilterResult.getOrPut(filter) { MultiFilter.multiFilter(filter, ::matchesSingleFilter ) }
|
||||
fun matchesFilter(filter: String, tileState: StateForConditionals? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, {
|
||||
cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } ||
|
||||
tileState != null && hasUnique(it, tileState) ||
|
||||
tileState == null && hasTagUnique(it)
|
||||
})
|
||||
else cachedMatchesFilterResult.getOrPut(filter) { matchesSingleFilter(filter) } ||
|
||||
tileState != null && hasUnique(filter, tileState) ||
|
||||
tileState == null && hasTagUnique(filter)
|
||||
}
|
||||
|
||||
private fun matchesSingleFilter(filter: String): Boolean {
|
||||
return when (filter) {
|
||||
@ -83,7 +91,7 @@ class TileImprovement : RulesetStatsObject() {
|
||||
"Improvement" -> true // For situations involving tileFilter
|
||||
"All Road" -> isRoad()
|
||||
"Great Improvement", "Great" -> isGreatImprovement()
|
||||
else -> uniqueMap.hasTagUnique(filter)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.unciv.models.ruleset.tile
|
||||
|
||||
import com.unciv.logic.MultiFilter
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.map.tile.Tile
|
||||
import com.unciv.models.ruleset.Belief
|
||||
@ -179,11 +180,17 @@ class TileResource : RulesetStatsObject() {
|
||||
}
|
||||
}
|
||||
|
||||
fun matchesFilter(filter: String) = when (filter) {
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null): Boolean =
|
||||
MultiFilter.multiFilter(filter, {
|
||||
matchesSingleFilter(filter) ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
})
|
||||
|
||||
fun matchesSingleFilter(filter: String) = when (filter) {
|
||||
name -> true
|
||||
"any" -> true
|
||||
resourceType.name -> true
|
||||
in uniques -> true
|
||||
else -> improvementStats?.any { filter == it.key.name } == true
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ object Conditionals {
|
||||
UniqueType.ConditionalTutorialsEnabled -> UncivGame.Current.settings.showTutorials
|
||||
UniqueType.ConditionalTutorialCompleted -> conditional.params[0] in UncivGame.Current.settings.tutorialTasksCompleted
|
||||
|
||||
UniqueType.ConditionalCivFilter, UniqueType.ConditionalCivFilterOld -> checkOnCiv { matchesFilter(conditional.params[0]) }
|
||||
UniqueType.ConditionalCivFilter, UniqueType.ConditionalCivFilterOld -> checkOnCiv { matchesFilter(conditional.params[0], state) }
|
||||
UniqueType.ConditionalWar -> checkOnCiv { isAtWar() }
|
||||
UniqueType.ConditionalNotWar -> checkOnCiv { !isAtWar() }
|
||||
UniqueType.ConditionalWithResource -> state.getResourceAmount(conditional.params[0]) > 0
|
||||
@ -207,7 +207,7 @@ object Conditionals {
|
||||
UniqueType.ConditionalWhenGarrisoned ->
|
||||
checkOnCity { getCenterTile().militaryUnit?.canGarrison() == true }
|
||||
|
||||
UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesFilter("City") == true
|
||||
UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesFilter("City", false) == true
|
||||
UniqueType.ConditionalVsUnits, UniqueType.ConditionalVsCombatant -> state.theirCombatant?.matchesFilter(conditional.params[0]) == true
|
||||
UniqueType.ConditionalOurUnit, UniqueType.ConditionalOurUnitOnUnit ->
|
||||
state.relevantUnit?.matchesFilter(conditional.params[0]) == true
|
||||
|
@ -38,10 +38,16 @@ interface IHasUniques : INamed {
|
||||
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
uniqueMap.getMatchingUniques(uniqueType, state)
|
||||
|
||||
fun getMatchingUniques(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
uniqueMap.getMatchingUniques(uniqueTag, state)
|
||||
|
||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals? = null) =
|
||||
uniqueMap.hasMatchingUnique(uniqueType, state ?: StateForConditionals.EmptyState)
|
||||
|
||||
fun hasUnique(uniqueTag: String, state: StateForConditionals? = null) =
|
||||
uniqueMap.hasMatchingUnique(uniqueTag, state ?: StateForConditionals.EmptyState)
|
||||
|
||||
fun hasTagUnique(tagUnique: String) =
|
||||
uniqueMap.hasTagUnique(tagUnique)
|
||||
|
||||
|
@ -28,8 +28,8 @@ data class StateForConditionals(
|
||||
|
||||
val ignoreConditionals: Boolean = false,
|
||||
) {
|
||||
constructor(city: City) : this(city.civ, city, tile = city.getCenterTile())
|
||||
constructor(unit: MapUnit) : this(unit.civ, unit = unit, tile = unit.currentTile)
|
||||
constructor(city: City) : this(city.civ, city, tile = city.getCenterTileOrNull())
|
||||
constructor(unit: MapUnit) : this(unit.civ, unit = unit, tile = if (unit.hasTile()) unit.getTile() else null)
|
||||
constructor(ourCombatant: ICombatant, theirCombatant: ICombatant? = null,
|
||||
attackedTile: Tile? = null, combatAction: CombatAction? = null) : this(
|
||||
ourCombatant.getCivInfo(),
|
||||
@ -52,7 +52,7 @@ data class StateForConditionals(
|
||||
?: tile
|
||||
// We need to protect against conditionals checking tiles for units pre-placement - see #10425, #10512
|
||||
?: relevantUnit?.run { if (hasTile()) getTile() else null }
|
||||
?: city?.getCenterTile()
|
||||
?: city?.getCenterTileOrNull()
|
||||
}
|
||||
|
||||
val relevantCity by lazy {
|
||||
|
@ -298,6 +298,9 @@ open class UniqueMap() {
|
||||
|
||||
fun hasUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
|
||||
fun hasUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag).any { it.conditionalsApply(state) && !it.isTimedTriggerable }
|
||||
|
||||
fun hasTagUnique(tagUnique: String) =
|
||||
innerUniqueMap.containsKey(tagUnique)
|
||||
@ -307,6 +310,10 @@ open class UniqueMap() {
|
||||
?.asSequence()
|
||||
?: emptySequence()
|
||||
|
||||
fun getUniques(uniqueTag: String) = innerUniqueMap[uniqueTag]
|
||||
?.asSequence()
|
||||
?: emptySequence()
|
||||
|
||||
fun getMatchingUniques(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType)
|
||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
||||
@ -317,10 +324,24 @@ open class UniqueMap() {
|
||||
else -> it.getMultiplied(state)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMatchingUniques(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag)
|
||||
// Same as .filter | .flatMap, but more cpu/mem performant (7.7 GB vs ?? for test)
|
||||
.flatMap {
|
||||
when {
|
||||
it.isTimedTriggerable -> emptySequence()
|
||||
!it.conditionalsApply(state) -> emptySequence()
|
||||
else -> it.getMultiplied(state)
|
||||
}
|
||||
}
|
||||
|
||||
fun hasMatchingUnique(uniqueType: UniqueType, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueType).any { it.conditionalsApply(state) }
|
||||
|
||||
fun hasMatchingUnique(uniqueTag: String, state: StateForConditionals = StateForConditionals.EmptyState) =
|
||||
getUniques(uniqueTag).any { it.conditionalsApply(state) }
|
||||
|
||||
fun getAllUniques() = innerUniqueMap.values.asSequence().flatten()
|
||||
|
||||
fun getTriggeredUniques(trigger: UniqueType, stateForConditionals: StateForConditionals,
|
||||
|
@ -327,7 +327,7 @@ object UniqueTriggerActivation {
|
||||
val policyFilter = unique.params[0]
|
||||
val policiesToRemove = civInfo.policies.adoptedPolicies
|
||||
.mapNotNull { civInfo.gameInfo.ruleset.policies[it] }
|
||||
.filter { it.matchesFilter(policyFilter) }
|
||||
.filter { it.matchesFilter(policyFilter, stateForConditionals) }
|
||||
if (policiesToRemove.isEmpty()) return null
|
||||
|
||||
return {
|
||||
@ -350,7 +350,7 @@ object UniqueTriggerActivation {
|
||||
val refundPercentage = unique.params[1].toInt()
|
||||
val policiesToRemove = civInfo.policies.adoptedPolicies
|
||||
.mapNotNull { civInfo.gameInfo.ruleset.policies[it] }
|
||||
.filter { it.matchesFilter(policyFilter) }
|
||||
.filter { it.matchesFilter(policyFilter, stateForConditionals) }
|
||||
if (policiesToRemove.isEmpty()) return null
|
||||
|
||||
val policiesToRemoveMap = civInfo.policies.getCultureRefundMap(policiesToRemove, refundPercentage)
|
||||
@ -907,7 +907,7 @@ object UniqueTriggerActivation {
|
||||
return {
|
||||
for (applicableCity in applicableCities) {
|
||||
val buildingsToRemove = applicableCity.cityConstructions.getBuiltBuildings().filter {
|
||||
it.matchesFilter(unique.params[0])
|
||||
it.matchesFilter(unique.params[0], StateForConditionals(applicableCity))
|
||||
}.toSet()
|
||||
applicableCity.cityConstructions.removeBuildings(buildingsToRemove)
|
||||
}
|
||||
@ -924,7 +924,7 @@ object UniqueTriggerActivation {
|
||||
return {
|
||||
for (applicableCity in applicableCities) {
|
||||
val buildingsToSell = applicableCity.cityConstructions.getBuiltBuildings().filter {
|
||||
it.matchesFilter(unique.params[0]) && it.isSellable()
|
||||
it.matchesFilter(unique.params[0], StateForConditionals(applicableCity)) && it.isSellable()
|
||||
}
|
||||
|
||||
for (building in buildingsToSell) applicableCity.sellBuilding(building)
|
||||
|
@ -120,6 +120,10 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
return super<RulesetObject>.hasUnique(uniqueType, state) || ::ruleset.isInitialized && type.hasUnique(uniqueType, state)
|
||||
}
|
||||
|
||||
override fun hasUnique(uniqueTag: String, state: StateForConditionals?): Boolean {
|
||||
return super<RulesetObject>.hasUnique(uniqueTag, state) || ::ruleset.isInitialized && type.hasUnique(uniqueTag, state)
|
||||
}
|
||||
|
||||
override fun hasTagUnique(tagUnique: String): Boolean {
|
||||
return super<RulesetObject>.hasTagUnique(tagUnique) || ::ruleset.isInitialized && type.hasTagUnique(tagUnique)
|
||||
}
|
||||
@ -138,6 +142,20 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
return ourUniques + type.getMatchingUniques(uniqueType, state)
|
||||
}
|
||||
|
||||
/** Allows unique functions (getMatchingUniques, hasUnique) to "see" uniques from the UnitType */
|
||||
override fun getMatchingUniques(uniqueTag: String, state: StateForConditionals): Sequence<Unique> {
|
||||
val ourUniques = super<RulesetObject>.getMatchingUniques(uniqueTag, state)
|
||||
if (! ::ruleset.isInitialized) { // Not sure if this will ever actually happen, but better safe than sorry
|
||||
return ourUniques
|
||||
}
|
||||
val typeUniques = type.getMatchingUniques(uniqueTag, state)
|
||||
// Memory optimization - very rarely do we actually get uniques from both sources,
|
||||
// and sequence addition is expensive relative to the rare case that we'll actually need it
|
||||
if (ourUniques.none()) return typeUniques
|
||||
if (typeUniques.none()) return ourUniques
|
||||
return ourUniques + type.getMatchingUniques(uniqueTag, state)
|
||||
}
|
||||
|
||||
override fun getProductionCost(civInfo: Civilization, city: City?): Int = costFunctions.getProductionCost(civInfo, city)
|
||||
|
||||
override fun canBePurchasedWithStat(city: City?, stat: Stat): Boolean {
|
||||
@ -211,7 +229,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
if (civ.tech.isResearched(obsoleteTech))
|
||||
yield(RejectionReasonType.Obsoleted.toInstance("Obsolete by $obsoleteTech"))
|
||||
|
||||
if (uniqueTo != null && !civ.matchesFilter(uniqueTo!!))
|
||||
if (uniqueTo != null && !civ.matchesFilter(uniqueTo!!, stateForConditionals))
|
||||
yield(RejectionReasonType.UniqueToOtherNation.toInstance("Unique to $uniqueTo"))
|
||||
if (civ.cache.uniqueUnits.any { it.replaces == name })
|
||||
yield(RejectionReasonType.ReplacedByOurUnique.toInstance("Our unique unit replaces this"))
|
||||
@ -250,7 +268,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
}
|
||||
|
||||
for (unique in civ.getMatchingUniques(UniqueType.CannotBuildUnits, stateForConditionals))
|
||||
if (this@BaseUnit.matchesFilter(unique.params[0])) {
|
||||
if (this@BaseUnit.matchesFilter(unique.params[0], stateForConditionals)) {
|
||||
val hasHappinessCondition = unique.hasModifier(UniqueType.ConditionalBelowHappiness)
|
||||
|| unique.hasModifier(UniqueType.ConditionalBetweenHappiness)
|
||||
if (hasHappinessCondition)
|
||||
@ -384,8 +402,17 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
private val cachedMatchesFilterResult = HashMap<String, Boolean>()
|
||||
|
||||
/** Implements [UniqueParameterType.BaseUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.BaseUnitFilter] */
|
||||
fun matchesFilter(filter: String): Boolean =
|
||||
cachedMatchesFilterResult.getOrPut(filter) { MultiFilter.multiFilter(filter, ::matchesSingleFilter ) }
|
||||
fun matchesFilter(filter: String, state: StateForConditionals? = null, multiFilter: Boolean = true): Boolean {
|
||||
return if (multiFilter) MultiFilter.multiFilter(filter, {
|
||||
cachedMatchesFilterResult.getOrPut(it) { matchesSingleFilter(it) } ||
|
||||
state != null && hasUnique(it, state) ||
|
||||
state == null && hasTagUnique(it)
|
||||
})
|
||||
else cachedMatchesFilterResult.getOrPut(filter) { matchesSingleFilter(filter) } ||
|
||||
state != null && hasUnique(filter, state) ||
|
||||
state == null && hasTagUnique(filter)
|
||||
}
|
||||
|
||||
|
||||
fun matchesSingleFilter(filter: String): Boolean {
|
||||
return when (filter) {
|
||||
@ -408,16 +435,16 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
"Religious" -> hasUnique(UniqueType.ReligiousUnit)
|
||||
|
||||
else -> {
|
||||
if (type.matchesFilter(filter)) return true
|
||||
for (requiredTech: String in requiredTechs())
|
||||
if (ruleset.technologies[requiredTech]?.matchesFilter(filter) == true) return true
|
||||
if (ruleset.technologies[requiredTech]?.matchesFilter(filter, multiFilter = false) == true) return true
|
||||
if (
|
||||
// Uniques using these kinds of filters should be deprecated and replaced with adjective-only parameters
|
||||
filter.endsWith(" units")
|
||||
// "military units" --> "Military", using invariant locale
|
||||
&& matchesFilter(filter.removeSuffix(" units").lowercase().replaceFirstChar { it.uppercaseChar() })
|
||||
&& matchesFilter(filter.removeSuffix(" units").lowercase().replaceFirstChar { it.uppercaseChar() },
|
||||
multiFilter = false)
|
||||
) return true
|
||||
return uniqueMap.hasTagUnique(filter)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,25 +36,25 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
|
||||
/** Contains only unit-specific uniques that allow purchasing with stat */
|
||||
fun canBePurchasedWithStat(city: City, stat: Stat): Boolean {
|
||||
val conditionalState = StateForConditionals(civInfo = city.civ, city = city)
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
if (city.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState)
|
||||
.any {
|
||||
it.params[2] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}
|
||||
) return true
|
||||
|
||||
if (city.getMatchingUniques(UniqueType.BuyUnitsByProductionCost, conditionalState)
|
||||
.any { it.params[1] == stat.name && baseUnit.matchesFilter(it.params[0]) }
|
||||
.any { it.params[1] == stat.name && baseUnit.matchesFilter(it.params[0], conditionalState) }
|
||||
)
|
||||
return true
|
||||
|
||||
if (city.getMatchingUniques(UniqueType.BuyUnitsWithStat, conditionalState)
|
||||
.any {
|
||||
it.params[1] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[2])
|
||||
}
|
||||
)
|
||||
@ -63,7 +63,7 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
if (city.getMatchingUniques(UniqueType.BuyUnitsForAmountStat, conditionalState)
|
||||
.any {
|
||||
it.params[2] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}
|
||||
)
|
||||
@ -75,9 +75,10 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
|
||||
fun getStatBuyCost(city: City, stat: Stat): Int? {
|
||||
var cost = baseUnit.getBaseBuyCost(city, stat)?.toDouble() ?: return null
|
||||
val conditionalState = StateForConditionals(city)
|
||||
|
||||
for (unique in city.getMatchingUniques(UniqueType.BuyUnitsDiscount)) {
|
||||
if (stat.name == unique.params[0] && baseUnit.matchesFilter(unique.params[1]))
|
||||
if (stat.name == unique.params[0] && baseUnit.matchesFilter(unique.params[1], conditionalState))
|
||||
cost *= unique.params[2].toPercent()
|
||||
}
|
||||
for (unique in city.getMatchingUniques(UniqueType.BuyItemsDiscount))
|
||||
@ -94,7 +95,7 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyUnitsIncreasingCost, conditionalState)
|
||||
.filter {
|
||||
it.params[2] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}.map {
|
||||
baseUnit.getCostForConstructionsIncreasingInPrice(
|
||||
@ -105,14 +106,14 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
}
|
||||
)
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyUnitsByProductionCost, conditionalState)
|
||||
.filter { it.params[1] == stat.name && baseUnit.matchesFilter(it.params[0]) }
|
||||
.filter { it.params[1] == stat.name && baseUnit.matchesFilter(it.params[0], conditionalState) }
|
||||
.map { (getProductionCost(city.civ, city) * it.params[2].toInt()).toFloat() }
|
||||
)
|
||||
|
||||
if (city.getMatchingUniques(UniqueType.BuyUnitsWithStat, conditionalState)
|
||||
.any {
|
||||
it.params[1] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[2])
|
||||
}
|
||||
) yield(city.civ.getEra().baseUnitBuyCost * city.civ.gameInfo.speed.statCostModifiers[stat]!!)
|
||||
@ -120,7 +121,7 @@ class BaseUnitCost(val baseUnit: BaseUnit) {
|
||||
yieldAll(city.getMatchingUniques(UniqueType.BuyUnitsForAmountStat, conditionalState)
|
||||
.filter {
|
||||
it.params[2] == stat.name
|
||||
&& baseUnit.matchesFilter(it.params[0])
|
||||
&& baseUnit.matchesFilter(it.params[0], conditionalState)
|
||||
&& city.matchesFilter(it.params[3])
|
||||
}.map { it.params[1].toInt() * city.civ.gameInfo.speed.statCostModifiers[stat]!! }
|
||||
)
|
||||
|
@ -36,7 +36,7 @@ class UnitType() : RulesetObject() {
|
||||
"Land" -> isLandUnit()
|
||||
"Water" -> isWaterUnit()
|
||||
"Air" -> isAirUnit()
|
||||
else -> uniqueMap.hasTagUnique(filter)
|
||||
else -> hasTagUnique(filter)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +269,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
||||
&& constructionButtonDTOList.any {
|
||||
(it.construction is Building) && (it.construction.name == dto.construction.requiredBuilding
|
||||
|| it.construction.replaces == dto.construction.requiredBuilding
|
||||
|| it.construction.hasTagUnique(dto.construction.requiredBuilding!!))
|
||||
|| it.construction.hasUnique(dto.construction.requiredBuilding!!, StateForConditionals(cityScreen.city)))
|
||||
})
|
||||
continue
|
||||
|
||||
|
@ -296,7 +296,7 @@ object UnitActionsFromUniques {
|
||||
|
||||
for (unique in uniquesToCheck) {
|
||||
val improvementFilter = unique.params[0]
|
||||
val improvements = tile.ruleset.tileImprovements.values.filter { it.matchesFilter(improvementFilter) }
|
||||
val improvements = tile.ruleset.tileImprovements.values.filter { it.matchesFilter(improvementFilter, StateForConditionals(unit = unit, tile = tile)) }
|
||||
|
||||
for (improvement in improvements) {
|
||||
// Try to skip Improvements we can never build
|
||||
|
Loading…
Reference in New Issue
Block a user