From ef9965e2182d5eddcad623792c92a1a7d5cab145 Mon Sep 17 00:00:00 2001 From: Oskar Niesen Date: Tue, 9 Apr 2024 15:12:21 -0500 Subject: [PATCH] Espionage Uniques, Buildings and Policy (#11401) * Added OneTimeSpiesLevelUp, OneTimeGainSpy, SpyEffectiveness, EnemySpyEffectiveness and HiddenWithoutEspionage Uniques * Spy effectiveness affects stealing tech and rigging elections * Fixed HiddenWithoutEspionage * Added Constabulary and Police Station * Added cityFilter to SpyEffectiveness * Added national Intelligence agency * Added Great Firewall * Fixed great firewall having a float value * EspionageManager addSpy now returns Spy instead of name * Added some simple espionage tests * Fixed OneTimeSpiesLevelUp still wanting parameter * Spy efficiency occurs after skill modifier * Added another test * Added Police State spy efficiency reduction unique * Fixed "Hidden when espionage is disabled" wording * Fixed "effectiveness" wording * Changed "enemy spy effectiveness" unique to use negative matters * Spy effectiveness only affect tech steal rate * Changed "Gain an extra spy" and "Promotes all spies" uniques * Removed Police State comment that is no longer accurate * Changed spy effectiveness to be multiplicative --- .../jsons/Civ V - Gods & Kings/Buildings.json | 33 +++++++++++ .../jsons/Civ V - Gods & Kings/Policies.json | 4 +- .../civilization/managers/EspionageManager.kt | 4 +- core/src/com/unciv/models/Spy.kt | 48 +++++++++++++--- core/src/com/unciv/models/ruleset/Building.kt | 4 ++ .../com/unciv/models/ruleset/nation/Nation.kt | 3 + .../ruleset/unique/UniqueTriggerActivation.kt | 22 ++++++- .../unciv/models/ruleset/unique/UniqueType.kt | 8 +++ .../TechnologyDescriptions.kt | 9 +-- .../civilopediascreen/CivilopediaScreen.kt | 8 +++ .../overviewscreen/WonderOverviewTab.kt | 2 + docs/Modders/uniques.md | 19 +++++++ .../logic/civilization/EspionageTests.kt | 57 +++++++++++++++++++ 13 files changed, 203 insertions(+), 18 deletions(-) create mode 100644 tests/src/com/unciv/logic/civilization/EspionageTests.kt diff --git a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json index ab5aa0eaf2..752f826b41 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Buildings.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Buildings.json @@ -709,6 +709,14 @@ "requiredBuilding": "Market", "requiredTech": "Banking" }, + { + "name": "Constabulary", + "cost": 160, + "maintenance": 1, + "hurryCostModifier": 10, + "uniques": ["Hidden when espionage is disabled", "[-25]% enemy spy effectiveness [in this city]"], + "requiredTech": "Banking" + }, // will be introduced in BNW expansion pack // { // "name": "Hanse", @@ -934,6 +942,15 @@ "uniques": ["Must be on [River]","[+1 Production] from [River] tiles [in this city]"], "requiredTech": "Electricity" }, + { + "name": "Police Station", + "cost": 300, + "maintenance": 1, + "hurryCostModifier": 10, + "uniques": ["Hidden when espionage is disabled", "[-25]% enemy spy effectiveness [in this city]"], + "requiredBuilding": "Constabulary", + "requiredTech": "Electricity" + }, // Modern Era @@ -964,6 +981,15 @@ "requiredTech": "Radio", "quote": "'We live only to discover beauty, all else is a form of waiting' - Kahlil Gibran" }, + { + "name": "National Intelligence Agency", + "cost": 120, + "culture": 1, + "isNationalWonder": true, + "uniques": ["Hidden when espionage is disabled", "Gain an extra spy", "Promotes all spies", "[-15]% enemy spy effectiveness [in this city]", + "Only available ", "Cost increases by [30] per owned city"], + "requiredTech": "Radio" + }, { "name": "Military Base", "cityStrength": 12, @@ -1083,6 +1109,13 @@ "Hidden when [Scientific] Victory is disabled", "Cannot be hurried"], "requiredTech": "Rocketry" }, + { + "name": "Great Firewall", + "isWonder": true, + "uniques": ["Hidden when espionage is disabled", "[-99]% enemy spy effectiveness [in this city]", + "[-25]% enemy spy effectiveness [in all cities]",], + "requiredTech": "Computers" + }, // Information Era diff --git a/android/assets/jsons/Civ V - Gods & Kings/Policies.json b/android/assets/jsons/Civ V - Gods & Kings/Policies.json index d93be0733b..8b1d48b6ee 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Policies.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Policies.json @@ -577,9 +577,9 @@ "name": "Police State", "uniques": [ "[+3 Happiness] from every [Courthouse]", - "[+100]% Production when constructing [Courthouse] buildings [in all cities]" + "[+100]% Production when constructing [Courthouse] buildings [in all cities]", + "[-25]% enemy spy effectiveness [in all cities]" ], - // There are also some uniques regarding espoinage, which as of this writing is not yet implemented "requires": ["Militarism"], "row": 2, "column": 4 diff --git a/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt b/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt index 7d6c5b540d..c2a6a59dc2 100644 --- a/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/EspionageManager.kt @@ -41,12 +41,12 @@ class EspionageManager : IsPartOfGameInfoSerialization { return validSpyNames.random() } - fun addSpy(): String { + fun addSpy(): Spy { val spyName = getSpyName() val newSpy = Spy(spyName) newSpy.setTransients(civInfo) spyList.add(newSpy) - return spyName + return newSpy } fun getTilesVisibleViaSpies(): Sequence { diff --git a/core/src/com/unciv/models/Spy.kt b/core/src/com/unciv/models/Spy.kt index 3e65cac7e4..7725b28b2f 100644 --- a/core/src/com/unciv/models/Spy.kt +++ b/core/src/com/unciv/models/Spy.kt @@ -8,6 +8,9 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.civilization.managers.EspionageManager +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.Unique +import com.unciv.models.ruleset.unique.UniqueType import kotlin.random.Random @@ -100,8 +103,10 @@ class Spy() : IsPartOfGameInfoSerialization { return } val techStealCost = stealableTechs.maxOfOrNull { civInfo.gameInfo.ruleset.technologies[it]!!.cost }!! + var progressThisTurn = getLocation()!!.cityStats.currentCityStats.science // 33% spy bonus for each level - val progressThisTurn = getLocation()!!.cityStats.currentCityStats.science * (rank + 2f) / 3f + progressThisTurn *= (rank + 2f) / 3f + progressThisTurn *= getEfficiencyModifier().toFloat() progressTowardsStealingTech += progressThisTurn.toInt() if (progressTowardsStealingTech > techStealCost) { stealTech() @@ -120,7 +125,7 @@ class Spy() : IsPartOfGameInfoSerialization { val oldSpyName = name name = espionageManager.getSpyName() action = SpyAction.None - civInfo.addNotification("We have recruited a new spy name [$name] after [$oldSpyName] was killed.", + civInfo.addNotification("We have recruited a new spy name [$name] after [$oldSpyName] was killed.", NotificationCategory.Espionage, NotificationIcon.Spy) } SpyAction.CounterIntelligence -> { @@ -129,7 +134,7 @@ class Spy() : IsPartOfGameInfoSerialization { // Once turnRemainingForAction is <= 0 the spy won't be considered to be doing work any more --turnsRemainingForAction return - } + } else -> return // Not implemented yet, so don't do anything } } @@ -157,7 +162,6 @@ class Spy() : IsPartOfGameInfoSerialization { // Subtract the experience of the counter inteligence spies val defendingSpy = city.civ.espionageManager.getSpyAssignedToCity(city) spyResult += defendingSpy?.getSkillModifier() ?: 0 - //TODO: Add policies modifier here val detectionString = when { spyResult < 0 -> null // Not detected @@ -228,7 +232,7 @@ class Spy() : IsPartOfGameInfoSerialization { action = SpyAction.Moving turnsRemainingForAction = 1 } - + fun canMoveTo(city: City): Boolean { if (getLocation() == city) return true if (!city.getCenterTile().isVisible(civInfo)) return false @@ -238,7 +242,7 @@ class Spy() : IsPartOfGameInfoSerialization { fun isSetUp() = action !in listOf(SpyAction.Moving, SpyAction.None, SpyAction.EstablishNetwork) // Only returns true if the spy is doing a helpful and implemented action - fun isDoingWork(): Boolean { + fun isDoingWork(): Boolean { if (action == SpyAction.StealingTech || action == SpyAction.EstablishNetwork || action == SpyAction.Moving) return true if (action == SpyAction.RiggingElections && !civInfo.isAtWarWith(getLocation()!!.civ)) return true if (action == SpyAction.CounterIntelligence && turnsRemainingForAction > 0) return true @@ -260,9 +264,9 @@ class Spy() : IsPartOfGameInfoSerialization { fun levelUpSpy() { //TODO: Make the spy level cap dependent on some unique - if (rank >= 3) return + if (rank >= 3) return if (getLocation() != null) { - civInfo.addNotification("Your spy [$name] has leveled up!", getLocation()!!.location, + civInfo.addNotification("Your spy [$name] has leveled up!", getLocation()!!.location, NotificationCategory.Espionage, NotificationIcon.Spy) } else { civInfo.addNotification("Your spy [$name] has leveled up!", @@ -275,6 +279,32 @@ class Spy() : IsPartOfGameInfoSerialization { return getSpyRank() * 30 } + /** + * Gets a friendly and enemy efficiency uniques for the spy at the location + * @return a value centered around 100 for the work efficiency of the spy, won't be negative + */ + fun getEfficiencyModifier(): Double { + lateinit var friendlyUniques: Sequence + lateinit var enemyUniques: Sequence + if (getLocation() != null) { + val city = getLocation()!! + if (city.civ == civInfo) { + friendlyUniques = city.getMatchingUniques(UniqueType.SpyEffectiveness, StateForConditionals(city), includeCivUniques = true) + enemyUniques = sequenceOf() + } else { + friendlyUniques = civInfo.getMatchingUniques(UniqueType.SpyEffectiveness) + enemyUniques = city.getMatchingUniques(UniqueType.EnemySpyEffectiveness, StateForConditionals(city), includeCivUniques = true) + } + } else { + friendlyUniques = civInfo.getMatchingUniques(UniqueType.SpyEffectiveness) + enemyUniques = sequenceOf() + } + var totalEfficiency = 1.0 + totalEfficiency *= (100.0 + friendlyUniques.sumOf { it.params[0].toInt() }) / 100 + totalEfficiency *= (100.0 + enemyUniques.sumOf { it.params[0].toInt() }) / 100 + return totalEfficiency.coerceAtLeast(0.0) + } + fun killSpy() { // We don't actually remove this spy object, we set them as dead and let them revive moveTo(null) @@ -282,6 +312,6 @@ class Spy() : IsPartOfGameInfoSerialization { turnsRemainingForAction = 5 rank = 1 } - + fun isAlive(): Boolean = action != SpyAction.Dead } diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 7506069bda..b4495804f0 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -315,6 +315,10 @@ class Building : RulesetStatsObject(), INonPerpetualConstruction { if (!civ.gameInfo.isReligionEnabled()) yield(RejectionReasonType.DisabledBySetting.toInstance()) + UniqueType.HiddenWithoutEspionage -> + if (!civ.gameInfo.isEspionageEnabled()) + yield(RejectionReasonType.DisabledBySetting.toInstance()) + UniqueType.MaxNumberBuildable -> if (civ.civConstructions.countConstructedObjects(this@Building) >= unique.params[0].toInt()) yield(RejectionReasonType.MaxNumberBuildable.toInstance()) diff --git a/core/src/com/unciv/models/ruleset/nation/Nation.kt b/core/src/com/unciv/models/ruleset/nation/Nation.kt index 34215e458c..55d55834a1 100644 --- a/core/src/com/unciv/models/ruleset/nation/Nation.kt +++ b/core/src/com/unciv/models/ruleset/nation/Nation.kt @@ -15,6 +15,7 @@ import com.unciv.ui.objectdescriptions.BaseUnitDescriptions import com.unciv.ui.objectdescriptions.BuildingDescriptions import com.unciv.ui.objectdescriptions.ImprovementDescriptions import com.unciv.ui.objectdescriptions.uniquesToCivilopediaTextLines +import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showEspionageInCivilopedia import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen.Companion.showReligionInCivilopedia import com.unciv.ui.screens.civilopediascreen.FormattedLine import kotlin.math.pow @@ -193,11 +194,13 @@ class Nation : RulesetObject() { private fun getUniqueBuildingsText(ruleset: Ruleset) = sequence { val religionEnabled = showReligionInCivilopedia(ruleset) + val espionageEnabled = showEspionageInCivilopedia(ruleset) for (building in ruleset.buildings.values) { when { building.uniqueTo != name -> continue building.hasUnique(UniqueType.HiddenFromCivilopedia) -> continue !religionEnabled && building.hasUnique(UniqueType.HiddenWithoutReligion) -> continue + !espionageEnabled && building.hasUnique(UniqueType.HiddenWithoutEspionage) -> continue } yield(FormattedLine(separator = true)) yield(FormattedLine("{${building.name}} -", link=building.makeLink())) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 920e0c9352..212447405a 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -804,7 +804,7 @@ object UniqueTriggerActivation { val currentEra = civInfo.getEra().name for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) { if (currentEra !in otherCiv.espionageManager.erasSpyEarnedFor) { - val spyName = otherCiv.espionageManager.addSpy() + val spyName = otherCiv.espionageManager.addSpy().name otherCiv.espionageManager.erasSpyEarnedFor.add(currentEra) if (otherCiv == civInfo || otherCiv.knows(civInfo)) // We don't tell which civilization entered the new era, as that is done in the notification directly above this one @@ -821,6 +821,26 @@ object UniqueTriggerActivation { } } + UniqueType.OneTimeSpiesLevelUp -> { + if (!civInfo.isMajorCiv()) return null + if (!civInfo.gameInfo.isEspionageEnabled()) return null + + return { + civInfo.espionageManager.spyList.forEach { it.levelUpSpy() } + true + } + } + + UniqueType.OneTimeGainSpy -> { + if (!civInfo.isMajorCiv()) return null + if (!civInfo.gameInfo.isEspionageEnabled()) return null + + return { + civInfo.espionageManager.addSpy() + true + } + } + UniqueType.GainFreeBuildings -> { val freeBuilding = civInfo.getEquivalentBuilding(unique.params[0]) val applicableCities = diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index f0b1b5e61d..1894f43746 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -227,6 +227,10 @@ enum class UniqueType( MayNotGenerateGreatProphet("May not generate great prophet equivalents naturally", UniqueTarget.Global), FaithCostOfGreatProphetChange("[relativeAmount]% Faith cost of generating Great Prophet equivalents", UniqueTarget.Global), + /// Espionage + SpyEffectiveness("[relativeAmount]% spy effectiveness [cityFilter]", UniqueTarget.Global, UniqueTarget.Global), + EnemySpyEffectiveness("[relativeAmount]% enemy spy effectiveness [cityFilter]", UniqueTarget.Global, UniqueTarget.Global), + /// Things you get at the start of the game StartingTech("Starting tech", UniqueTarget.Tech), StartsWithTech("Starts with [tech]", UniqueTarget.Nation), @@ -787,6 +791,8 @@ enum class UniqueType( OneTimeRevealCrudeMap("From a randomly chosen tile [positiveAmount] tiles away from the ruins, reveal tiles up to [positiveAmount] tiles away with [positiveAmount]% chance", UniqueTarget.Ruins), OneTimeGlobalAlert("Triggers the following global alert: [comment]", UniqueTarget.Triggerable), // used in Policy OneTimeGlobalSpiesWhenEnteringEra("Every major Civilization gains a spy once a civilization enters this era", UniqueTarget.Era), + OneTimeSpiesLevelUp("Promotes all spies", UniqueTarget.Triggerable), // used in Policies, Buildings + OneTimeGainSpy("Gain an extra spy", UniqueTarget.Triggerable), // used in Wonders OneTimeUnitHeal("Heal this unit by [positiveAmount] HP", UniqueTarget.UnitTriggerable), OneTimeUnitDamage("This Unit takes [positiveAmount] damage", UniqueTarget.UnitTriggerable), @@ -853,6 +859,8 @@ enum class UniqueType( HiddenWithoutReligion("Hidden when religion is disabled", UniqueTarget.Unit, UniqueTarget.Building, UniqueTarget.Ruins, UniqueTarget.Tutorial, flags = UniqueFlag.setOfHiddenToUsers), + HiddenWithoutEspionage("Hidden when espionage is disabled", UniqueTarget.Building, + flags = UniqueFlag.setOfHiddenToUsers), HiddenWithoutVictoryType("Hidden when [victoryType] Victory is disabled", UniqueTarget.Building, UniqueTarget.Unit, flags = UniqueFlag.setOfHiddenToUsers), HiddenFromCivilopedia("Will not be displayed in Civilopedia", *UniqueTarget.Displayable, flags = UniqueFlag.setOfHiddenToUsers), diff --git a/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt b/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt index 76c2d46f1e..cf8a393b99 100644 --- a/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt +++ b/core/src/com/unciv/ui/objectdescriptions/TechnologyDescriptions.kt @@ -309,20 +309,21 @@ object TechnologyDescriptions { civInfo: Civilization?, predicate: (Building) -> Boolean ): Sequence { - val (nuclearWeaponsEnabled, religionEnabled) = getNukeAndReligionSwitches(civInfo) + val (nuclearWeaponsEnabled, religionEnabled, espionageEnabled) = getNukeAndReligionSwitches(civInfo) return ruleset.buildings.values.asSequence() .filter { predicate(it) // expected to be the most selective, thus tested first && (it.uniqueTo == civInfo?.civName || it.uniqueTo == null && civInfo?.getEquivalentBuilding(it) == it) && (nuclearWeaponsEnabled || !it.hasUnique(UniqueType.EnablesNuclearWeapons)) && (religionEnabled || !it.hasUnique(UniqueType.HiddenWithoutReligion)) + && (espionageEnabled || !it.hasUnique(UniqueType.HiddenWithoutEspionage)) && !it.hasUnique(UniqueType.HiddenFromCivilopedia) } } - private fun getNukeAndReligionSwitches(civInfo: Civilization?): Pair { - if (civInfo == null) return true to true - return civInfo.gameInfo.run { gameParameters.nuclearWeaponsEnabled to isReligionEnabled() } + private fun getNukeAndReligionSwitches(civInfo: Civilization?): Triple { + if (civInfo == null) return Triple(true, true, true) + return civInfo.gameInfo.run { Triple(gameParameters.nuclearWeaponsEnabled, isReligionEnabled(), isEspionageEnabled()) } } /** diff --git a/core/src/com/unciv/ui/screens/civilopediascreen/CivilopediaScreen.kt b/core/src/com/unciv/ui/screens/civilopediascreen/CivilopediaScreen.kt index bba95e6f44..97f1f7fb36 100644 --- a/core/src/com/unciv/ui/screens/civilopediascreen/CivilopediaScreen.kt +++ b/core/src/com/unciv/ui/screens/civilopediascreen/CivilopediaScreen.kt @@ -200,12 +200,14 @@ class CivilopediaScreen( val imageSize = 50f val religionEnabled = showReligionInCivilopedia(ruleset) + val espionageEnabled = showEspionageInCivilopedia(ruleset) val victoryTypes = game.gameInfo?.gameParameters?.victoryTypes ?: ruleset.victories.keys fun shouldBeDisplayed(obj: IHasUniques): Boolean { return when { obj.hasUnique(UniqueType.HiddenFromCivilopedia) -> false (!religionEnabled && obj.hasUnique(UniqueType.HiddenWithoutReligion)) -> false + (!espionageEnabled && obj.hasUnique(UniqueType.HiddenWithoutEspionage)) -> false obj.getMatchingUniques(UniqueType.HiddenWithoutVictoryType).any { !victoryTypes.contains(it.params[0]) } -> false else -> true } @@ -326,5 +328,11 @@ class CivilopediaScreen( ruleset != null -> ruleset.beliefs.isNotEmpty() else -> true } + + fun showEspionageInCivilopedia(ruleset: Ruleset? = null) = when { + UncivGame.isCurrentInitialized() && UncivGame.Current.gameInfo != null -> + UncivGame.Current.gameInfo!!.isEspionageEnabled() + else -> true + } } } diff --git a/core/src/com/unciv/ui/screens/overviewscreen/WonderOverviewTab.kt b/core/src/com/unciv/ui/screens/overviewscreen/WonderOverviewTab.kt index 75fd1be801..3d5f1de9aa 100644 --- a/core/src/com/unciv/ui/screens/overviewscreen/WonderOverviewTab.kt +++ b/core/src/com/unciv/ui/screens/overviewscreen/WonderOverviewTab.kt @@ -98,6 +98,7 @@ class WonderInfo { val gameInfo = UncivGame.Current.gameInfo!! val ruleSet = gameInfo.ruleset private val hideReligionItems = !gameInfo.isReligionEnabled() + private val hideEspionageItems = !gameInfo.isEspionageEnabled() private val startingObsolete = ruleSet.eras[gameInfo.gameParameters.startingEra]!!.startingObsoleteWonders enum class WonderStatus(val label: String) { @@ -150,6 +151,7 @@ class WonderInfo { private fun shouldBeDisplayed(viewingPlayer: Civilization, wonder: Building, wonderEra: Int?) = when { wonder.hasUnique(UniqueType.HiddenFromCivilopedia) -> false wonder.hasUnique(UniqueType.HiddenWithoutReligion) && hideReligionItems -> false + wonder.hasUnique(UniqueType.HiddenWithoutEspionage) && hideEspionageItems -> false wonder.name in startingObsolete -> false wonder.getMatchingUniques(UniqueType.HiddenWithoutVictoryType) .any { unique -> diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 48affa5d08..0fc85393c9 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -144,6 +144,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Triggerable +??? example "Promotes all spies" + Applicable to: Triggerable + +??? example "Gain an extra spy" + Applicable to: Triggerable + ??? example "Turn this tile into a [terrainName] tile" Example: "Turn this tile into a [Forest] tile" @@ -758,6 +764,16 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Global +??? example "[relativeAmount]% spy effectiveness [cityFilter]" + Example: "[+20]% spy effectiveness [in all cities]" + + Applicable to: Global + +??? example "[relativeAmount]% enemy spy effectiveness [cityFilter]" + Example: "[+20]% enemy spy effectiveness [in all cities]" + + Applicable to: Global + ??? example "Triggers victory" Applicable to: Global @@ -1176,6 +1192,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl ??? example "Hidden when religion is disabled" Applicable to: Building, Unit, Ruins, Tutorial +??? example "Hidden when espionage is disabled" + Applicable to: Building + ??? example "Hidden when [victoryType] Victory is disabled" Example: "Hidden when [Domination] Victory is disabled" diff --git a/tests/src/com/unciv/logic/civilization/EspionageTests.kt b/tests/src/com/unciv/logic/civilization/EspionageTests.kt new file mode 100644 index 0000000000..f6aaa1fb02 --- /dev/null +++ b/tests/src/com/unciv/logic/civilization/EspionageTests.kt @@ -0,0 +1,57 @@ +package com.unciv.logic.civilization + +import com.badlogic.gdx.math.Vector2 +import com.unciv.testing.GdxTestRunner +import com.unciv.testing.TestGame +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(GdxTestRunner::class) +class EspionageTests { + private val testGame = TestGame() + + val civA = testGame.addCiv() + val civB = testGame.addCiv() + @Before + fun setup() { + testGame.gameInfo.gameParameters.espionageEnabled = true + civA.diplomacyFunctions.makeCivilizationsMeet(civB) + testGame.makeHexagonalMap(3) + } + + @Test + fun `Espionage manager add spy`() { + val espionageManagerA = civA.espionageManager + assertEquals(0, espionageManagerA.spyList.size) + espionageManagerA.addSpy() + assertEquals(1, espionageManagerA.spyList.size) + } + + @Test + fun `Espionage check spy effectiveness reduction unique`() { + val espionageManagerA = civA.espionageManager + val spy = espionageManagerA.addSpy() + val city = civB.addCity(Vector2(1f,1f)) + spy.moveTo(city) + assertEquals(1.0, spy.getEfficiencyModifier(), 0.1) + city.cityConstructions.addBuilding("Constabulary") + assertEquals(0.75, spy.getEfficiencyModifier(), 0.1) + } + + @Test + fun `Spy effectiveness can't go below zero`() { + val espionageManagerA = civA.espionageManager + val spy = espionageManagerA.addSpy() + val city = civB.addCity(Vector2(1f,1f)) + spy.moveTo(city) + city.cityConstructions.addBuilding("Constabulary") + city.cityConstructions.addBuilding("Police Station") + city.cityConstructions.addBuilding("National Intelligence Agency") + city.cityConstructions.addBuilding("Great Firewall") + assertTrue(spy.getEfficiencyModifier() >= 0) + } + +}