Merge branch 'yairm210:master' into updated-so-you-can-build-naval-unit-on-water-tile

This commit is contained in:
General_E 2025-08-29 14:14:57 +02:00 committed by GitHub
commit 22935e661d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 121 additions and 80 deletions

View File

@ -3,7 +3,7 @@ package com.unciv.build
object BuildConfig { object BuildConfig {
const val appName = "Unciv" const val appName = "Unciv"
const val appCodeNumber = 1158 const val appCodeNumber = 1159
const val appVersion = "4.17.17" const val appVersion = "4.17.17-patch1"
const val identifier = "com.unciv.app" const val identifier = "com.unciv.app"
} }

View File

@ -494,7 +494,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
companion object { companion object {
//region AUTOMATICALLY GENERATED VERSION DATA - DO NOT CHANGE THIS REGION, INCLUDING THIS COMMENT //region AUTOMATICALLY GENERATED VERSION DATA - DO NOT CHANGE THIS REGION, INCLUDING THIS COMMENT
val VERSION = Version("4.17.17", 1158) val VERSION = Version("4.17.17-patch1", 1159)
//endregion //endregion
/** Global reference to the one Gdx.Game instance created by the platform launchers - do not use without checking [isCurrentInitialized] first. */ /** Global reference to the one Gdx.Game instance created by the platform launchers - do not use without checking [isCurrentInitialized] first. */

View File

@ -297,6 +297,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
} else value += when { } else value += when {
building.hasUnique(UniqueType.CreatesOneImprovement) -> 5f // District-type buildings, should be weighed by the stats (incl. adjacencies) of the improvement building.hasUnique(UniqueType.CreatesOneImprovement) -> 5f // District-type buildings, should be weighed by the stats (incl. adjacencies) of the improvement
building.hasUnique(UniqueType.ProvidesResources) -> 2f // Should be weighed by how much we need the resources building.hasUnique(UniqueType.ProvidesResources) -> 2f // Should be weighed by how much we need the resources
building.hasUnique(UniqueType.StatPercentFromObjectToResource) -> 1.5f // Should be weighed by the amount of active improvementFilter/buildingFilter in the city
else -> 0f else -> 0f
} }
return value return value

View File

@ -130,6 +130,7 @@ object ReligiousUnitAutomation {
return null return null
val holyCity = unit.civ.religionManager.getHolyCity() val holyCity = unit.civ.religionManager.getHolyCity()
// Our own holy city was taken over!
if (holyCity != null && holyCity.religion.getMajorityReligion() != unit.civ.religionManager.religion!!) if (holyCity != null && holyCity.religion.getMajorityReligion() != unit.civ.religionManager.religion!!)
return holyCity return holyCity
@ -137,12 +138,22 @@ object ReligiousUnitAutomation {
if (blockedHolyCity != null) if (blockedHolyCity != null)
return blockedHolyCity return blockedHolyCity
return unit.civ.cities.asSequence() // Find cities
.filter { it.religion.getMajorityReligion() != null } val relevantCities = unit.civ.gameInfo.getCities()
.filter { it.religion.getMajorityReligion()!! != unit.civ.religionManager.religion } .filter { it.getCenterTile().isExplored(unit.civ) } // Cities we know about
// Don't go if it takes too long // Someone else is controlling this city
.filter {
val majorityReligion = it.religion.getMajorityReligion()
majorityReligion != null && majorityReligion != unit.civ.religionManager.religion
}
val closeCity = relevantCities
.filter { it.getCenterTile().aerialDistanceTo(unit.currentTile) <= 20 } .filter { it.getCenterTile().aerialDistanceTo(unit.currentTile) <= 20 }
.maxByOrNull { it.religion.getPressureDeficit(unit.civ.religionManager.religion?.name) } // Find the city that we're the closest to converting
.minByOrNull { it.religion.getPressureDeficit(unit.civ.religionManager.religion?.name) }
if (closeCity != null) return closeCity
return relevantCities.minByOrNull { it.religion.getPressureDeficit(unit.civ.religionManager.religion?.name) }
} }

View File

