mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-08 23:08:35 +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.culture *= statPercentBonusesSum.culture.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
|
||||
|
@ -8,11 +8,9 @@ import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.ruleset.tile.*
|
||||
import com.unciv.models.ruleset.unique.LocalUniqueCache
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.*
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.stats.Stats
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.civilopedia.FormattedLine
|
||||
@ -319,22 +317,26 @@ open class TileInfo {
|
||||
tileUniques += city.getMatchingUniques(UniqueType.StatsFromObject, stateForConditionals)
|
||||
for (unique in localUniqueCache.get("StatsFromTilesAndObjects", tileUniques)) {
|
||||
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))
|
||||
stats.add(unique.stats)
|
||||
if (tileType == "Natural Wonder" && naturalWonder != null && city.civInfo.hasUnique(UniqueType.DoubleStatsFromNaturalWonders)) {
|
||||
if (!matchesTerrainFilter(tileType, observingCiv)) continue
|
||||
stats.add(unique.stats)
|
||||
if (naturalWonder != null
|
||||
&& tileType == "Natural Wonder"
|
||||
&& city.civInfo.hasUnique(UniqueType.DoubleStatsFromNaturalWonders)
|
||||
) {
|
||||
stats.add(unique.stats)
|
||||
}
|
||||
}
|
||||
|
||||
for (unique in localUniqueCache.get("StatsFromTilesWithout",
|
||||
city.getMatchingUniques(UniqueType.StatsFromTilesWithout, stateForConditionals)))
|
||||
city.getMatchingUniques(UniqueType.StatsFromTilesWithout, stateForConditionals))
|
||||
) {
|
||||
if (
|
||||
matchesTerrainFilter(unique.params[1]) &&
|
||||
!matchesTerrainFilter(unique.params[2]) &&
|
||||
city.matchesFilter(unique.params[3])
|
||||
)
|
||||
stats.add(unique.stats)
|
||||
}
|
||||
}
|
||||
|
||||
if (isAdjacentToRiver()) stats.gold++
|
||||
@ -347,17 +349,61 @@ open class TileInfo {
|
||||
if (improvement != null)
|
||||
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())
|
||||
stats.gold++
|
||||
}
|
||||
if (isCityCenter()) {
|
||||
if (stats.food < 2) stats.food = 2f
|
||||
if (stats.production < 1) stats.production = 1f
|
||||
}
|
||||
|
||||
for ((stat, value) in stats)
|
||||
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
|
||||
}
|
||||
|
||||
@ -421,6 +467,7 @@ open class TileInfo {
|
||||
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 {
|
||||
val stats = improvement.cloneStats()
|
||||
if (hasViewableResource(observingCiv) && tileResource.isImprovedBy(improvement.name)
|
||||
@ -464,17 +511,49 @@ open class TileInfo {
|
||||
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)) {
|
||||
if (improvement.matchesFilter(unique.params[1]))
|
||||
stats.timesInPlace(unique.params[0].toPercent())
|
||||
if (!improvement.matchesFilter(unique.params[1])) continue
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,6 +199,13 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
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),
|
||||
StatsFromTilesWithout("[stats] from [tileFilter] tiles without [tileFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
// This is a doozy
|
||||
@ -96,8 +95,9 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
|
||||
// Stat percentage boosts
|
||||
StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
StatPercentBonusCities("[relativeAmount]% [stat] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
StatPercentFromObject("[relativeAmount]% [stat] from every [tileFilter/specialist/buildingName]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
AllStatsPercentFromObject("[relativeAmount]% Yield from every [tileFilter]", UniqueTarget.FollowerBelief, UniqueTarget.Global),
|
||||
// Todo: Add support for specialist next to buildingName
|
||||
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),
|
||||
BonusStatsFromCityStates("[relativeAmount]% [stat] from City-States", 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),
|
||||
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),
|
||||
StatsSpendingGreatPeople("[stats] whenever a Great Person is expended", UniqueTarget.Global),
|
||||
|
||||
UnhappinessFromPopulationTypePercentageChange("[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||
@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
|
||||
|
||||
??? 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: "[+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
|
||||
|
||||
??? example "[relativeAmount]% [stat] from every [tileFilter/specialist/buildingName]"
|
||||
??? example "[relativeAmount]% [stat] from every [tileFilter/buildingFilter]"
|
||||
Example: "[+20]% [Culture] from every [Farm]"
|
||||
|
||||
Applicable to: Global, FollowerBelief
|
||||
|
||||
??? example "[relativeAmount]% Yield from every [tileFilter]"
|
||||
??? example "[relativeAmount]% Yield from every [tileFilter/buildingFilter]"
|
||||
Example: "[+20]% Yield from every [Farm]"
|
||||
|
||||
Applicable to: Global, FollowerBelief
|
||||
@ -290,6 +285,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
||||
|
||||
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: "[+20]% Unhappiness from [Followers of this Religion] [in all cities]"
|
||||
|
||||
|
@ -106,21 +106,6 @@ class GlobalUniquesTests {
|
||||
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
|
||||
fun statsFromTiles() {
|
||||
game.makeHexagonalMap(2)
|
||||
@ -200,8 +185,6 @@ class GlobalUniquesTests {
|
||||
inBetweenTile.roadStatus = RoadStatus.Road
|
||||
civInfo.transients().updateCitiesConnectedToCapital()
|
||||
city2.cityStats.update()
|
||||
println(city2.isConnectedToCapital())
|
||||
println(city2.cityStats.finalStatList)
|
||||
|
||||
Assert.assertTrue(city2.cityStats.finalStatList["Trade routes"]!!.science == 30f)
|
||||
}
|
||||
@ -255,8 +238,6 @@ class GlobalUniquesTests {
|
||||
|
||||
civ1.updateStatsForNextTurn()
|
||||
|
||||
println(civ1.statsForNextTurn.science)
|
||||
|
||||
Assert.assertTrue(civ1.statsForNextTurn.science == 90f)
|
||||
}
|
||||
|
||||
@ -273,11 +254,92 @@ class GlobalUniquesTests {
|
||||
city.cityConstructions.addBuilding(building.name)
|
||||
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)
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@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.ruleset.*
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.utils.withItem
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
class TestGame {
|
||||
|
||||
@ -61,7 +58,9 @@ class TestGame {
|
||||
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) {
|
||||
val newTileMap = TileMap(newRadius, ruleset, tileMap.mapParameters.worldWrap)
|
||||
newTileMap.mapParameters.mapSize = MapSizeNew(newRadius)
|
||||
|
Reference in New Issue
Block a user