mirror of
https://github.com/yairm210/Unciv.git
synced 2025-08-03 12:37:42 -04:00
Another countables test (#13184)
* Faster TestGame class * Fix fragile unit tests * Prerequisites for a Countables.FilteredBuildings unit test * A Countables.FilteredBuildings unit test * Revert buildingFilter duplicate tagUnique support
This commit is contained in:
parent
b0c9295372
commit
a6da025705
@ -71,11 +71,11 @@ class Nation : RulesetObject() {
|
||||
override fun getUniqueTarget() = UniqueTarget.Nation
|
||||
|
||||
@Transient
|
||||
private lateinit var outerColorObject: Color
|
||||
private var outerColorObject = Color.WHITE // Not lateinit for unit tests
|
||||
fun getOuterColor(): Color = outerColorObject
|
||||
|
||||
@Transient
|
||||
private lateinit var innerColorObject: Color
|
||||
private var innerColorObject = Color.BLACK // Not lateinit for unit tests
|
||||
|
||||
fun getInnerColor(): Color = innerColorObject
|
||||
|
||||
|
@ -211,7 +211,9 @@ enum class Countables(
|
||||
companion object {
|
||||
fun getMatching(parameterText: String, ruleset: Ruleset?) = Countables.entries
|
||||
.filter {
|
||||
if (it.matchesWithRuleset) it.matches(parameterText, ruleset!!) else it.matches(parameterText)
|
||||
if (it.matchesWithRuleset)
|
||||
ruleset != null && it.matches(parameterText, ruleset!!)
|
||||
else it.matches(parameterText)
|
||||
}
|
||||
|
||||
fun getCountableAmount(parameterText: String, stateForConditionals: StateForConditionals): Int? {
|
||||
|
@ -389,8 +389,8 @@ class BattleTest {
|
||||
@Test
|
||||
fun `should always destroy unit directly hit by nuke`() {
|
||||
// given
|
||||
val defenderUnit = testGame.addUnit("Warrior", defenderCiv, testGame.getTile(Vector2.Y))
|
||||
defenderUnit.baseUnit.strength = 1_000_000
|
||||
val megaWarrior = testGame.createBaseUnit("Sword").apply { strength = 1_000_000 }
|
||||
val defenderUnit = testGame.addUnit(megaWarrior.name, defenderCiv, testGame.getTile(Vector2.Y))
|
||||
|
||||
testGame.addCity(attackerCiv, testGame.getTile(Vector2.Y))
|
||||
val attackerUnit = testGame.addUnit("Atomic Bomb", attackerCiv, testGame.getTile(Vector2.Y))
|
||||
|
@ -32,7 +32,11 @@ import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.models.ruleset.unit.Promotion
|
||||
import com.unciv.models.ruleset.unit.UnitType
|
||||
|
||||
class TestGame(overrideRuleset: (() -> Ruleset)? = null) {
|
||||
/**
|
||||
* A testing game using a fresh clone of the Civ_V_GnK ruleset so it can be modded in-place
|
||||
* @param addGlobalUniques optional global uniques to add to the ruleset
|
||||
*/
|
||||
class TestGame(vararg addGlobalUniques: String) {
|
||||
|
||||
private var objectsCreated = 0
|
||||
val ruleset: Ruleset
|
||||
@ -49,8 +53,14 @@ class TestGame(overrideRuleset: (() -> Ruleset)? = null) {
|
||||
UncivGame.Current.gameInfo = gameInfo
|
||||
|
||||
// Create a new ruleset we can easily edit, and set the important variables of gameInfo
|
||||
RulesetCache.loadRulesets(noMods = true)
|
||||
ruleset = overrideRuleset?.invoke() ?: RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!!
|
||||
if (RulesetCache.isEmpty())
|
||||
RulesetCache.loadRulesets(noMods = true)
|
||||
ruleset = RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!!.clone()
|
||||
ruleset.globalUniques.uniques.run {
|
||||
for (unique in addGlobalUniques)
|
||||
add(unique)
|
||||
}
|
||||
|
||||
gameInfo.ruleset = ruleset
|
||||
gameInfo.difficulty = "Prince"
|
||||
gameInfo.difficultyObject = ruleset.difficulties["Prince"]!!
|
||||
@ -128,9 +138,8 @@ class TestGame(overrideRuleset: (() -> Ruleset)? = null) {
|
||||
cities = arrayListOf("The Capital")
|
||||
this.cityStateType = cityStateType
|
||||
}
|
||||
val nation = createRulesetObject(ruleset.nations, *uniques) {
|
||||
nationFactory()
|
||||
}
|
||||
val nation = createRulesetObject(ruleset.nations, *uniques, factory = ::nationFactory)
|
||||
|
||||
val civInfo = Civilization()
|
||||
civInfo.nation = nation
|
||||
civInfo.gameInfo = gameInfo
|
||||
|
@ -1,14 +1,13 @@
|
||||
package com.unciv.uniques
|
||||
|
||||
import com.unciv.models.metadata.BaseRuleset
|
||||
import com.unciv.logic.city.City
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.unique.Countables
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueParameterType
|
||||
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
|
||||
import com.unciv.models.ruleset.validation.RulesetErrorList
|
||||
import com.unciv.models.ruleset.validation.RulesetValidator
|
||||
import com.unciv.models.stats.Stat
|
||||
import com.unciv.models.translations.getPlaceholderParameters
|
||||
@ -25,9 +24,9 @@ import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(GdxTestRunner::class)
|
||||
class CountableTests {
|
||||
private var game = TestGame().apply { makeHexagonalMap(3) }
|
||||
private var civInfo = game.addCiv()
|
||||
private var city = game.addCity(civInfo, game.tileMap[2,0])
|
||||
private lateinit var game: TestGame
|
||||
private lateinit var civ: Civilization
|
||||
private lateinit var city: City
|
||||
|
||||
@Test
|
||||
fun testCountableConventions() {
|
||||
@ -87,11 +86,12 @@ class CountableTests {
|
||||
|
||||
@Test
|
||||
fun testPerCountableForGlobalAndLocalResources() {
|
||||
setupModdedGame()
|
||||
// one coal provided locally
|
||||
val providesCoal = game.createBuilding("Provides [1] [Coal]")
|
||||
city.cityConstructions.addBuilding(providesCoal)
|
||||
// one globally
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Provides [1] [Coal] <for [2] turns>"), civInfo)
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Provides [1] [Coal] <for [2] turns>"), civ)
|
||||
val providesFaithPerCoal = game.createBuilding("[+1 Faith] [in this city] <for every [Coal]>")
|
||||
city.cityConstructions.addBuilding(providesFaithPerCoal)
|
||||
assertEquals(2f, city.cityStats.currentCityStats.faith)
|
||||
@ -99,10 +99,11 @@ class CountableTests {
|
||||
|
||||
@Test
|
||||
fun testStatsCountables() {
|
||||
setupModdedGame()
|
||||
fun verifyStats(state: StateForConditionals) {
|
||||
for (stat in Stat.entries) {
|
||||
val countableResult = Countables.Stats.eval(stat.name, state)
|
||||
val expected = if (stat == Stat.Happiness) civInfo.getHappiness()
|
||||
val expected = if (stat == Stat.Happiness) civ.getHappiness()
|
||||
else state.getStatAmount(stat)
|
||||
assertEquals("Testing $stat countable:", countableResult, expected)
|
||||
}
|
||||
@ -111,21 +112,22 @@ class CountableTests {
|
||||
val providesStats =
|
||||
game.createBuilding("[+1 Gold, +2 Food, +3 Production, +4 Happiness, +3 Science, +2 Culture, +1 Faith] [in this city] <when number of [Cities] is equal to [1]>")
|
||||
city.cityConstructions.addBuilding(providesStats)
|
||||
verifyStats(StateForConditionals(civInfo, city))
|
||||
verifyStats(StateForConditionals(civ, city))
|
||||
|
||||
val city2 = game.addCity(civInfo, game.tileMap[-2,0])
|
||||
val city2 = game.addCity(civ, game.tileMap[-2,0])
|
||||
val providesStats2 =
|
||||
game.createBuilding("[+3 Gold, +2 Food, +1 Production, -4 Happiness, +1 Science, +2 Culture, +3 Faith] [in this city] <when number of [Cities] is more than [1]>")
|
||||
city2.cityConstructions.addBuilding(providesStats2)
|
||||
verifyStats(StateForConditionals(civInfo, city2))
|
||||
verifyStats(StateForConditionals(civ, city2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOwnedTilesCountable() {
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civInfo, tile = game.tileMap[-3,0])
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civInfo, tile = game.tileMap[3,0])
|
||||
setupModdedGame()
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civ, tile = game.tileMap[-3,0])
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civ, tile = game.tileMap[3,0])
|
||||
|
||||
game.addCity(civInfo, game.tileMap[-2,0], initialPopulation = 9)
|
||||
game.addCity(civ, game.tileMap[-2,0], initialPopulation = 9)
|
||||
val tests = listOf(
|
||||
"Owned [All] Tiles" to 14,
|
||||
"Owned [worked] Tiles" to 8,
|
||||
@ -135,16 +137,17 @@ class CountableTests {
|
||||
"Owned [Farm] Tiles" to 0,
|
||||
)
|
||||
for ((test, expected) in tests) {
|
||||
val actual = Countables.getCountableAmount(test, StateForConditionals(civInfo))
|
||||
val actual = Countables.getCountableAmount(test, StateForConditionals(civ))
|
||||
assertEquals("Testing `$test` countable:", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilteredCitiesCountable() {
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civInfo, tile = game.tileMap[-3,0])
|
||||
setupModdedGame()
|
||||
UniqueTriggerActivation.triggerUnique(Unique("Turn this tile into a [Coast] tile"), civ, tile = game.tileMap[-3,0])
|
||||
|
||||
val city2 = game.addCity(civInfo, game.tileMap[-2,0], initialPopulation = 9)
|
||||
val city2 = game.addCity(civ, game.tileMap[-2,0], initialPopulation = 9)
|
||||
city2.isPuppet = true
|
||||
val tests = listOf(
|
||||
"[Capital] Cities" to 1,
|
||||
@ -153,11 +156,40 @@ class CountableTests {
|
||||
"[Your] Cities" to 2,
|
||||
)
|
||||
for ((test, expected) in tests) {
|
||||
val actual = Countables.getCountableAmount(test, StateForConditionals(civInfo))
|
||||
val actual = Countables.getCountableAmount(test, StateForConditionals(civ))
|
||||
assertEquals("Testing `$test` countable:", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFilteredBuildingsCountable () {
|
||||
setupModdedGame(withCiv = false)
|
||||
val building = game.createBuilding("Ancestor Tree") // That's a filtering Unique, not the building name
|
||||
building[Stat.Culture] = 1f
|
||||
|
||||
civ = game.addCiv(
|
||||
"[+1 Culture] from all [Ancestor Tree] buildings <for every [1] [[Ancestor Tree] Buildings]> <when number of [[Ancestor Tree] Buildings] is more than [1]>",
|
||||
"[+50]% [Culture] from every [Ancestor Tree] <when number of [[Ancestor Tree] Buildings] is more than [0]>",
|
||||
)
|
||||
city = game.addCity(civ, game.tileMap[2,0])
|
||||
city.cityConstructions.addBuilding(building)
|
||||
val city2 = game.addCity(civ, game.tileMap[-2,0])
|
||||
city2.cityConstructions.addBuilding(building)
|
||||
|
||||
// updateStatsForNextTurn won't run the city part because no happiness change across a boundary
|
||||
for (city3 in civ.cities)
|
||||
city3.cityStats.update(updateCivStats = false)
|
||||
civ.updateStatsForNextTurn()
|
||||
|
||||
// Expect: (1 Palace + 1 Base Ancestor Tree + 2 for-every) * 1.5 = 6
|
||||
val capitalCulture = city.cityStats.currentCityStats.culture
|
||||
// Expect: capitalCulture + (1 Base Ancestor Tree + 2 for-every) * 1.5 = 10.5
|
||||
val civCulture = civ.stats.statsForNextTurn.culture
|
||||
|
||||
assertEquals(6f, capitalCulture, 0.005f)
|
||||
assertEquals(10.5f, civCulture, 0.005f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRulesetValidation() {
|
||||
/** These are `Pair<String, Int>` with the second being the expected number of parameters to fail UniqueParameterType validation */
|
||||
@ -190,7 +222,10 @@ class CountableTests {
|
||||
val totalNotACountableExpected = testData.sumOf { it.second }
|
||||
val notACountableRegex = Regex(""".*parameter "(.*)" which does not fit parameter type countable.*""")
|
||||
|
||||
val ruleset = setupModdedGame(*testData.map { it.first }.toTypedArray())
|
||||
val ruleset = setupModdedGame(
|
||||
*testData.map { it.first }.toTypedArray(),
|
||||
withCiv = false // City founding would only slow this down
|
||||
)
|
||||
ruleset.modOptions.isBaseRuleset = true // To get ruleset-specific validation
|
||||
|
||||
val errors = RulesetValidator(ruleset).getErrorList()
|
||||
@ -235,28 +270,21 @@ class CountableTests {
|
||||
"[+1 Happiness] <for every [[42] Monkeys]>",
|
||||
)
|
||||
|
||||
game.makeHexagonalMap(3)
|
||||
civInfo = game.addCiv()
|
||||
city = game.addCity(civInfo, game.tileMap[2,0])
|
||||
|
||||
val cityState = game.addCiv(cityStateType = game.ruleset.cityStateTypes.keys.first())
|
||||
game.addCity(cityState, game.tileMap[-2,0], true)
|
||||
civInfo.updateStatsForNextTurn()
|
||||
civ.updateStatsForNextTurn()
|
||||
|
||||
val happiness = Countables.getCountableAmount("Happiness", StateForConditionals(civInfo, city))
|
||||
val happiness = Countables.getCountableAmount("Happiness", StateForConditionals(civ, city))
|
||||
// Base 9, -1 city, -3 population +1 deprecated countable should still work, but the bogus one should not
|
||||
assertEquals("Testing Happiness", 6, happiness)
|
||||
}
|
||||
|
||||
private fun setupModdedGame(vararg uniques: String): Ruleset {
|
||||
val mod = Ruleset()
|
||||
mod.name = "Testing"
|
||||
for (unique in uniques)
|
||||
mod.globalUniques.uniques.add(unique)
|
||||
game = TestGame {
|
||||
RulesetCache[mod.name] = mod
|
||||
RulesetCache.getComplexRuleset(RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!!, listOf(mod))
|
||||
}
|
||||
private fun setupModdedGame(vararg addGlobalUniques: String, withCiv: Boolean = true): Ruleset {
|
||||
game = TestGame(*addGlobalUniques)
|
||||
game.makeHexagonalMap(3)
|
||||
if (!withCiv) return game.ruleset
|
||||
civ = game.addCiv()
|
||||
city = game.addCity(civ, game.tileMap[2,0])
|
||||
return game.ruleset
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package com.unciv.uniques
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.json.json
|
||||
import com.unciv.logic.map.mapunit.UnitTurnManager
|
||||
import com.unciv.models.UnitActionType
|
||||
import com.unciv.models.ruleset.tile.TileImprovement
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.translations.fillPlaceholders
|
||||
import com.unciv.testing.GdxTestRunner
|
||||
@ -52,11 +54,14 @@ class UnitUniquesTests {
|
||||
@Test
|
||||
fun canConstructResourceRequiringImprovement() {
|
||||
// Do this early so the uniqueObjects lazy is still un-triggered
|
||||
val improvement = game.ruleset.tileImprovements["Manufactory"]!!
|
||||
val requireUnique = UniqueType.ConsumesResources.text.fillPlaceholders("3", "Iron")
|
||||
// Get a clone with lazies un-tripped
|
||||
val oldImprovement = game.ruleset.tileImprovements["Manufactory"]!!
|
||||
val improvement = json().run { fromJson(TileImprovement::class.java, toJson(oldImprovement)) }
|
||||
improvement.uniques.add(requireUnique)
|
||||
Assert.assertFalse("Test preparation failed to add ConsumesResources to Manufactory",
|
||||
improvement.uniqueObjects.none { it.type == UniqueType.ConsumesResources })
|
||||
game.ruleset.tileImprovements["Manufactory"] = improvement
|
||||
|
||||
game.makeHexagonalMap(1)
|
||||
val civ = game.addCiv(isPlayer = true)
|
||||
@ -72,6 +77,7 @@ class UnitUniquesTests {
|
||||
} catch (ex: Throwable) {
|
||||
// Give that IndexOutOfBoundsException a nicer name
|
||||
Assert.fail("getImprovementConstructionActions throws Exception ${ex.javaClass.simpleName}")
|
||||
game.ruleset.tileImprovements["Manufactory"] = oldImprovement
|
||||
return
|
||||
}.filter { it.action != null }
|
||||
Assert.assertTrue("Great Engineer should NOT be able to create a Manufactory modded to require Iron with 0 Iron",
|
||||
@ -94,6 +100,7 @@ class UnitUniquesTests {
|
||||
.filter { it.action != null }
|
||||
Assert.assertFalse("Great Engineer SHOULD be able to create a Manufactory modded to require Iron once Iron is available",
|
||||
actionsWithIron.none())
|
||||
game.ruleset.tileImprovements["Manufactory"] = oldImprovement
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user