mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-12 00:39:56 +07:00
Added more unit tests for uniques; added missing unique implementations (#6886)
* Added more unit tests for uniques; added missing implementations * This of course shouldn't go here as there is another function for it * Stylistic changes * This generates better unique examples * Reviews * Reordered for efficiency * Reverted improvement percentage bonuses applying to tiles
This commit is contained in:
@ -539,6 +539,7 @@ class CityStats(val cityInfo: CityInfo) {
|
|||||||
entry.gold *= statPercentBonusesSum.gold.toPercent()
|
entry.gold *= statPercentBonusesSum.gold.toPercent()
|
||||||
entry.culture *= statPercentBonusesSum.culture.toPercent()
|
entry.culture *= statPercentBonusesSum.culture.toPercent()
|
||||||
entry.food *= statPercentBonusesSum.food.toPercent()
|
entry.food *= statPercentBonusesSum.food.toPercent()
|
||||||
|
entry.faith *= statPercentBonusesSum.faith.toPercent()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AFTER we've gotten all the gold stats figured out, only THEN do we plonk that gold into Science
|
// AFTER we've gotten all the gold stats figured out, only THEN do we plonk that gold into Science
|
||||||
|
@ -8,11 +8,9 @@ import com.unciv.logic.city.CityInfo
|
|||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.civilization.PlayerType
|
import com.unciv.logic.civilization.PlayerType
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
|
||||||
import com.unciv.models.ruleset.tile.*
|
import com.unciv.models.ruleset.tile.*
|
||||||
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
import com.unciv.models.ruleset.unique.*
|
||||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.ruleset.unique.Unique
|
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.civilopedia.FormattedLine
|
import com.unciv.ui.civilopedia.FormattedLine
|
||||||
@ -259,13 +257,13 @@ open class TileInfo {
|
|||||||
fun getTerrainMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this) ): Sequence<Unique> {
|
fun getTerrainMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this) ): Sequence<Unique> {
|
||||||
return getAllTerrains().flatMap { it.getMatchingUniques(uniqueType, stateForConditionals) }
|
return getAllTerrains().flatMap { it.getMatchingUniques(uniqueType, stateForConditionals) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get all uniques of this type that any part of this tile has: terrains, improvement, resource */
|
/** Get all uniques of this type that any part of this tile has: terrains, improvement, resource */
|
||||||
fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this)) =
|
fun getMatchingUniques(uniqueType: UniqueType, stateForConditionals: StateForConditionals = StateForConditionals(tile=this)) =
|
||||||
getTerrainMatchingUniques(uniqueType, stateForConditionals) +
|
getTerrainMatchingUniques(uniqueType, stateForConditionals) +
|
||||||
(getTileImprovement()?.getMatchingUniques(uniqueType, stateForConditionals) ?: sequenceOf()) +
|
(getTileImprovement()?.getMatchingUniques(uniqueType, stateForConditionals) ?: sequenceOf()) +
|
||||||
if (resource == null) sequenceOf() else tileResource.getMatchingUniques(uniqueType, stateForConditionals)
|
if (resource == null) sequenceOf() else tileResource.getMatchingUniques(uniqueType, stateForConditionals)
|
||||||
|
|
||||||
fun getWorkingCity(): CityInfo? {
|
fun getWorkingCity(): CityInfo? {
|
||||||
val civInfo = getOwner() ?: return null
|
val civInfo = getOwner() ?: return null
|
||||||
return civInfo.cities.firstOrNull { it.isWorked(this) }
|
return civInfo.cities.firstOrNull { it.isWorked(this) }
|
||||||
@ -319,22 +317,26 @@ open class TileInfo {
|
|||||||
tileUniques += city.getMatchingUniques(UniqueType.StatsFromObject, stateForConditionals)
|
tileUniques += city.getMatchingUniques(UniqueType.StatsFromObject, stateForConditionals)
|
||||||
for (unique in localUniqueCache.get("StatsFromTilesAndObjects", tileUniques)) {
|
for (unique in localUniqueCache.get("StatsFromTilesAndObjects", tileUniques)) {
|
||||||
val tileType = unique.params[1]
|
val tileType = unique.params[1]
|
||||||
if (tileType == improvement) continue // This is added to the calculation in getImprovementStats. we don't want to add it twice
|
if (!matchesTerrainFilter(tileType, observingCiv)) continue
|
||||||
if (matchesTerrainFilter(tileType, observingCiv))
|
stats.add(unique.stats)
|
||||||
stats.add(unique.stats)
|
if (naturalWonder != null
|
||||||
if (tileType == "Natural Wonder" && naturalWonder != null && city.civInfo.hasUnique(UniqueType.DoubleStatsFromNaturalWonders)) {
|
&& tileType == "Natural Wonder"
|
||||||
|
&& city.civInfo.hasUnique(UniqueType.DoubleStatsFromNaturalWonders)
|
||||||
|
) {
|
||||||
stats.add(unique.stats)
|
stats.add(unique.stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unique in localUniqueCache.get("StatsFromTilesWithout",
|
for (unique in localUniqueCache.get("StatsFromTilesWithout",
|
||||||
city.getMatchingUniques(UniqueType.StatsFromTilesWithout, stateForConditionals)))
|
city.getMatchingUniques(UniqueType.StatsFromTilesWithout, stateForConditionals))
|
||||||
|
) {
|
||||||
if (
|
if (
|
||||||
matchesTerrainFilter(unique.params[1]) &&
|
matchesTerrainFilter(unique.params[1]) &&
|
||||||
!matchesTerrainFilter(unique.params[2]) &&
|
!matchesTerrainFilter(unique.params[2]) &&
|
||||||
city.matchesFilter(unique.params[3])
|
city.matchesFilter(unique.params[3])
|
||||||
)
|
)
|
||||||
stats.add(unique.stats)
|
stats.add(unique.stats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdjacentToRiver()) stats.gold++
|
if (isAdjacentToRiver()) stats.gold++
|
||||||
@ -347,17 +349,61 @@ open class TileInfo {
|
|||||||
if (improvement != null)
|
if (improvement != null)
|
||||||
stats.add(getImprovementStats(improvement, observingCiv, city))
|
stats.add(getImprovementStats(improvement, observingCiv, city))
|
||||||
|
|
||||||
if (isCityCenter()) {
|
|
||||||
if (stats.food < 2) stats.food = 2f
|
|
||||||
if (stats.production < 1) stats.production = 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
|
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
|
||||||
stats.gold++
|
stats.gold++
|
||||||
}
|
}
|
||||||
|
if (isCityCenter()) {
|
||||||
|
if (stats.food < 2) stats.food = 2f
|
||||||
|
if (stats.production < 1) stats.production = 1f
|
||||||
|
}
|
||||||
|
|
||||||
for ((stat, value) in stats)
|
for ((stat, value) in stats)
|
||||||
if (value < 0f) stats[stat] = 0f
|
if (value < 0f) stats[stat] = 0f
|
||||||
|
|
||||||
|
for ((stat, value) in getTilePercentageStats(observingCiv, city)) {
|
||||||
|
stats[stat] *= value.toPercent()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only gets the tile percentage bonus, not the improvement percentage bonus
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
fun getTilePercentageStats(observingCiv: CivilizationInfo?, city: CityInfo?): Stats {
|
||||||
|
val stats = Stats()
|
||||||
|
val stateForConditionals = StateForConditionals(civInfo = observingCiv, cityInfo = city, tile = this)
|
||||||
|
|
||||||
|
if (city != null) {
|
||||||
|
for (unique in city.getMatchingUniques(UniqueType.StatPercentFromObject, stateForConditionals)) {
|
||||||
|
val tileFilter = unique.params[2]
|
||||||
|
if (matchesTerrainFilter(tileFilter, observingCiv))
|
||||||
|
stats[Stat.valueOf(unique.params[1])] += unique.params[0].toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unique in city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, stateForConditionals)) {
|
||||||
|
val tileFilter = unique.params[1]
|
||||||
|
if (!matchesTerrainFilter(tileFilter, observingCiv)) continue
|
||||||
|
val statPercentage = unique.params[0].toFloat()
|
||||||
|
for (stat in Stat.values())
|
||||||
|
stats[stat] += statPercentage
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (observingCiv != null) {
|
||||||
|
for (unique in observingCiv.getMatchingUniques(UniqueType.StatPercentFromObject, stateForConditionals)) {
|
||||||
|
val tileFilter = unique.params[2]
|
||||||
|
if (matchesTerrainFilter(tileFilter, observingCiv))
|
||||||
|
stats[Stat.valueOf(unique.params[1])] += unique.params[0].toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unique in observingCiv.getMatchingUniques(UniqueType.AllStatsPercentFromObject, stateForConditionals)) {
|
||||||
|
val tileFilter = unique.params[1]
|
||||||
|
if (!matchesTerrainFilter(tileFilter, observingCiv)) continue
|
||||||
|
val statPercentage = unique.params[0].toFloat()
|
||||||
|
for (stat in Stat.values())
|
||||||
|
stats[stat] += statPercentage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,6 +467,7 @@ open class TileInfo {
|
|||||||
return fertility
|
return fertility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also multiplies the stats by the percentage bonus for improvements (but not for tiles)
|
||||||
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
|
fun getImprovementStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
|
||||||
val stats = improvement.cloneStats()
|
val stats = improvement.cloneStats()
|
||||||
if (hasViewableResource(observingCiv) && tileResource.isImprovedBy(improvement.name)
|
if (hasViewableResource(observingCiv) && tileResource.isImprovedBy(improvement.name)
|
||||||
@ -464,17 +511,49 @@ open class TileInfo {
|
|||||||
stats.add(unique.stats)
|
stats.add(unique.stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unique in city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
|
|
||||||
if (improvement.matchesFilter(unique.params[1]))
|
|
||||||
stats.timesInPlace(unique.params[0].toPercent())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (city == null) { // As otherwise we already got this above
|
for ((stat, value) in getImprovementPercentageStats(improvement, observingCiv, city)) {
|
||||||
|
stats[stat] *= value.toPercent()
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
fun getImprovementPercentageStats(improvement: TileImprovement, observingCiv: CivilizationInfo, city: CityInfo?): Stats {
|
||||||
|
val stats = Stats()
|
||||||
|
val conditionalState = StateForConditionals(civInfo = observingCiv, cityInfo = city, tile = this)
|
||||||
|
|
||||||
|
// I would love to make an interface 'canCallMatchingUniques'
|
||||||
|
// from which both cityInfo and CivilizationInfo derive, so I don't have to duplicate all this code
|
||||||
|
// But something something too much for this PR.
|
||||||
|
|
||||||
|
if (city != null) {
|
||||||
|
for (unique in city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
|
||||||
|
if (!improvement.matchesFilter(unique.params[1])) continue
|
||||||
|
for (stat in Stat.values()) {
|
||||||
|
stats[stat] += unique.params[0].toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unique in city.getMatchingUniques(UniqueType.StatPercentFromObject, conditionalState)) {
|
||||||
|
if (!improvement.matchesFilter(unique.params[2])) continue
|
||||||
|
val stat = Stat.valueOf(unique.params[1])
|
||||||
|
stats[stat] += unique.params[0].toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
for (unique in observingCiv.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
|
for (unique in observingCiv.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
|
||||||
if (improvement.matchesFilter(unique.params[1]))
|
if (!improvement.matchesFilter(unique.params[1])) continue
|
||||||
stats.timesInPlace(unique.params[0].toPercent())
|
for (stat in Stat.values()) {
|
||||||
|
stats[stat] += unique.params[0].toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (unique in observingCiv.getMatchingUniques(UniqueType.StatPercentFromObject, conditionalState)) {
|
||||||
|
if (!improvement.matchesFilter(unique.params[2])) continue
|
||||||
|
val stat = Stat.valueOf(unique.params[1])
|
||||||
|
stats[stat] += unique.params[0].toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,10 +567,10 @@ open class TileInfo {
|
|||||||
if (terrainFilter == Constants.freshWater && isAdjacentToRiver()) return true
|
if (terrainFilter == Constants.freshWater && isAdjacentToRiver()) return true
|
||||||
return (neighbors + this).any { neighbor -> neighbor.matchesFilter(terrainFilter) }
|
return (neighbors + this).any { neighbor -> neighbor.matchesFilter(terrainFilter) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if the [improvement] can be built on this [TileInfo] */
|
/** Returns true if the [improvement] can be built on this [TileInfo] */
|
||||||
fun canBuildImprovement(improvement: TileImprovement, civInfo: CivilizationInfo): Boolean {
|
fun canBuildImprovement(improvement: TileImprovement, civInfo: CivilizationInfo): Boolean {
|
||||||
|
|
||||||
val stateForConditionals = StateForConditionals(civInfo, tile=this)
|
val stateForConditionals = StateForConditionals(civInfo, tile=this)
|
||||||
val knownFeatureRemovals = ruleset.tileImprovements.values
|
val knownFeatureRemovals = ruleset.tileImprovements.values
|
||||||
.filter { rulesetImprovement ->
|
.filter { rulesetImprovement ->
|
||||||
@ -523,7 +602,7 @@ open class TileInfo {
|
|||||||
else -> canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo), knownFeatureRemovals, stateForConditionals)
|
else -> canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo), knownFeatureRemovals, stateForConditionals)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Without regards to what CivInfo it is, a lot of the checks are just for the improvement on the tile.
|
/** Without regards to what CivInfo it is, a lot of the checks are just for the improvement on the tile.
|
||||||
* Doubles as a check for the map editor.
|
* Doubles as a check for the map editor.
|
||||||
*/
|
*/
|
||||||
@ -551,13 +630,13 @@ open class TileInfo {
|
|||||||
clonedTile.removeTerrainFeature(topTerrain.name)
|
clonedTile.removeTerrainFeature(topTerrain.name)
|
||||||
return clonedTile.canImprovementBeBuiltHere(improvement, resourceIsVisible, knownFeatureRemovals, stateForConditionals)
|
return clonedTile.canImprovementBeBuiltHere(improvement, resourceIsVisible, knownFeatureRemovals, stateForConditionals)
|
||||||
}
|
}
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
improvement.name == this.improvement -> false
|
improvement.name == this.improvement -> false
|
||||||
isCityCenter() -> false
|
isCityCenter() -> false
|
||||||
|
|
||||||
// First we handle a few special improvements
|
// First we handle a few special improvements
|
||||||
|
|
||||||
// Can only cancel if there is actually an improvement being built
|
// Can only cancel if there is actually an improvement being built
|
||||||
improvement.name == Constants.cancelImprovementOrder -> (this.improvementInProgress != null)
|
improvement.name == Constants.cancelImprovementOrder -> (this.improvementInProgress != null)
|
||||||
// Can only remove roads if that road is actually there
|
// Can only remove roads if that road is actually there
|
||||||
@ -566,7 +645,7 @@ open class TileInfo {
|
|||||||
improvement.name.startsWith(Constants.remove) -> terrainFeatures.any { it == improvement.name.removePrefix(Constants.remove) }
|
improvement.name.startsWith(Constants.remove) -> terrainFeatures.any { it == improvement.name.removePrefix(Constants.remove) }
|
||||||
// Can only build roads if on land and they are better than the current road
|
// Can only build roads if on land and they are better than the current road
|
||||||
RoadStatus.values().any { it.name == improvement.name } -> !isWater && RoadStatus.valueOf(improvement.name) > roadStatus
|
RoadStatus.values().any { it.name == improvement.name } -> !isWater && RoadStatus.valueOf(improvement.name) > roadStatus
|
||||||
|
|
||||||
// Then we check if there is any reason to not allow this improvement to be build
|
// Then we check if there is any reason to not allow this improvement to be build
|
||||||
|
|
||||||
// Can't build if there is already an irremovable improvement here
|
// Can't build if there is already an irremovable improvement here
|
||||||
@ -574,7 +653,7 @@ open class TileInfo {
|
|||||||
|
|
||||||
// Can't build if this terrain is unbuildable, except when we are specifically allowed to
|
// Can't build if this terrain is unbuildable, except when we are specifically allowed to
|
||||||
getLastTerrain().unbuildable && !improvement.canBeBuildOnThisUnbuildableTerrain(knownFeatureRemovals) -> false
|
getLastTerrain().unbuildable && !improvement.canBeBuildOnThisUnbuildableTerrain(knownFeatureRemovals) -> false
|
||||||
|
|
||||||
// Can't build if any terrain specifically prevents building this improvement
|
// Can't build if any terrain specifically prevents building this improvement
|
||||||
getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any {
|
getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any {
|
||||||
unique -> !improvement.matchesFilter(unique.params[0])
|
unique -> !improvement.matchesFilter(unique.params[0])
|
||||||
@ -602,20 +681,20 @@ open class TileInfo {
|
|||||||
) -> false
|
) -> false
|
||||||
|
|
||||||
// At this point we know this is a normal improvement and that there is no reason not to allow it to be built.
|
// At this point we know this is a normal improvement and that there is no reason not to allow it to be built.
|
||||||
|
|
||||||
// Lastly we check if the improvement may be built on this terrain or resource
|
// Lastly we check if the improvement may be built on this terrain or resource
|
||||||
improvement.canBeBuiltOn(getLastTerrain().name) -> true
|
improvement.canBeBuiltOn(getLastTerrain().name) -> true
|
||||||
isLand && improvement.canBeBuiltOn("Land") -> true
|
isLand && improvement.canBeBuiltOn("Land") -> true
|
||||||
isWater && improvement.canBeBuiltOn("Water") -> true
|
isWater && improvement.canBeBuiltOn("Water") -> true
|
||||||
// DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests.
|
// DO NOT reverse this &&. isAdjacentToFreshwater() is a lazy which calls a function, and reversing it breaks the tests.
|
||||||
improvement.hasUnique(UniqueType.ImprovementBuildableByFreshWater, stateForConditionals)
|
improvement.hasUnique(UniqueType.ImprovementBuildableByFreshWater, stateForConditionals)
|
||||||
&& isAdjacentTo(Constants.freshWater) -> true
|
&& isAdjacentTo(Constants.freshWater) -> true
|
||||||
|
|
||||||
// I don't particularly like this check, but it is required to build mines on non-hill resources
|
// I don't particularly like this check, but it is required to build mines on non-hill resources
|
||||||
resourceIsVisible && tileResource.isImprovedBy(improvement.name) -> true
|
resourceIsVisible && tileResource.isImprovedBy(improvement.name) -> true
|
||||||
// DEPRECATED since 4.0.14, REMOVE SOON:
|
// DEPRECATED since 4.0.14, REMOVE SOON:
|
||||||
isLand && improvement.terrainsCanBeBuiltOn.isEmpty() && !improvement.hasUnique(UniqueType.CanOnlyImproveResource) -> true
|
isLand && improvement.terrainsCanBeBuiltOn.isEmpty() && !improvement.hasUnique(UniqueType.CanOnlyImproveResource) -> true
|
||||||
// No reason this improvement should be built here, so can't build it
|
// No reason this improvement should be built here, so can't build it
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -722,7 +801,7 @@ open class TileInfo {
|
|||||||
if (isWater || isImpassible())
|
if (isWater || isImpassible())
|
||||||
return false
|
return false
|
||||||
if (getTilesInDistance(modConstants.minimalCityDistanceOnDifferentContinents)
|
if (getTilesInDistance(modConstants.minimalCityDistanceOnDifferentContinents)
|
||||||
.any { it.isCityCenter() && it.getContinent() != getContinent() }
|
.any { it.isCityCenter() && it.getContinent() != getContinent() }
|
||||||
|| getTilesInDistance(modConstants.minimalCityDistance)
|
|| getTilesInDistance(modConstants.minimalCityDistance)
|
||||||
.any { it.isCityCenter() && it.getContinent() == getContinent() }
|
.any { it.isCityCenter() && it.getContinent() == getContinent() }
|
||||||
) {
|
) {
|
||||||
@ -942,7 +1021,7 @@ open class TileInfo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets this tile's [resource] and, if [newResource] is a Strategic resource, [resourceAmount] fields.
|
* Sets this tile's [resource] and, if [newResource] is a Strategic resource, [resourceAmount] fields.
|
||||||
*
|
*
|
||||||
* [resourceAmount] is determined by [MapParameters.mapResources] and [majorDeposit], and
|
* [resourceAmount] is determined by [MapParameters.mapResources] and [majorDeposit], and
|
||||||
* if the latter is `null` a random choice between major and minor deposit is made, approximating
|
* if the latter is `null` a random choice between major and minor deposit is made, approximating
|
||||||
* the frequency [MapRegions][com.unciv.logic.map.mapgenerator.MapRegions] would use.
|
* the frequency [MapRegions][com.unciv.logic.map.mapgenerator.MapRegions] would use.
|
||||||
|
@ -198,6 +198,13 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
|||||||
if (matchesFilter(unique.params[2]))
|
if (matchesFilter(unique.params[2]))
|
||||||
stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
|
stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (unique in localUniqueCache.get("AllStatsPercentFromObject", civInfo.getMatchingUniques(UniqueType.AllStatsPercentFromObject))) {
|
||||||
|
if (!matchesFilter(unique.params[1])) continue
|
||||||
|
for (stat in Stat.values()) {
|
||||||
|
stats.add(stat, unique.params[0].toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,6 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
|||||||
|
|
||||||
StatsFromCitiesOnSpecificTiles("[stats] in cities on [terrainFilter] tiles", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
StatsFromCitiesOnSpecificTiles("[stats] in cities on [terrainFilter] tiles", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
StatsFromBuildings("[stats] from all [buildingFilter] buildings", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
StatsFromBuildings("[stats] from all [buildingFilter] buildings", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
StatsSpendingGreatPeople("[stats] whenever a Great Person is expended", UniqueTarget.Global),
|
|
||||||
StatsFromTiles("[stats] from [tileFilter] tiles [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
StatsFromTiles("[stats] from [tileFilter] tiles [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
StatsFromTilesWithout("[stats] from [tileFilter] tiles without [tileFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
StatsFromTilesWithout("[stats] from [tileFilter] tiles without [tileFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
// This is a doozy
|
// This is a doozy
|
||||||
@ -96,8 +95,9 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
|||||||
// Stat percentage boosts
|
// Stat percentage boosts
|
||||||
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/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
// Todo: Add support for specialist next to buildingName
|
||||||
AllStatsPercentFromObject("[relativeAmount]% Yield from every [tileFilter]", UniqueTarget.FollowerBelief, UniqueTarget.Global),
|
StatPercentFromObject("[relativeAmount]% [stat] 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),
|
StatPercentFromReligionFollowers("[relativeAmount]% [stat] from every follower, up to [relativeAmount]%", UniqueTarget.FollowerBelief),
|
||||||
BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", UniqueTarget.Global),
|
BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", UniqueTarget.Global),
|
||||||
StatPercentFromTradeRoutes("[relativeAmount]% [stat] from Trade Routes", UniqueTarget.Global),
|
StatPercentFromTradeRoutes("[relativeAmount]% [stat] from Trade Routes", UniqueTarget.Global),
|
||||||
@ -165,6 +165,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
|||||||
FreeExtraAnyBeliefs("May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion", UniqueTarget.Global),
|
FreeExtraAnyBeliefs("May choose [amount] additional belief(s) of any type when [foundingOrEnhancing] a religion", UniqueTarget.Global),
|
||||||
StatsWhenAdoptingReligionSpeed("[stats] when a city adopts this religion for the first time (modified by game speed)", UniqueTarget.Global),
|
StatsWhenAdoptingReligionSpeed("[stats] when a city adopts this religion for the first time (modified by game speed)", UniqueTarget.Global),
|
||||||
StatsWhenAdoptingReligion("[stats] when a city adopts this religion for the first time", UniqueTarget.Global),
|
StatsWhenAdoptingReligion("[stats] when a city adopts this religion for the first time", UniqueTarget.Global),
|
||||||
|
StatsSpendingGreatPeople("[stats] whenever a Great Person is expended", UniqueTarget.Global),
|
||||||
|
|
||||||
UnhappinessFromPopulationTypePercentageChange("[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
UnhappinessFromPopulationTypePercentageChange("[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
@Deprecated("As of 3.19.19", ReplaceWith("[relativeAmount]% Unhappiness from [Population] [cityFilter]"))
|
@Deprecated("As of 3.19.19", ReplaceWith("[relativeAmount]% Unhappiness from [Population] [cityFilter]"))
|
||||||
|
@ -98,11 +98,6 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: Global, FollowerBelief
|
Applicable to: Global, FollowerBelief
|
||||||
|
|
||||||
??? example "[stats] whenever a Great Person is expended"
|
|
||||||
Example: "[+1 Gold, +2 Production] whenever a Great Person is expended"
|
|
||||||
|
|
||||||
Applicable to: Global
|
|
||||||
|
|
||||||
??? example "[stats] from [tileFilter] tiles [cityFilter]"
|
??? example "[stats] from [tileFilter] tiles [cityFilter]"
|
||||||
Example: "[+1 Gold, +2 Production] from [Farm] tiles [in all cities]"
|
Example: "[+1 Gold, +2 Production] from [Farm] tiles [in all cities]"
|
||||||
|
|
||||||
@ -133,12 +128,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: Global, FollowerBelief
|
Applicable to: Global, FollowerBelief
|
||||||
|
|
||||||
??? example "[relativeAmount]% [stat] from every [tileFilter/specialist/buildingName]"
|
??? example "[relativeAmount]% [stat] from every [tileFilter/buildingFilter]"
|
||||||
Example: "[+20]% [Culture] from every [Farm]"
|
Example: "[+20]% [Culture] from every [Farm]"
|
||||||
|
|
||||||
Applicable to: Global, FollowerBelief
|
Applicable to: Global, FollowerBelief
|
||||||
|
|
||||||
??? example "[relativeAmount]% Yield from every [tileFilter]"
|
??? example "[relativeAmount]% Yield from every [tileFilter/buildingFilter]"
|
||||||
Example: "[+20]% Yield from every [Farm]"
|
Example: "[+20]% Yield from every [Farm]"
|
||||||
|
|
||||||
Applicable to: Global, FollowerBelief
|
Applicable to: Global, FollowerBelief
|
||||||
@ -290,6 +285,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
|
|
||||||
Applicable to: Global
|
Applicable to: Global
|
||||||
|
|
||||||
|
??? example "[stats] whenever a Great Person is expended"
|
||||||
|
Example: "[+1 Gold, +2 Production] whenever a Great Person is expended"
|
||||||
|
|
||||||
|
Applicable to: Global
|
||||||
|
|
||||||
??? example "[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]"
|
??? example "[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]"
|
||||||
Example: "[+20]% Unhappiness from [Followers of this Religion] [in all cities]"
|
Example: "[+20]% Unhappiness from [Followers of this Religion] [in all cities]"
|
||||||
|
|
||||||
|
@ -106,21 +106,6 @@ class GlobalUniquesTests {
|
|||||||
Assert.assertTrue(cityInfo.cityStats.finalStatList["Buildings"]!!.gold == 0f)
|
Assert.assertTrue(cityInfo.cityStats.finalStatList["Buildings"]!!.gold == 0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun statsSpendingGreatPeople() {
|
|
||||||
val civInfo = game.addCiv()
|
|
||||||
val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert)
|
|
||||||
val cityInfo = game.addCity(civInfo, tile, true)
|
|
||||||
val unit = game.addUnit("Great Engineer", civInfo, tile)
|
|
||||||
val building = game.createBuildingWithUnique("[+250 Gold] whenever a Great Person is expended")
|
|
||||||
cityInfo.cityConstructions.addBuilding(building.name)
|
|
||||||
civInfo.addGold(-civInfo.gold)
|
|
||||||
|
|
||||||
civInfo.addGold(-civInfo.gold) // reset gold just to be sure
|
|
||||||
unit.consume()
|
|
||||||
Assert.assertTrue(civInfo.gold == 250)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun statsFromTiles() {
|
fun statsFromTiles() {
|
||||||
game.makeHexagonalMap(2)
|
game.makeHexagonalMap(2)
|
||||||
@ -200,8 +185,6 @@ class GlobalUniquesTests {
|
|||||||
inBetweenTile.roadStatus = RoadStatus.Road
|
inBetweenTile.roadStatus = RoadStatus.Road
|
||||||
civInfo.transients().updateCitiesConnectedToCapital()
|
civInfo.transients().updateCitiesConnectedToCapital()
|
||||||
city2.cityStats.update()
|
city2.cityStats.update()
|
||||||
println(city2.isConnectedToCapital())
|
|
||||||
println(city2.cityStats.finalStatList)
|
|
||||||
|
|
||||||
Assert.assertTrue(city2.cityStats.finalStatList["Trade routes"]!!.science == 30f)
|
Assert.assertTrue(city2.cityStats.finalStatList["Trade routes"]!!.science == 30f)
|
||||||
}
|
}
|
||||||
@ -255,8 +238,6 @@ class GlobalUniquesTests {
|
|||||||
|
|
||||||
civ1.updateStatsForNextTurn()
|
civ1.updateStatsForNextTurn()
|
||||||
|
|
||||||
println(civ1.statsForNextTurn.science)
|
|
||||||
|
|
||||||
Assert.assertTrue(civ1.statsForNextTurn.science == 90f)
|
Assert.assertTrue(civ1.statsForNextTurn.science == 90f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,11 +254,92 @@ class GlobalUniquesTests {
|
|||||||
city.cityConstructions.addBuilding(building.name)
|
city.cityConstructions.addBuilding(building.name)
|
||||||
city.cityStats.update()
|
city.cityStats.update()
|
||||||
|
|
||||||
println(city.cityStats.finalStatList)
|
Assert.assertTrue(city.cityStats.finalStatList["Buildings"]!!.science == 30f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun statPercentBonusCities() {
|
||||||
|
val civ = game.addCiv(uniques = listOf("[+200]% [Science] [in all cities]"))
|
||||||
|
val tile = game.getTile(Vector2(0f, 0f))
|
||||||
|
val city = game.addCity(civ, tile, true)
|
||||||
|
val building = game.createBuildingWithUniques(arrayListOf("[+10 Science]"))
|
||||||
|
city.cityConstructions.addBuilding(building.name)
|
||||||
|
city.cityStats.update()
|
||||||
|
|
||||||
Assert.assertTrue(city.cityStats.finalStatList["Buildings"]!!.science == 30f)
|
Assert.assertTrue(city.cityStats.finalStatList["Buildings"]!!.science == 30f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun statPercentFromObject() {
|
||||||
|
game.makeHexagonalMap(1)
|
||||||
|
val emptyBuilding = game.createBuildingWithUniques()
|
||||||
|
val civInfo = game.addCiv(
|
||||||
|
uniques = listOf(
|
||||||
|
"[+3 Faith] from every [Farm]",
|
||||||
|
"[+200]% [Faith] from every [${emptyBuilding.name}]",
|
||||||
|
"[+200]% [Faith] from every [Farm]",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert)
|
||||||
|
val city = game.addCity(civInfo, tile, true)
|
||||||
|
val faithBuilding = game.createBuildingWithUniques()
|
||||||
|
faithBuilding.faith = 3f
|
||||||
|
city.cityConstructions.addBuilding(faithBuilding.name)
|
||||||
|
|
||||||
|
val tile2 = game.setTileFeatures(Vector2(0f,1f), Constants.grassland)
|
||||||
|
tile2.improvement = "Farm"
|
||||||
|
Assert.assertTrue(tile2.getTileStats(city, civInfo).faith == 9f)
|
||||||
|
|
||||||
|
city.cityConstructions.addBuilding(emptyBuilding.name)
|
||||||
|
city.cityStats.update()
|
||||||
|
|
||||||
|
Assert.assertTrue(city.cityStats.finalStatList["Buildings"]!!.faith == 9f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun allStatsPercentFromObject() {
|
||||||
|
game.makeHexagonalMap(1)
|
||||||
|
val emptyBuilding = game.createBuildingWithUniques()
|
||||||
|
val civInfo = game.addCiv(
|
||||||
|
uniques = listOf(
|
||||||
|
"[+3 Faith] from every [Farm]",
|
||||||
|
"[+200]% Yield from every [${emptyBuilding.name}]",
|
||||||
|
"[+200]% Yield from every [Farm]",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert)
|
||||||
|
val city = game.addCity(civInfo, tile, true)
|
||||||
|
val faithBuilding = game.createBuildingWithUniques()
|
||||||
|
faithBuilding.faith = 3f
|
||||||
|
city.cityConstructions.addBuilding(faithBuilding.name)
|
||||||
|
|
||||||
|
val tile2 = game.setTileFeatures(Vector2(0f,1f), Constants.grassland)
|
||||||
|
tile2.improvement = "Farm"
|
||||||
|
Assert.assertTrue(tile2.getTileStats(city, civInfo).faith == 9f)
|
||||||
|
|
||||||
|
city.cityConstructions.addBuilding(emptyBuilding.name)
|
||||||
|
city.cityStats.update()
|
||||||
|
|
||||||
|
Assert.assertTrue(city.cityStats.finalStatList["Buildings"]!!.faith == 9f)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun statsSpendingGreatPeople() {
|
||||||
|
val civInfo = game.addCiv()
|
||||||
|
val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert)
|
||||||
|
val cityInfo = game.addCity(civInfo, tile, true)
|
||||||
|
val unit = game.addUnit("Great Engineer", civInfo, tile)
|
||||||
|
val building = game.createBuildingWithUnique("[+250 Gold] whenever a Great Person is expended")
|
||||||
|
cityInfo.cityConstructions.addBuilding(building.name)
|
||||||
|
|
||||||
|
civInfo.addGold(-civInfo.gold) // reset gold just to be sure
|
||||||
|
|
||||||
|
unit.consume()
|
||||||
|
Assert.assertTrue(civInfo.gold == 250)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,6 @@ import com.unciv.models.metadata.BaseRuleset
|
|||||||
import com.unciv.models.metadata.GameSettings
|
import com.unciv.models.metadata.GameSettings
|
||||||
import com.unciv.models.ruleset.*
|
import com.unciv.models.ruleset.*
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
import com.unciv.ui.utils.withItem
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class TestGame {
|
class TestGame {
|
||||||
|
|
||||||
@ -61,7 +58,9 @@ class TestGame {
|
|||||||
gameInfo.tileMap = newTileMap
|
gameInfo.tileMap = newTileMap
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Makes a new hexagonal tileMap and sets it in gameInfo. Removes all existing tiles. All new tiles have terrain [baseTerrain] */
|
/** Makes a new hexagonal tileMap with radius [newRadius] and sets it in gameInfo.
|
||||||
|
* Removes all existing tiles. All new tiles have terrain [baseTerrain]
|
||||||
|
*/
|
||||||
fun makeHexagonalMap(newRadius: Int, baseTerrain: String = Constants.desert) {
|
fun makeHexagonalMap(newRadius: Int, baseTerrain: String = Constants.desert) {
|
||||||
val newTileMap = TileMap(newRadius, ruleset, tileMap.mapParameters.worldWrap)
|
val newTileMap = TileMap(newRadius, ruleset, tileMap.mapParameters.worldWrap)
|
||||||
newTileMap.mapParameters.mapSize = MapSizeNew(newRadius)
|
newTileMap.mapParameters.mapSize = MapSizeNew(newRadius)
|
||||||
|
Reference in New Issue
Block a user