From aa0fb9ed8bdb506e97f25e0935315af33ef32e2d Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Sun, 25 Jun 2023 08:35:31 +0200 Subject: [PATCH] More unit unique tests (#9639) --- .../jsons/Civ V - Gods & Kings/Nations.json | 4 +- .../assets/jsons/Civ V - Vanilla/Nations.json | 2 +- core/src/com/unciv/logic/city/CityStats.kt | 14 +++++- .../unciv/models/ruleset/unique/UniqueType.kt | 9 +++- .../com/unciv/uniques/GlobalUniquesTests.kt | 45 ++++++++++++++++++- tests/src/com/unciv/uniques/TestGame.kt | 1 + 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/android/assets/jsons/Civ V - Gods & Kings/Nations.json b/android/assets/jsons/Civ V - Gods & Kings/Nations.json index 00d604e469..beebd7b64d 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Nations.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Nations.json @@ -358,7 +358,7 @@ "favoredReligion": "Hinduism", "uniqueName": "Population Growth", - "uniques": ["Unhappiness from number of Cities doubled", "[-50]% Unhappiness from [Population] [in all cities]"], + "uniques": ["[+100]% unhappiness from the number of Cities", "[-50]% Unhappiness from [Population] [in all cities]"], "cities": ["Delhi","Mumbai","Vijayanagara","Pataliputra","Varanasi","Agra","Calcutta","Lahore","Bangalore","Hyderabad","Madurai","Ahmedabad", "Kolhapur","Prayaga","Ayodhya","Indraprastha","Mathura","Ujjain","Gulbarga","Jaunpur","Rajagriha","Sravasti","Tiruchirapalli","Thanjavur", "Bodhgaya","Kushinagar","Amaravati","Gaur","Gwalior","Jaipur","Karachi"], @@ -976,7 +976,7 @@ "cities": ["Edinburgh","Dublin","Cardiff","Truro","Nantes","Douglas","Glasgow","Cork","Aberystwyth", "Penzance","Rennes","Ramsey","Inverness","Limerick","Swansea","St. Ives","Brest","Peel","Aberdeen", "Belfast","Caernarfon","Newquay","Saint-Nazaire","Castletown","Stirling","Galway","Conwy", - "St. Austell","Saint-Malo","Onchan","Dundee","Londonderry","Llanfairpwllgwyngyll","Falmouth","Lorient"] + "St. Austell","Saint-Malo","Onchan","Dundee","Londonderry","Llanfairpwllgwyngyll","Falmouth","Lorient"], // Llanfairpwllgwyngyll should actually be Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch "spyNames": ["Crìsdean", "Siobhán", "Seamus", "Ffion", "Pádraig", "Deirdre", "Mr. Quinn", "Éadaoin", "Alwyn", "Col Ceathar"] }, diff --git a/android/assets/jsons/Civ V - Vanilla/Nations.json b/android/assets/jsons/Civ V - Vanilla/Nations.json index 7b9b6274ab..29aadc87b7 100644 --- a/android/assets/jsons/Civ V - Vanilla/Nations.json +++ b/android/assets/jsons/Civ V - Vanilla/Nations.json @@ -331,7 +331,7 @@ "innerColor": [255, 163, 71], "uniqueName": "Population Growth", - "uniques": ["Unhappiness from number of Cities doubled", "[-50]% Unhappiness from [Population] [in all cities]"], + "uniques": ["[+100]% unhappiness from the number of Cities", "[-50]% Unhappiness from [Population] [in all cities]"], "cities": ["Delhi","Mumbai","Vijayanagara","Pataliputra","Varanasi","Agra","Calcutta","Lahore","Bangalore","Hyderabad","Madurai","Ahmedabad", "Kolhapur","Prayaga","Ayodhya","Indraprastha","Mathura","Ujjain","Gulbarga","Jaunpur","Rajagriha","Sravasti","Tiruchirapalli","Thanjavur", "Bodhgaya","Kushinagar","Amaravati","Gaur","Gwalior","Jaipur","Karachi"] diff --git a/core/src/com/unciv/logic/city/CityStats.kt b/core/src/com/unciv/logic/city/CityStats.kt index d478761ca0..9522158bc0 100644 --- a/core/src/com/unciv/logic/city/CityStats.kt +++ b/core/src/com/unciv/logic/city/CityStats.kt @@ -384,6 +384,14 @@ class CityStats(val city: City) { fun updateCityHappiness(statsFromBuildings: StatTreeNode) { val civInfo = city.civ val newHappinessList = LinkedHashMap() + // This calculation seems weird to me. + // Suppose we calculate the modifier for an AI (non-human) player when the game settings has difficulty level 'prince'. + // We first get the difficulty modifier for this civilization, which results in the 'chieftain' modifier (0.6) being used, + // as this is a non-human player. Then we multiply that by the ai modifier in general, which is 1.0 for prince. + // The end result happens to be 0.6, which seems correct. However, if we were playing on chieftain difficulty, + // we would get back 0.6 twice and the modifier would be 0.36. Thus, in general there seems to be something wrong here + // I don't know enough about the original whether they do something similar or not and can't be bothered to find where + // in the source code this calculation takes place, but it would surprise me if they also did this double multiplication thing. ~xlenstra var unhappinessModifier = civInfo.getDifficulty().unhappinessModifier if (!civInfo.isHuman()) unhappinessModifier *= civInfo.gameInfo.getDifficulty().aiUnhappinessModifier @@ -394,7 +402,11 @@ class CityStats(val city: City) { if (civInfo.hasUnique(UniqueType.UnhappinessFromCitiesDoubled)) unhappinessFromCity *= 2f //doubled for the Indian - newHappinessList["Cities"] = unhappinessFromCity * unhappinessModifier + var uniqueUnhappinessModifier = 0f + for (unique in civInfo.getMatchingUniques(UniqueType.UnhappinessFromCitiesPercentage)) + uniqueUnhappinessModifier += unique.params[0].toFloat() + + newHappinessList["Cities"] = unhappinessFromCity * unhappinessModifier * uniqueUnhappinessModifier.toPercent() var unhappinessFromCitizens = city.population.population.toFloat() diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index d79216f989..45f7f50755 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -156,11 +156,16 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: FoodConsumptionBySpecialists("[relativeAmount]% Food consumption by specialists [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), /// Happiness + @Deprecated("As of 4.7.3", ReplaceWith("[+100]% unhappiness from the number of cities")) UnhappinessFromCitiesDoubled("Unhappiness from number of Cities doubled", UniqueTarget.Global), + UnhappinessFromCitiesPercentage("[relativeAmount]% unhappiness from the number of cities", UniqueTarget.Global), + // Todo: capitalization of 'Unhappiness' -> 'unhappiness' UnhappinessFromPopulationTypePercentageChange("[relativeAmount]% Unhappiness from [populationFilter] [cityFilter]", UniqueTarget.Global, UniqueTarget.FollowerBelief), - ExcessHappinessToGlobalStat("[relativeAmount]% of excess happiness converted to [stat]", UniqueTarget.Global), - RetainHappinessFromLuxury("Retain [relativeAmount]% of the happiness from a luxury after the last copy has been traded away", UniqueTarget.Global), BonusHappinessFromLuxury("[amount] Happiness from each type of luxury resource", UniqueTarget.Global), + // Todo: capitalization of 'happiness' -> 'Happiness' + RetainHappinessFromLuxury("Retain [relativeAmount]% of the happiness from a luxury after the last copy has been traded away", UniqueTarget.Global), + // Todo: capitalization of 'happiness' -> 'Happiness' + ExcessHappinessToGlobalStat("[relativeAmount]% of excess happiness converted to [stat]", UniqueTarget.Global), /// Unit Production CannotBuildUnits("Cannot build [baseUnitFilter] units", UniqueTarget.Global), diff --git a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt index ba5e0afc4e..58dcb89694 100644 --- a/tests/src/com/unciv/uniques/GlobalUniquesTests.kt +++ b/tests/src/com/unciv/uniques/GlobalUniquesTests.kt @@ -7,16 +7,17 @@ import com.unciv.logic.map.tile.RoadStatus import com.unciv.models.ruleset.BeliefType import com.unciv.models.stats.Stats import com.unciv.testing.GdxTestRunner -import com.unciv.ui.screens.worldscreen.unit.actions.UnitActionsPillage import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import kotlin.math.abs @RunWith(GdxTestRunner::class) class GlobalUniquesTests { private lateinit var game: TestGame + private val epsilon = 0.01 // for float comparisons @Before fun initTheWorld() { @@ -571,6 +572,48 @@ class GlobalUniquesTests { // endregion growth + // region happiness + + @Test + fun unhappinessFromCitiesPercentageTest() { + val civInfo = game.addCiv("[+100]% unhappiness from the number of cities") + val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert) + val cityInfo = game.addCity(civInfo, tile, true) + + cityInfo.cityStats.update() + // Because of some weird design choices, -3.6 is correct, though I would expect it to be -6 instead. + // As I'm not certain enough in my feeling that it should be -6, I'm changing the test to fit the code instead of the other way around. + // I've also written some text about this in CityStats.updateCityHappiness(). ~xlenstra + println(cityInfo.cityStats.happinessList) + Assert.assertTrue(abs(cityInfo.cityStats.happinessList["Cities"]!! - -3.6f) < epsilon) // Float rounding errors + } + + @Test + fun unhappinessFromPopulationTypePercentageChangeTest() { + val civInfo = game.addCiv("[-50]% Unhappiness from [Population] [in all cities]") + val tile = game.setTileFeatures(Vector2(0f,0f), Constants.desert) + val cityInfo = game.addCity(civInfo, tile, true, initialPopulation = 4) + + cityInfo.cityStats.update() + // This test suffers from the same problems as `unhappinessFromCitiesPercentageTest()`. + // This should be -2, I believe, as every pop should add -1 happiness and 4 pop with -50% unhappiness = -2 unhappiness. + println(cityInfo.cityStats.happinessList) + Assert.assertTrue(abs(cityInfo.cityStats.happinessList["Population"]!! - -1.2f) < epsilon) + + val building = game.createBuilding("[-50]% Unhappiness from [Specialists] [in all cities]") + val specialist = game.createSpecialist() + building.specialistSlots[specialist] = 2 + cityInfo.population.specialistAllocations[specialist] = 2 + cityInfo.cityConstructions.addBuilding(building.name) + + cityInfo.cityStats.update() + println(cityInfo.cityStats.happinessList) + // This test suffers from the same problems as above. It should be -1, I believe. + Assert.assertTrue(abs(cityInfo.cityStats.happinessList["Population"]!! - -0.6f) < epsilon) + } + + + // endregion happiness // region Great Persons diff --git a/tests/src/com/unciv/uniques/TestGame.kt b/tests/src/com/unciv/uniques/TestGame.kt index c11f13e12f..06a1a19dde 100644 --- a/tests/src/com/unciv/uniques/TestGame.kt +++ b/tests/src/com/unciv/uniques/TestGame.kt @@ -49,6 +49,7 @@ class TestGame { RulesetCache.loadRulesets(noMods = true) ruleset = RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!! gameInfo.ruleset = ruleset + gameInfo.difficulty = "Prince" gameInfo.difficultyObject = ruleset.difficulties["Prince"]!! gameInfo.speed = ruleset.speeds[Speed.DEFAULTFORSIMULATION]!! gameInfo.currentPlayerCiv = Civilization() // Will be uninitialized, do not build on for tests