@ -170,9 +170,9 @@ class City : IsPartOfGameInfoSerialization, INamed {
@Readonly fun getCenterTileOrNull(): Tile? = if (::centerTile.isInitialized) centerTile else null @Readonly fun getCenterTileOrNull(): Tile? = if (::centerTile.isInitialized) centerTile else null
@Readonly fun getTiles(): Sequence<Tile> = tiles.asSequence().map { tileMap[it] } @Readonly fun getTiles(): Sequence<Tile> = tiles.asSequence().map { tileMap[it] }
@Readonly fun getWorkableTiles() = tilesInRange.asSequence().filter { it.getOwner() == civ } @Readonly fun getWorkableTiles() = tilesInRange.asSequence().filter { it.getOwner() == civ }
@Readonly fun getWorkedTiles(): Sequence<Tile> = workedTiles.asSequence().map { tileMap[it] }
@Readonly fun isWorked(tile: Tile) = workedTiles.contains(tile.position) @Readonly fun isWorked(tile: Tile) = workedTiles.contains(tile.position)
@Readonly fun isCapital(): Boolean = cityConstructions.builtBuildingUniqueMap.hasUnique(UniqueType.IndicatesCapital, state) @Readonly fun isCapital(): Boolean = cityConstructions.builtBuildingUniqueMap.hasUnique(UniqueType.IndicatesCapital, state)
@Readonly fun isCoastal(): Boolean = centerTile.isCoastalTile() @Readonly fun isCoastal(): Boolean = centerTile.isCoastalTile()

View File

@ -1,12 +1,14 @@
package com.unciv.logic.city package com.unciv.logic.city
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.stats.Stat
import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.GameContext import com.unciv.models.ruleset.unique.GameContext
import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unique.UniqueType
import yairm210.purity.annotations.LocalState import yairm210.purity.annotations.LocalState
import yairm210.purity.annotations.Readonly import yairm210.purity.annotations.Readonly
import com.unciv.models.ruleset.unique.UniqueParameterType
object CityResources { object CityResources {
@ -14,7 +16,7 @@ object CityResources {
@Readonly @Readonly
fun getResourcesGeneratedByCity(city: City, resourceModifiers: Map<String, Float>): ResourceSupplyList { fun getResourcesGeneratedByCity(city: City, resourceModifiers: Map<String, Float>): ResourceSupplyList {
@LocalState val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifiers) @LocalState val cityResources = getResourcesGeneratedByCityNotIncludingBuildings(city, resourceModifiers)
cityResources += getCityResourcesGeneratedFromUniqueBuildings(city, resourceModifiers) cityResources += getCityResourcesGeneratedFromUniques(city, resourceModifiers, false)
return cityResources return cityResources
} }
@ -30,7 +32,7 @@ object CityResources {
// We can't use getResourcesGeneratedByCity directly, because that would include the resources generated by buildings - // We can't use getResourcesGeneratedByCity directly, because that would include the resources generated by buildings -
// which are part of the civ-wide uniques, so we'd be getting them twice! // which are part of the civ-wide uniques, so we'd be getting them twice!
// This way we get them once, but it is ugly, I welcome other ideas :/ // This way we get them once, but it is ugly, I welcome other ideas :/
cityResources.add(getCityResourcesFromCiv(city, resourceModifers)) cityResources.add(getCityResourcesGeneratedFromUniques(city, resourceModifers, true))
cityResources.removeAll { !it.resource.isCityWide } cityResources.removeAll { !it.resource.isCityWide }
@ -56,20 +58,6 @@ object CityResources {
return cityResources return cityResources
} }
@Readonly
private fun getCityResourcesGeneratedFromUniqueBuildings(city: City, resourceModifer: Map<String, Float>): ResourceSupplyList {
val buildingResources = ResourceSupplyList()
for (unique in city.getMatchingUniques(UniqueType.ProvidesResources, city.state, false)) { // E.G "Provides [1] [Iron]"
val resource = city.getRuleset().tileResources[unique.params[1]]
?: continue
buildingResources.add(
resource, unique.getSourceNameForUser(),
(unique.params[0].toFloat() * resourceModifer[resource.name]!!).toInt()
)
}
return buildingResources
}
/** Gets the number of resources available to this city /** Gets the number of resources available to this city
* Accommodates both city-wide and civ-wide resources */ * Accommodates both city-wide and civ-wide resources */
@Readonly @Readonly
@ -129,18 +117,46 @@ object CityResources {
} }
@Readonly @Readonly
private fun getCityResourcesFromCiv(city: City, resourceModifers: HashMap<String, Float>): ResourceSupplyList { private fun getCityResourcesGeneratedFromUniques(city: City, resourceModifers: Map<String, Float>, includeCivUniques: Boolean = true): ResourceSupplyList {
val resourceSupplyList = ResourceSupplyList() val buildingResources = ResourceSupplyList()
// This includes the uniques from buildings, from this and all other cities for (unique in city.getMatchingUniques(UniqueType.ProvidesResources, city.state, includeCivUniques)) { // E.G "Provides [1] [Iron]"
for (unique in city.getMatchingUniques(UniqueType.ProvidesResources, city.state)) { // E.G "Provides [1] [Iron]"
val resource = city.getRuleset().tileResources[unique.params[1]] val resource = city.getRuleset().tileResources[unique.params[1]]
?: continue ?: continue
resourceSupplyList.add( buildingResources.add(
resource, unique.getSourceNameForUser(), resource, unique.getSourceNameForUser(),
(unique.params[0].toFloat() * resourceModifers[resource.name]!!).toInt() (unique.params[0].toFloat() * resourceModifers[resource.name]!!).toInt()
) )
} }
return resourceSupplyList
// StatPercentFromObjectToResource - Example: "[50]% of [Culture] from every [improvementFilter/buildingFilter] in the city added to [Iron]"
for (unique in city.getMatchingUniques(UniqueType.StatPercentFromObjectToResource, city.state, includeCivUniques)) {
val resource = city.getRuleset().tileResources[unique.params[3]] ?: continue
val stat = Stat.safeValueOf(unique.params[1]) ?: continue
val filter = unique.params[2]
var amount = 0.0
// Building Filter
if (UniqueParameterType.BuildingFilter.isKnownValue(filter, city.getRuleset())) {
amount += city.cityConstructions.getBuiltBuildings()
.filter { it.isStatRelated(stat) && it.matchesFilter(filter, city.state) }
.sumOf { it.getStats(city)[stat].toDouble() }
}
// Improvement Filter
if (UniqueParameterType.ImprovementFilter.isKnownValue(filter, city.getRuleset())) {
amount += city.getWorkedTiles()
.mapNotNull { it.getUnpillagedTileImprovement() }
.filter { it[stat] > 0f && it.matchesFilter(filter, city.state) }
.sumOf { it[stat].toDouble() }
}
if (amount > 0.0) {
amount *= unique.params[0].toDouble() / 100.0 * (resourceModifers[resource.name] ?: 1f).toDouble()
buildingResources.add(resource, unique.getSourceNameForUser(), amount.toInt())
}
}
return buildingResources
} }
@Readonly @Readonly

View File

@ -330,7 +330,8 @@ class CityReligionManager : IsPartOfGameInfoSerialization {
return pressure.toInt() return pressure.toInt()
} }
/** Calculates how much pressure this religion is lacking compared to the majority religion */ /** Calculates how much pressure this religion is lacking compared to the majority religion
* That is, if we gain more than this, we'll be the majority */
@Readonly @Readonly
fun getPressureDeficit(otherReligion: String?): Int { fun getPressureDeficit(otherReligion: String?): Int {
val pressures = getPressures() val pressures = getPressures()

View File

@ -267,13 +267,8 @@ class UnitMovement(val unit: MapUnit) {
* @return The tile that we reached this turn * @return The tile that we reached this turn
*/ */
fun headTowards(destination: Tile): Tile { fun headTowards(destination: Tile): Tile {
val escortUnit = if (unit.isEscorting()) unit.getOtherEscortUnit() else null
val startTile = unit.getTile()
val destinationTileThisTurn = getTileToMoveToThisTurn(destination) val destinationTileThisTurn = getTileToMoveToThisTurn(destination)
moveToTile(destinationTileThisTurn) moveToTile(destinationTileThisTurn)
if (startTile != unit.getTile() && escortUnit != null) {
escortUnit.movement.headTowards(unit.getTile())
}
return unit.currentTile return unit.currentTile
} }
@ -748,10 +743,6 @@ class UnitMovement(val unit: MapUnit) {
movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap(), movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap(),
includeOtherEscortUnit: Boolean = true includeOtherEscortUnit: Boolean = true
): PathsToTilesWithinTurn { ): PathsToTilesWithinTurn {
// val cacheResults = pathfindingCache.getDistanceToTiles(considerZoneOfControl)
// if (cacheResults != null) {
// return cacheResults
// }
val distanceToTiles = getMovementToTilesAtPosition( val distanceToTiles = getMovementToTilesAtPosition(
unit.currentTile.position, unit.currentTile.position,
unit.currentMovement, unit.currentMovement,
@ -762,8 +753,6 @@ class UnitMovement(val unit: MapUnit) {
includeOtherEscortUnit includeOtherEscortUnit
) )
pathfindingCache.setDistanceToTiles(considerZoneOfControl, distanceToTiles)
return distanceToTiles return distanceToTiles
} }
@ -838,14 +827,13 @@ class UnitMovement(val unit: MapUnit) {
class PathfindingCache(private val unit: MapUnit) { class PathfindingCache(private val unit: MapUnit) {
private var shortestPathCache = listOf<Tile>() private var shortestPathCache = listOf<Tile>()
private var destination: Tile? = null private var destination: Tile? = null
private val distanceToTilesCache = mutableMapOf<Boolean, PathsToTilesWithinTurn>()
private var movement = -1f private var movement = -1f
private var currentTile: Tile? = null private var currentTile: Tile? = null
/** Check if the caches are valid (only checking if the unit has moved or consumed movement points; /** Check if the caches are valid (only checking if the unit has moved or consumed movement points;
* the isPlayerCivilization check is performed in the functions because we want isValid() == false * the isPlayerCivilization check is performed in the functions because we want isValid() == false
* to have a specific behavior) */ * to have a specific behavior) */
private fun isValid(): Boolean = (movement == unit.currentMovement) && (unit.getTile() == currentTile) @Readonly private fun isValid(): Boolean = (movement == unit.currentMovement) && (unit.getTile() == currentTile)
fun getShortestPathCache(destination: Tile): List<Tile> { fun getShortestPathCache(destination: Tile): List<Tile> {
if (unit.civ.isHuman()) return listOf() if (unit.civ.isHuman()) return listOf()
@ -863,23 +851,7 @@ class PathfindingCache(private val unit: MapUnit) {
} }
} }
fun getDistanceToTiles(zoneOfControl: Boolean): PathsToTilesWithinTurn? {
if (unit.civ.isHuman()) return null
if (isValid())
return distanceToTilesCache[zoneOfControl]
return null
}
fun setDistanceToTiles(zoneOfControl: Boolean, paths: PathsToTilesWithinTurn) {
if (unit.civ.isHuman()) return
if (!isValid()) {
clear() // we want to reset the entire cache at this point
}
distanceToTilesCache[zoneOfControl] = paths
}
fun clear() { fun clear() {
distanceToTilesCache.clear()
movement = unit.currentMovement movement = unit.currentMovement
currentTile = unit.getTile() currentTile = unit.getTile()
destination = null destination = null

View File

@ -143,7 +143,11 @@ class TileResource : RulesetStatsObject(), GameResource {
val buildingsThatProvideThis = ruleset.buildings.values val buildingsThatProvideThis = ruleset.buildings.values
.filter { building -> .filter { building ->
building.uniqueObjects.any { unique -> building.uniqueObjects.any { unique ->
unique.type == UniqueType.ProvidesResources && unique.params[1] == name when (unique.type) {
UniqueType.ProvidesResources -> unique.params[1] == name
UniqueType.StatPercentFromObjectToResource -> unique.params[3] == name
else -> false
}
} }
} }
if (buildingsThatProvideThis.isNotEmpty()) { if (buildingsThatProvideThis.isNotEmpty()) {

View File

@ -506,7 +506,7 @@ enum class UniqueParameterType(
/**Used by [UniqueType.ConditionalCityReligion]*/ /**Used by [UniqueType.ConditionalCityReligion]*/
ReligionFilter("religionFilter", "major") { ReligionFilter("religionFilter", "major") {
override val staticKnownValues = setOf("any", "major", "enhanced", "your", "foreign","enemy") override val staticKnownValues = setOf("any", "major", "enhanced", "your", "foreign", "enemy")
override fun isKnownValue(parameterText: String, ruleset: Ruleset): Boolean { override fun isKnownValue(parameterText: String, ruleset: Ruleset): Boolean {
return when (parameterText) { return when (parameterText) {
in staticKnownValues -> true in staticKnownValues -> true

View File

@ -102,7 +102,7 @@ object UniqueTriggerActivation {
if (timingConditional != null) { if (timingConditional != null) {
return { return {
civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt())) civInfo.temporaryUniques.add(TemporaryUnique(unique, timingConditional.params[0].toInt()))
if (unique.type in setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources)) if (unique.type in setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources, UniqueType.StatPercentFromObjectToResource))
civInfo.cache.updateCivResources() civInfo.cache.updateCivResources()
true true
} }

View File

@ -47,6 +47,7 @@ enum class UniqueType(
StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentBonusCities("[relativeAmount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentBonusCities("[relativeAmount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentFromObject("[relativeAmount]% [stat] from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), StatPercentFromObject("[relativeAmount]% [stat] from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentFromObjectToResource("[positiveAmount]% of [stat] from every [improvementFilter/buildingFilter] in the city added to [resource]", UniqueTarget.Building),
AllStatsPercentFromObject("[relativeAmount]% Yield from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), AllStatsPercentFromObject("[relativeAmount]% Yield from every [tileFilter/buildingFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatPercentFromReligionFollowers("[relativeAmount]% [stat] from every follower, up to [relativeAmount]%", UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief), StatPercentFromReligionFollowers("[relativeAmount]% [stat] from every follower, up to [relativeAmount]%", UniqueTarget.FollowerBelief, UniqueTarget.FounderBelief),
BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", UniqueTarget.Global), BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", UniqueTarget.Global),

View File

@ -183,7 +183,7 @@ class UniqueValidator(val ruleset: Ruleset) {
} }
private val resourceUniques = setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources, private val resourceUniques = setOf(UniqueType.ProvidesResources, UniqueType.ConsumesResources,
UniqueType.PercentResourceProduction) UniqueType.PercentResourceProduction, UniqueType.StatPercentFromObjectToResource)
private val resourceConditionals = setOf( private val resourceConditionals = setOf(
UniqueType.ConditionalWithResource, UniqueType.ConditionalWithResource,
UniqueType.ConditionalWithoutResource, UniqueType.ConditionalWithoutResource,

View File

@ -46,7 +46,7 @@ Allows filtering for specific nations. Used by [ModOptions.nationsToRemove](Mod-
Allowed values: Allowed values:
- `All` - `All`, `all`
- `City-States`, `City-State` - `City-States`, `City-State`
- `Major` - `Major`
- Nation name - Nation name
@ -68,11 +68,11 @@ Allowed values:
- `non-air` for non-air non-missile units - `non-air` for non-air non-missile units
- `Military`, `military units` - `Military`, `military units`
- `Civilian`, `civilian units` - `Civilian`, `civilian units`
- `All` - `All`, `all`
- `Melee` - `Melee`
- `Ranged` - `Ranged`
- `Nuclear Weapon` - `Nuclear Weapon`
- `Great Person`, `Great` - `Great Person`
- `Embarked` - `Embarked`
- Matching [technologyfilter](#technologyfilter) for the tech this unit requires - e.g. `Modern Era` - Matching [technologyfilter](#technologyfilter) for the tech this unit requires - e.g. `Modern Era`
- Any exact unique the unit has - Any exact unique the unit has
@ -88,11 +88,12 @@ Allowed values:
- Any matching [baseUnitFilter](#baseunitfilter) - Any matching [baseUnitFilter](#baseunitfilter)
- Any [civFilter](#civfilter) matching the owner - Any [civFilter](#civfilter) matching the owner
- Any unique the unit has - also includes uniques not caught by the [baseUnitFilter](#baseunitfilter), for example promotions - Any unique the unit has - also includes uniques not caught by the [baseUnitFilter](#baseunitfilter), for example promotions
- Any promotion name - Any promotion name, or an exact unique a promotion has
- `Wounded` - `Wounded`
- `Embarked` - `Embarked`
- `City-State` - `City-State`
- `Barbarians`, `Barbarian` - `Barbarians`, `Barbarian`
- `Non-City`
- Again, any combination of the above is also allowed, e.g. `[{Wounded} {Water}]` units. - Again, any combination of the above is also allowed, e.g. `[{Wounded} {Water}]` units.
You can check this in-game using the console with the `unit checkfilter <filter>` command You can check this in-game using the console with the `unit checkfilter <filter>` command
@ -103,7 +104,7 @@ Allows to only activate a unique for certain buildings.
Allowed values: Allowed values:
- `All` - `All`, `all`
- `Buildings`, `Building` - `Buildings`, `Building`
- `Wonder`, `Wonders` - `Wonder`, `Wonders`
- `National Wonder`, `National` - `National Wonder`, `National`
@ -124,7 +125,7 @@ Allowed values:
cityFilters allow us to choose the range of cities affected by this unique: cityFilters allow us to choose the range of cities affected by this unique:
- `in this city` - `in this city`
- `in all cities` - `in all cities`, `All`, `all` - Generally applies to all cities owned by the relevant civ
- `in your cities`, `Your` - `in your cities`, `Your`
- `in all coastal cities`, `Coastal` - `in all coastal cities`, `Coastal`
- `in capital`, `Capital` - `in capital`, `Capital`
@ -137,6 +138,7 @@ cityFilters allow us to choose the range of cities affected by this unique:
- `in foreign cities`, `Foreign` - `in foreign cities`, `Foreign`
- `in annexed cities`, `Annexed` - `in annexed cities`, `Annexed`
- `in puppeted cities`, `Puppeted` - `in puppeted cities`, `Puppeted`
- `in resisting cities`, `Resisting`
- `in cities being razed`, `Razing` - `in cities being razed`, `Razing`
- `in holy cities`, `Holy` - `in holy cities`, `Holy`
- `in City-State cities` - `in City-State cities`
@ -155,8 +157,10 @@ For filtering a specific improvement.
Allowed values: Allowed values:
- improvement name - improvement name
- `All` - An exact unique the improvement has (e.g.: `spaceship improvement`)
- `Great Improvements`, `Great` - `Improvement`
- `All`, `all`
- `Great Improvement`, `Great`
- `All Road` - for Roads & Railroads - `All Road` - for Roads & Railroads
## populationFilter ## populationFilter
@ -189,7 +193,7 @@ For filtering specific relgions
Allowed values: Allowed values:
- `All` or `all` - `All`, `all`
- `[policyBranchName] branch` - `[policyBranchName] branch`
- The name of the policy - The name of the policy
- A unique the Policy has (verbatim, no placeholders) - A unique the Policy has (verbatim, no placeholders)
@ -238,7 +242,8 @@ These can be strung together with ", " between them, for example: `+2 Production
Allowed values: Allowed values:
- Resource name - Resource name
- `any`, `all` - `any`
- `All`, `all`
- Resource type: `Strategic`, `Luxury`, `Bonus` - Resource type: `Strategic`, `Luxury`, `Bonus`
- Stat provided by the resource when improved (e.g. `Food`) - Stat provided by the resource when improved (e.g. `Food`)
@ -270,7 +275,7 @@ At the moment only implemented for [ModOptions.techsToRemove](Mod-file-structure
Allowed values: Allowed values:
- `All` - `All`, `all`
- The name of an Era - The name of an Era
- The name of a Technology - The name of a Technology
- A unique a Technology has (verbatim, no placeholders) - A unique a Technology has (verbatim, no placeholders)
@ -290,19 +295,23 @@ Allowed values:
- Natural wonder - Natural wonder
- A [nationFilter](#nationfilter) matching the tile owner - A [nationFilter](#nationfilter) matching the tile owner
- Or the filter is a constant string choosing a derived test: - Or the filter is a constant string choosing a derived test:
- `All` - `All`, `all`
- `Terrain` - `Terrain`
- `Water`, `Land` - `Water`, `Land`
- `Coastal` (at least one direct neighbor is a coast) - `Coastal` (at least one direct neighbor is a coast)
- `River` (as in all 'river on tile' contexts, it means 'adjacent to a river on at least one side') - `River` (as in all 'river on tile' contexts, it means 'adjacent to a river on at least one side')
- `Open terrain`, `Rough terrain` (note all terrain not having the rough unique is counted as open) - `Open terrain`, `Rough terrain` (note all terrain not having the rough unique is counted as open)
- `Friendly Land` - land belonging to you, or other civs with open borders to you - `Friendly Land`, `Friendly` - land belonging to you, or other civs with open borders to you
- `Foreign Land` - any land that isn't friendly land - `Foreign Land` - any land that isn't friendly land
- `Enemy Land` - any land belonging to a civ you are at war with - `Enemy Land`, `Enemy` - any land belonging to a civ you are at war with
- `your` - land belonging to you - `your` - land belonging to you
- `unowned` - land that is not owned by any civ - `Unowned` - land that is not owned by any civ
- `Water resource`, `Strategic resource`, `Luxury resource`, `Bonus resource`, `resource` - `Water resource`, `Strategic resource`, `Luxury resource`, `Bonus resource`, `resource`
- `Natural Wonder` (as opposed to above which means testing for a specific Natural Wonder by name, this tests for any of them) - `Natural Wonder` (as opposed to above which means testing for a specific Natural Wonder by name, this tests for any of them)
- `Featureless`
- `Fresh Water`
- `non-fresh water`
- `Impassible`
Please note all of these are _case-sensitive_. Please note all of these are _case-sensitive_.
@ -316,6 +325,7 @@ Allowed values:
- [terrainFilter](#terrainfilter) for this tile - [terrainFilter](#terrainfilter) for this tile
- [improvementFilter](#improvementfilter) for this tile - [improvementFilter](#improvementfilter) for this tile
- [civFilter](#civfilter) of the civilization who owns this tile
- `Improvement` or `improved` for tiles with any improvements - `Improvement` or `improved` for tiles with any improvements
- `unimproved` for tiles with no improvement - `unimproved` for tiles with no improvement
- `pillaged` for pillaged tiles - `pillaged` for pillaged tiles

View File

@ -1523,6 +1523,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, Difficulty, EventChoice Applicable to: Nation, Tech, Policy, FounderBelief, FollowerBelief, Building, Unit, UnitType, Promotion, Terrain, Improvement, Resource, Ruins, Speed, Difficulty, EventChoice
## Building uniques ## Building uniques
??? example "[positiveAmount]% of [stat] from every [improvementFilter/buildingFilter] in the city added to [resource]"
Example: "[3]% of [Culture] from every [All Road] in the city added to [Iron]"
Applicable to: Building
??? example "Consumes [amount] [resource]" ??? example "Consumes [amount] [resource]"
Example: "Consumes [3] [Iron]" Example: "Consumes [3] [Iron]"

View File

@ -166,6 +166,26 @@ class ResourceTests {
assertEquals("4 Iron from Buildings", resources[0].toString()) assertEquals("4 Iron from Buildings", resources[0].toString())
} }
@Test
fun `should handle StatPercentFromObjectToResource with a buildingFilter`() {
city.cityConstructions.addBuilding("Monument")
var building = game.createBuilding("[300]% of [Culture] from every [Monument] in the city added to [Iron]")
city.cityConstructions.addBuilding(building)
assertEquals(6, city.getAvailableResourceAmount("Iron")) // 2 Culture * 3
}
@Test
fun `should handle StatPercentFromObjectToResource with a improvementFilter`() {
val tile = game.tileMap[1,1]
tile.resource = "Wheat"
tile.resourceAmount = 1
tile.setImprovement("Farm")
city.population.addPopulation(5) // Add population, since the tile needs to be worked
var building = game.createBuilding("[300]% of [Food] from every [Farm] in the city added to [Iron]")
city.cityConstructions.addBuilding(building)
assertEquals(3, city.getAvailableResourceAmount("Iron"))
}
@Test @Test
fun `should reduce resources due to buildings`() { fun `should reduce resources due to buildings`() {
// given // given