mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-04 23:40:01 +07:00
"for every [countable]" unique modifier (#11641)
* "for every [countable]" unique modifier * detekt fix * Test city-unique edge cases
This commit is contained in:
@ -483,7 +483,7 @@ class City : IsPartOfGameInfoSerialization {
|
||||
+ religion.getUniques().filter { it.type == uniqueType }
|
||||
).filter {
|
||||
!it.isTimedTriggerable && it.conditionalsApply(stateForConditionals)
|
||||
}
|
||||
}.flatMap { it.getMultiplied(stateForConditionals) }
|
||||
}
|
||||
|
||||
// Uniques special to this city
|
||||
@ -491,6 +491,7 @@ class City : IsPartOfGameInfoSerialization {
|
||||
val uniques = cityConstructions.builtBuildingUniqueMap.getUniques(uniqueType).filter { it.isLocalEffect } +
|
||||
religion.getUniques().filter { it.type == uniqueType }
|
||||
return if (uniques.any()) uniques.filter { !it.isTimedTriggerable && it.conditionalsApply(stateForConditionals) }
|
||||
.flatMap { it.getMultiplied(stateForConditionals) }
|
||||
else uniques
|
||||
}
|
||||
|
||||
@ -499,7 +500,7 @@ class City : IsPartOfGameInfoSerialization {
|
||||
val uniques = cityConstructions.builtBuildingUniqueMap.getUniques(uniqueType)
|
||||
// Memory performance showed that this function was very memory intensive, thus we only create the filter if needed
|
||||
return if (uniques.any()) uniques.filter { !it.isLocalEffect && !it.isTimedTriggerable
|
||||
&& it.conditionalsApply(stateForConditionals) }
|
||||
&& it.conditionalsApply(stateForConditionals) }.flatMap { it.getMultiplied(stateForConditionals) }
|
||||
else uniques
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package com.unciv.models.ruleset.unique
|
||||
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.battle.CombatAction
|
||||
import com.unciv.logic.battle.MapUnitCombatant
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.managers.ReligionState
|
||||
@ -20,62 +19,31 @@ object Conditionals {
|
||||
if (condition.type?.targetTypes?.any { it.modifierType == UniqueTarget.ModifierType.Other } == true)
|
||||
return true // not a filtering condition, includes e.g. ModifierHiddenFromUsers
|
||||
|
||||
val relevantUnit by lazy {
|
||||
if (state.ourCombatant != null && state.ourCombatant is MapUnitCombatant) state.ourCombatant.unit
|
||||
else state.unit
|
||||
}
|
||||
|
||||
val relevantTile by lazy { state.attackedTile
|
||||
?: state.tile
|
||||
// We need to protect against conditionals checking tiles for units pre-placement - see #10425, #10512
|
||||
?: relevantUnit?.run { if (hasTile()) getTile() else null }
|
||||
?: state.city?.getCenterTile()
|
||||
}
|
||||
|
||||
val relevantCity by lazy {
|
||||
state.city
|
||||
?: relevantTile?.getCity()
|
||||
}
|
||||
|
||||
val relevantCiv by lazy {
|
||||
state.civInfo ?:
|
||||
relevantCity?.civ ?:
|
||||
relevantUnit?.civ
|
||||
}
|
||||
|
||||
val gameInfo by lazy { relevantCiv?.gameInfo }
|
||||
|
||||
val stateBasedRandom by lazy { Random(state.hashCode() * 31 + (gameInfo?.turns?.hashCode() ?: 0)) }
|
||||
|
||||
fun getResourceAmount(resourceName: String): Int {
|
||||
if (relevantCity != null) return relevantCity!!.getAvailableResourceAmount(resourceName)
|
||||
if (relevantCiv != null) return relevantCiv!!.getResourceAmount(resourceName)
|
||||
return 0
|
||||
}
|
||||
val stateBasedRandom by lazy { Random(state.hashCode() * 31 + (state.gameInfo?.turns?.hashCode() ?: 0)) }
|
||||
|
||||
/** Helper to simplify conditional tests requiring gameInfo */
|
||||
fun checkOnGameInfo(predicate: (GameInfo.() -> Boolean)): Boolean {
|
||||
if (gameInfo == null) return false
|
||||
return gameInfo!!.predicate()
|
||||
if (state.gameInfo == null) return false
|
||||
return state.gameInfo!!.predicate()
|
||||
}
|
||||
|
||||
/** Helper to simplify conditional tests requiring a Civilization */
|
||||
fun checkOnCiv(predicate: (Civilization.() -> Boolean)): Boolean {
|
||||
if (relevantCiv == null) return false
|
||||
return relevantCiv!!.predicate()
|
||||
if (state.relevantCiv == null) return false
|
||||
return state.relevantCiv!!.predicate()
|
||||
}
|
||||
|
||||
/** Helper to simplify conditional tests requiring a City */
|
||||
fun checkOnCity(predicate: (City.() -> Boolean)): Boolean {
|
||||
if (relevantCity == null) return false
|
||||
return relevantCity!!.predicate()
|
||||
if (state.relevantCity == null) return false
|
||||
return state.relevantCity!!.predicate()
|
||||
}
|
||||
|
||||
/** Helper to simplify the "compare civ's current era with named era" conditions */
|
||||
fun compareEra(eraParam: String, compare: (civEra: Int, paramEra: Int) -> Boolean): Boolean {
|
||||
if (gameInfo == null) return false
|
||||
val era = gameInfo!!.ruleset.eras[eraParam] ?: return false
|
||||
return compare(relevantCiv!!.getEraNumber(), era.eraNumber)
|
||||
if (state.gameInfo == null) return false
|
||||
val era = state.gameInfo!!.ruleset.eras[eraParam] ?: return false
|
||||
return compare(state.relevantCiv!!.getEraNumber(), era.eraNumber)
|
||||
}
|
||||
|
||||
/** Helper for ConditionalWhenAboveAmountStatResource and its below counterpart */
|
||||
@ -86,50 +54,27 @@ object Conditionals {
|
||||
modifyByGameSpeed: Boolean = false,
|
||||
compare: (current: Int, lowerLimit: Float, upperLimit: Float) -> Boolean
|
||||
): Boolean {
|
||||
if (gameInfo == null) return false
|
||||
var gameSpeedModifier = if (modifyByGameSpeed) gameInfo!!.speed.modifier else 1f
|
||||
if (state.gameInfo == null) return false
|
||||
var gameSpeedModifier = if (modifyByGameSpeed) state.gameInfo!!.speed.modifier else 1f
|
||||
|
||||
if (gameInfo!!.ruleset.tileResources.containsKey(resourceOrStatName))
|
||||
return compare(getResourceAmount(resourceOrStatName), lowerLimit * gameSpeedModifier, upperLimit * gameSpeedModifier)
|
||||
if (state.gameInfo!!.ruleset.tileResources.containsKey(resourceOrStatName))
|
||||
return compare(state.getResourceAmount(resourceOrStatName), lowerLimit * gameSpeedModifier, upperLimit * gameSpeedModifier)
|
||||
val stat = Stat.safeValueOf(resourceOrStatName)
|
||||
?: return false
|
||||
val statReserve = if (relevantCity != null) relevantCity!!.getStatReserve(stat) else relevantCiv!!.getStatReserve(stat)
|
||||
val statReserve = if (state.relevantCity != null) state.relevantCity!!.getStatReserve(stat) else state.relevantCiv!!.getStatReserve(stat)
|
||||
|
||||
gameSpeedModifier = if (modifyByGameSpeed) gameInfo!!.speed.statCostModifiers[stat]!! else 1f
|
||||
gameSpeedModifier = if (modifyByGameSpeed) state.gameInfo!!.speed.statCostModifiers[stat]!! else 1f
|
||||
return compare(statReserve, lowerLimit * gameSpeedModifier, upperLimit * gameSpeedModifier)
|
||||
}
|
||||
|
||||
fun getCountableAmount(countable: String): Float? {
|
||||
if (countable.toFloatOrNull() != null) return countable.toFloat()
|
||||
|
||||
val relevantStat = Stat.safeValueOf(countable)
|
||||
|
||||
if (relevantStat != null) {
|
||||
return if (relevantCity != null) {
|
||||
relevantCity!!.getStatReserve(relevantStat).toFloat()
|
||||
} else if (relevantStat in Stat.statsWithCivWideField && relevantCiv != null) {
|
||||
relevantCiv!!.getStatReserve(relevantStat).toFloat()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (gameInfo == null) return null
|
||||
|
||||
if (countable == "year") return gameInfo!!.getYear(gameInfo!!.turns).toFloat()
|
||||
if (gameInfo!!.ruleset.tileResources.containsKey(countable))
|
||||
return getResourceAmount(countable).toFloat()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun compareCountables(
|
||||
first: String,
|
||||
second: String,
|
||||
compare: (first: Float, second: Float) -> Boolean): Boolean {
|
||||
compare: (first: Int, second: Int) -> Boolean): Boolean {
|
||||
|
||||
val firstNumber = getCountableAmount(first)
|
||||
val secondNumber = getCountableAmount(second)
|
||||
val firstNumber = Countables.getCountableAmount(first, state)
|
||||
val secondNumber = Countables.getCountableAmount(second, state)
|
||||
|
||||
return if (firstNumber != null && secondNumber != null)
|
||||
compare(firstNumber, secondNumber)
|
||||
@ -138,11 +83,11 @@ object Conditionals {
|
||||
}
|
||||
|
||||
fun compareCountables(first: String, second: String, third: String,
|
||||
compare: (first: Float, second: Float, third: Float) -> Boolean): Boolean {
|
||||
compare: (first: Int, second: Int, third: Int) -> Boolean): Boolean {
|
||||
|
||||
val firstNumber = getCountableAmount(first)
|
||||
val secondNumber = getCountableAmount(second)
|
||||
val thirdNumber = getCountableAmount(third)
|
||||
val firstNumber = Countables.getCountableAmount(first, state)
|
||||
val secondNumber = Countables.getCountableAmount(second, state)
|
||||
val thirdNumber = Countables.getCountableAmount(third, state)
|
||||
|
||||
return if (firstNumber != null && secondNumber != null && thirdNumber != null)
|
||||
compare(firstNumber, secondNumber, thirdNumber)
|
||||
@ -162,8 +107,8 @@ object Conditionals {
|
||||
UniqueType.ConditionalCivFilter -> checkOnCiv { matchesFilter(condition.params[0]) }
|
||||
UniqueType.ConditionalWar -> checkOnCiv { isAtWar() }
|
||||
UniqueType.ConditionalNotWar -> checkOnCiv { !isAtWar() }
|
||||
UniqueType.ConditionalWithResource -> getResourceAmount(condition.params[0]) > 0
|
||||
UniqueType.ConditionalWithoutResource -> getResourceAmount(condition.params[0]) <= 0
|
||||
UniqueType.ConditionalWithResource -> state.getResourceAmount(condition.params[0]) > 0
|
||||
UniqueType.ConditionalWithoutResource -> state.getResourceAmount(condition.params[0]) <= 0
|
||||
|
||||
UniqueType.ConditionalWhenAboveAmountStatResource ->
|
||||
checkResourceOrStatAmount(condition.params[1], condition.params[0].toFloat(), Float.MAX_VALUE)
|
||||
@ -233,15 +178,15 @@ object Conditionals {
|
||||
checkOnGameInfo { getCities().any { it.cityConstructions.containsBuildingOrEquivalent(condition.params[0]) } }
|
||||
|
||||
// Filtered via city.getMatchingUniques
|
||||
UniqueType.ConditionalInThisCity -> relevantCity != null
|
||||
UniqueType.ConditionalCityFilter -> checkOnCity { matchesFilter(condition.params[0], relevantCiv) }
|
||||
UniqueType.ConditionalInThisCity -> state.relevantCity != null
|
||||
UniqueType.ConditionalCityFilter -> checkOnCity { matchesFilter(condition.params[0], state.relevantCiv) }
|
||||
UniqueType.ConditionalCityConnected -> checkOnCity { isConnectedToCapital() }
|
||||
UniqueType.ConditionalCityMajorReligion -> checkOnCity {
|
||||
religion.getMajorityReligion()?.isMajorReligion() == true }
|
||||
UniqueType.ConditionalCityEnhancedReligion -> checkOnCity {
|
||||
religion.getMajorityReligion()?.isEnhancedReligion() == true }
|
||||
UniqueType.ConditionalCityThisReligion -> checkOnCity {
|
||||
religion.getMajorityReligion() == relevantCiv?.religionManager?.religion }
|
||||
religion.getMajorityReligion() == state.relevantCiv?.religionManager?.religion }
|
||||
UniqueType.ConditionalWLTKD -> checkOnCity { isWeLoveTheKingDayActive() }
|
||||
UniqueType.ConditionalCityWithBuilding ->
|
||||
checkOnCity { cityConstructions.containsBuildingOrEquivalent(condition.params[0]) }
|
||||
@ -257,58 +202,58 @@ object Conditionals {
|
||||
UniqueType.ConditionalVsCity -> state.theirCombatant?.matchesFilter("City") == true
|
||||
UniqueType.ConditionalVsUnits, UniqueType.ConditionalVsCombatant -> state.theirCombatant?.matchesFilter(condition.params[0]) == true
|
||||
UniqueType.ConditionalOurUnit, UniqueType.ConditionalOurUnitOnUnit ->
|
||||
relevantUnit?.matchesFilter(condition.params[0]) == true
|
||||
UniqueType.ConditionalUnitWithPromotion -> relevantUnit?.promotions?.promotions?.contains(condition.params[0]) == true
|
||||
UniqueType.ConditionalUnitWithoutPromotion -> relevantUnit?.promotions?.promotions?.contains(condition.params[0]) == false
|
||||
state.relevantUnit?.matchesFilter(condition.params[0]) == true
|
||||
UniqueType.ConditionalUnitWithPromotion -> state.relevantUnit?.promotions?.promotions?.contains(condition.params[0]) == true
|
||||
UniqueType.ConditionalUnitWithoutPromotion -> state.relevantUnit?.promotions?.promotions?.contains(condition.params[0]) == false
|
||||
UniqueType.ConditionalAttacking -> state.combatAction == CombatAction.Attack
|
||||
UniqueType.ConditionalDefending -> state.combatAction == CombatAction.Defend
|
||||
UniqueType.ConditionalAboveHP -> relevantUnit != null && relevantUnit!!.health > condition.params[0].toInt()
|
||||
UniqueType.ConditionalAboveHP -> state.relevantUnit != null && state.relevantUnit!!.health > condition.params[0].toInt()
|
||||
|| state.ourCombatant != null && state.ourCombatant.getHealth() > condition.params[0].toInt()
|
||||
UniqueType.ConditionalBelowHP -> relevantUnit != null && relevantUnit!!.health < condition.params[0].toInt()
|
||||
UniqueType.ConditionalBelowHP -> state.relevantUnit != null && state.relevantUnit!!.health < condition.params[0].toInt()
|
||||
||state.ourCombatant != null && state.ourCombatant.getHealth() < condition.params[0].toInt()
|
||||
UniqueType.ConditionalHasNotUsedOtherActions ->
|
||||
state.unit == null || // So we get the action as a valid action in BaseUnit.hasUnique()
|
||||
state.unit.abilityToTimesUsed.isEmpty()
|
||||
|
||||
UniqueType.ConditionalInTiles ->
|
||||
relevantTile?.matchesFilter(condition.params[0], relevantCiv) == true
|
||||
state.relevantTile?.matchesFilter(condition.params[0], state.relevantCiv) == true
|
||||
UniqueType.ConditionalInTilesNot ->
|
||||
relevantTile?.matchesFilter(condition.params[0], relevantCiv) == false
|
||||
UniqueType.ConditionalAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], relevantCiv) == true
|
||||
UniqueType.ConditionalNotAdjacentTo -> relevantTile?.isAdjacentTo(condition.params[0], relevantCiv) == false
|
||||
state.relevantTile?.matchesFilter(condition.params[0], state.relevantCiv) == false
|
||||
UniqueType.ConditionalAdjacentTo -> state.relevantTile?.isAdjacentTo(condition.params[0], state.relevantCiv) == true
|
||||
UniqueType.ConditionalNotAdjacentTo -> state.relevantTile?.isAdjacentTo(condition.params[0], state.relevantCiv) == false
|
||||
UniqueType.ConditionalFightingInTiles ->
|
||||
state.attackedTile?.matchesFilter(condition.params[0], relevantCiv) == true
|
||||
state.attackedTile?.matchesFilter(condition.params[0], state.relevantCiv) == true
|
||||
UniqueType.ConditionalNearTiles ->
|
||||
relevantTile != null && relevantTile!!.getTilesInDistance(condition.params[0].toInt()).any {
|
||||
state.relevantTile != null && state.relevantTile!!.getTilesInDistance(condition.params[0].toInt()).any {
|
||||
it.matchesFilter(condition.params[1])
|
||||
}
|
||||
|
||||
UniqueType.ConditionalVsLargerCiv -> {
|
||||
val yourCities = relevantCiv?.cities?.size ?: 1
|
||||
val yourCities = state.relevantCiv?.cities?.size ?: 1
|
||||
val theirCities = state.theirCombatant?.getCivInfo()?.cities?.size ?: 0
|
||||
yourCities < theirCities
|
||||
}
|
||||
UniqueType.ConditionalForeignContinent -> checkOnCiv {
|
||||
relevantTile != null && (
|
||||
state.relevantTile != null && (
|
||||
cities.isEmpty() || getCapital() == null
|
||||
|| getCapital()!!.getCenterTile().getContinent() != relevantTile!!.getContinent()
|
||||
|| getCapital()!!.getCenterTile().getContinent() != state.relevantTile!!.getContinent()
|
||||
)
|
||||
}
|
||||
UniqueType.ConditionalAdjacentUnit ->
|
||||
relevantCiv != null &&
|
||||
relevantUnit != null &&
|
||||
relevantTile!!.neighbors.any {
|
||||
state.relevantCiv != null &&
|
||||
state.relevantUnit != null &&
|
||||
state.relevantTile!!.neighbors.any {
|
||||
it.getUnits().any {
|
||||
it != relevantUnit &&
|
||||
it.civ == relevantCiv &&
|
||||
it != state.relevantUnit &&
|
||||
it.civ == state.relevantCiv &&
|
||||
it.matchesFilter(condition.params[0])
|
||||
}
|
||||
}
|
||||
|
||||
UniqueType.ConditionalNeighborTiles ->
|
||||
relevantTile != null
|
||||
&& relevantTile!!.neighbors.count {
|
||||
it.matchesFilter(condition.params[2], relevantCiv)
|
||||
state.relevantTile != null
|
||||
&& state.relevantTile!!.neighbors.count {
|
||||
it.matchesFilter(condition.params[2], state.relevantCiv)
|
||||
} in condition.params[0].toInt()..condition.params[1].toInt()
|
||||
|
||||
UniqueType.ConditionalOnWaterMaps -> state.region?.continentID == -1
|
||||
@ -319,7 +264,7 @@ object Conditionals {
|
||||
unique != null
|
||||
&& unique.sourceObjectType == UniqueTarget.Tech
|
||||
&& checkOnGameInfo { civilizations.none {
|
||||
it != relevantCiv && it.isMajorCiv()
|
||||
it != state.relevantCiv && it.isMajorCiv()
|
||||
&& it.tech.isResearched(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||
} }
|
||||
|
||||
@ -327,7 +272,7 @@ object Conditionals {
|
||||
unique != null
|
||||
&& unique.sourceObjectType == UniqueTarget.Policy
|
||||
&& checkOnGameInfo { civilizations.none {
|
||||
it != relevantCiv && it.isMajorCiv()
|
||||
it != state.relevantCiv && it.isMajorCiv()
|
||||
&& it.policies.isAdopted(unique.sourceObjectName!!) // guarded by the sourceObjectType check
|
||||
} }
|
||||
|
||||
|
30
core/src/com/unciv/models/ruleset/unique/Countables.kt
Normal file
30
core/src/com/unciv/models/ruleset/unique/Countables.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package com.unciv.models.ruleset.unique
|
||||
|
||||
import com.unciv.models.stats.Stat
|
||||
|
||||
object Countables {
|
||||
|
||||
fun getCountableAmount(countable: String, stateForConditionals: StateForConditionals): Int? {
|
||||
if (countable.toIntOrNull() != null) return countable.toInt()
|
||||
|
||||
val relevantStat = Stat.safeValueOf(countable)
|
||||
|
||||
if (relevantStat != null) {
|
||||
return if (stateForConditionals.relevantCity != null) {
|
||||
stateForConditionals.relevantCity!!.getStatReserve(relevantStat)
|
||||
} else if (relevantStat in Stat.statsWithCivWideField && stateForConditionals.relevantCiv != null) {
|
||||
stateForConditionals.relevantCiv!!.getStatReserve(relevantStat)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (stateForConditionals.gameInfo == null) return null
|
||||
|
||||
if (countable == "year") return stateForConditionals.gameInfo!!.getYear(stateForConditionals.gameInfo!!.turns)
|
||||
if (stateForConditionals.gameInfo!!.ruleset.tileResources.containsKey(countable))
|
||||
return stateForConditionals.getResourceAmount(countable)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
@ -37,7 +37,10 @@ interface IHasUniques : INamed {
|
||||
|
||||
fun getMatchingUniques(uniqueTemplate: String, stateForConditionals: StateForConditionals? = null): Sequence<Unique> {
|
||||
val matchingUniques = uniqueMap[uniqueTemplate] ?: return sequenceOf()
|
||||
return matchingUniques.asSequence().filter { it.conditionalsApply(stateForConditionals ?: StateForConditionals()) }
|
||||
|
||||
val actualStateForConditionals = stateForConditionals ?: StateForConditionals()
|
||||
val uniques = matchingUniques.asSequence().filter { it.conditionalsApply(actualStateForConditionals) }
|
||||
return uniques.flatMap { it.getMultiplied(actualStateForConditionals) }
|
||||
}
|
||||
|
||||
fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals? = null) =
|
||||
|
@ -39,6 +39,38 @@ data class StateForConditionals(
|
||||
combatAction
|
||||
)
|
||||
|
||||
|
||||
val relevantUnit by lazy {
|
||||
if (ourCombatant != null && ourCombatant is MapUnitCombatant) ourCombatant.unit
|
||||
else unit
|
||||
}
|
||||
|
||||
val relevantTile by lazy { attackedTile
|
||||
?: 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()
|
||||
}
|
||||
|
||||
val relevantCity by lazy {
|
||||
city
|
||||
?: relevantTile?.getCity()
|
||||
}
|
||||
|
||||
val relevantCiv by lazy {
|
||||
civInfo ?:
|
||||
relevantCity?.civ ?:
|
||||
relevantUnit?.civ
|
||||
}
|
||||
|
||||
val gameInfo by lazy { relevantCiv?.gameInfo }
|
||||
|
||||
fun getResourceAmount(resourceName: String): Int {
|
||||
if (relevantCity != null) return relevantCity!!.getAvailableResourceAmount(resourceName)
|
||||
if (relevantCiv != null) return relevantCiv!!.getResourceAmount(resourceName)
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object {
|
||||
val IgnoreConditionals = StateForConditionals(ignoreConditionals = true)
|
||||
}
|
||||
@ -67,4 +99,7 @@ data class StateForConditionals(
|
||||
result = 31 * result + ignoreConditionals.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,30 @@ class Unique(val text: String, val sourceObjectType: UniqueTarget? = null, val s
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getUniqueMultiplier(stateForConditionals: StateForConditionals = StateForConditionals()): Int {
|
||||
val multiplierConditionals = conditionals.filter { it.type == UniqueType.ForEveryCountable }
|
||||
if (multiplierConditionals.isEmpty()) return 1
|
||||
var amount = 1
|
||||
for (conditional in multiplierConditionals) { // multiple multipliers DO multiply.
|
||||
val multiplier = Countables.getCountableAmount(conditional.params[0], stateForConditionals)
|
||||
if (multiplier != null) amount *= multiplier
|
||||
}
|
||||
return amount.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
/** Multiplies the unique according to the multiplication conditionals */
|
||||
fun getMultiplied(stateForConditionals: StateForConditionals = StateForConditionals()): Sequence<Unique> {
|
||||
val multiplier = getUniqueMultiplier(stateForConditionals)
|
||||
return EndlessSequenceOf(this).take(multiplier)
|
||||
}
|
||||
|
||||
private class EndlessSequenceOf<T>(private val value: T) : Sequence<T> {
|
||||
override fun iterator(): Iterator<T> = object : Iterator<T> {
|
||||
override fun next() = value
|
||||
override fun hasNext() = true
|
||||
}
|
||||
}
|
||||
|
||||
fun getDeprecationAnnotation(): Deprecated? = type?.getDeprecationAnnotation()
|
||||
|
||||
fun getSourceNameForUser(): String {
|
||||
@ -244,9 +268,6 @@ class UniqueMap() : HashMap<String, ArrayList<Unique>>() {
|
||||
&& unique.conditionalsApply(stateForConditionals)
|
||||
}
|
||||
}
|
||||
|
||||
/** This is an alias for [containsKey] to clarify when a pure string-based check is legitimate. */
|
||||
fun containsFilteringUnique(filter: String) = containsKey(filter)
|
||||
}
|
||||
|
||||
|
||||
|
@ -87,7 +87,12 @@ object UniqueTriggerActivation {
|
||||
|
||||
val timingConditional = unique.conditionals.firstOrNull { it.type == UniqueType.ConditionalTimedUnique }
|
||||
if (timingConditional != null) {
|
||||
return { civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) }
|
||||
return {
|
||||
civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt()))
|
||||
if (unique.type in setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources))
|
||||
civInfo.cache.updateCivResources()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
val stateForConditionals = StateForConditionals(civInfo, city, unit, tile)
|
||||
|
@ -870,6 +870,7 @@ enum class UniqueType(
|
||||
HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers),
|
||||
HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.Displayable, flags = UniqueFlag.setOfHiddenToUsers),
|
||||
ModifierHiddenFromUsers("hidden from users", UniqueTarget.MetaModifier),
|
||||
ForEveryCountable("for every [countable]", UniqueTarget.MetaModifier),
|
||||
Comment("Comment [comment]", *UniqueTarget.Displayable,
|
||||
docDescription = "Allows displaying arbitrary text in a Unique listing. Only the text within the '[]' brackets will be displayed, the rest serves to allow Ruleset validation to recognize the intent."),
|
||||
|
||||
|
@ -2479,6 +2479,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
??? example "<hidden from users>"
|
||||
Applicable to: MetaModifier
|
||||
|
||||
??? example "<for every [countable]>"
|
||||
Example: "<for every [1000]>"
|
||||
|
||||
Applicable to: MetaModifier
|
||||
|
||||
|
||||
*[amount]: This indicates a whole number, possibly with a + or - sign, such as `2`, `+13`, or `-3`.
|
||||
*[baseTerrain]: The name of any terrain that is a base terrain according to the json file.
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.unciv.uniques
|
||||
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
import com.unciv.testing.TestGame
|
||||
import org.junit.Assert
|
||||
@ -121,4 +123,16 @@ class ResourceTests {
|
||||
city.cityConstructions.addBuilding(doubleStrategic)
|
||||
Assert.assertTrue(civInfo.getCivResourcesByName()["Coal"] == 4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPerCountableForGlobalAndLocalResources() {
|
||||
// one coal provided locally
|
||||
val consumesCoal = game.createBuilding("Provides [1] [Coal]")
|
||||
city.cityConstructions.addBuilding(consumesCoal)
|
||||
// one globally
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Provides [1] [Coal] <for [2] turns>"), civInfo)
|
||||
val providesFaithPerCoal = game.createBuilding("[+1 Faith] [in this city] <for every [Coal]>")
|
||||
city.cityConstructions.addBuilding(providesFaithPerCoal)
|
||||
Assert.assertEquals(2f, city.cityStats.currentCityStats.faith)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user