From 0a41417fdda80c2e08c6cb569c37b72be2c32c68 Mon Sep 17 00:00:00 2001 From: General_E <121508218+Emandac@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:31:57 +0200 Subject: [PATCH] Add don't spy on us has a demand (#13351) * Settler settle best tile when not escort and dangerous Tiles instead of running away Settler unit will now settle on best tile in dangerous Tiles without escort instead of running away. * Update WorkerAutomation.kt * Update SpecificUnitAutomation.kt * Update WorkerAutomation.kt * Update SpecificUnitAutomation.kt * Now city states get mad when you steal their Lands * new version * change to getDiplomacyManagerOrMeet * added text to template.properties and changed AlertPopup.kt * Update template.properties * with period at the end :b * add flag now * Made Option to declare war when a city state is bullied unavailable * added option to change the Maximum Autosave turns stored * remove print * change letter * should fix issue with building test * update with changes * Added UniqueType.FoundPuppetCity with "Founds a new puppet city" in "uniques" of an unit in Units.json. Making it so you can now settle a puppet city. * Added save promotion * Updated for PR * Updated with requested changes * Removed unnecessary check * updated PR * Update PromotionPickerScreen.kt to save promotion cells too * change name and added ! * updated name of variable * updated version from unitType to BaseUnit * updated variable name * Added unitType to reduce the xp cost of promotions for all units in a civ This was a unique type that the Zulu have in civ 5 * updated name * remove UniqueTarget.FollowerBelief * Experience from to XP * fix ? * XP * change it back to Experience because it didn't want to build on git :( * back to XP then * update auto promotion and fix negative XP on unit * Fix build issues and remove the XPForPromotionModifier from xpForNextPromotion and xpForNextNPromotions * remove XPForPromotionModifier * re added Statuses and remove duplicate comment * remove some white space and 1 used import "com.unciv.ui.components.extensions.toPercent" * remove unique from uniques.md * update 0 * name change from spys to spies * Now in a working state * change println * mini update * final update * update * mini update * Update * updated it for the ai can bypass agreement to stop spaying on x civ * updated addSpyingOnUsDespiteOurPromise text text by itanasi * fixed build issue ? * Apply suggestions from code review * update comment * Update core/src/com/unciv/models/Spy.kt Co-authored-by: Yair Morgenstern * If true it will return false in moveTo function * Update * Update AlertPopup.kt --------- Co-authored-by: Yair Morgenstern --- .../jsons/translations/template.properties | 9 ++++- .../civilization/NextTurnAutomation.kt | 32 ++++++++++++++++- .../unciv/logic/civilization/PopupAlert.kt | 3 ++ .../diplomacy/DiplomacyManager.kt | 22 ++++++++++++ .../diplomacy/DiplomacyTurnManager.kt | 1 + core/src/com/unciv/models/Spy.kt | 34 +++++++++++++++++-- .../diplomacyscreen/MajorCivDiplomacyTable.kt | 28 +++++++++++++++ .../ui/screens/worldscreen/AlertPopup.kt | 26 ++++++++++++++ 8 files changed, 151 insertions(+), 4 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 0f10fcf57e..88bd5336b7 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -228,7 +228,12 @@ Please don't spread your religion to us. = Don't spread religion in our cities. = Very well, we shall spread our faith elsewhere. = We noticed you have continued spreading your faith, despite your promise. This will have...consequences. = - + +Stop spying on us. = +We see our people are not welcome in your lands... we will take our attention elsewhere. = +I'll do what's necessary for my empire to survive. = +Take back your spy and your broken promises. = + I've been informed that my armies have taken tribute from [civName], a city-state under your protection.\nI assure you, this was quite unintentional, and I hope that this does not serve to drive us apart. = We asked [civName] for a tribute recently and they gave in.\nYou promised to protect them from such things, but we both know you cannot back that up. = It's come to my attention that I may have attacked [civName].\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action. = @@ -1098,6 +1103,8 @@ One of our trades with [nation] has been cut short = [nation] refused to stop settling cities near us! = [nation] agreed to stop spreading religion to us! = [nation] refused to stop spreading religion to us! = +[nation] agreed to stop spying on us!" = +[nation] refused to stop spying on us! = We have allied with [nation]. = We have lost alliance with [nation]. = We have discovered [naturalWonder]! = diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index 70f66ba531..ed690d9947 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -110,7 +110,6 @@ object NextTurnAutomation { } private fun respondToPopupAlerts(civInfo: Civilization) { for (popupAlert in civInfo.popupAlerts.toList()) { // toList because this can trigger other things that give alerts, like Golden Age - if (popupAlert.type == AlertType.DemandToStopSettlingCitiesNear) { // we're called upon to make a decision val demandingCiv = civInfo.gameInfo.getCivilization(popupAlert.value) val diploManager = civInfo.getDiplomacyManager(demandingCiv)!! @@ -127,6 +126,15 @@ object NextTurnAutomation { diploManager.agreeNotToSpreadReligionTo() else diploManager.refuseNotToSpreadReligionTo() } + + if (popupAlert.type == AlertType.DemandToStopSpyingOnUs) { + val demandingCiv = civInfo.gameInfo.getCivilization(popupAlert.value) + val diploManager = civInfo.getDiplomacyManager(demandingCiv)!! + if (Automation.threatAssessment(civInfo, demandingCiv) >= ThreatLevel.High + || diploManager.isRelationshipLevelGT(RelationshipLevel.Ally)) + diploManager.agreeNotToSpreadSpiesTo() + else diploManager.refuseNotToSpreadSpiesTo() + } if (popupAlert.type == AlertType.DeclarationOfFriendship) { val requestingCiv = civInfo.gameInfo.getCivilization(popupAlert.value) @@ -596,6 +604,8 @@ object NextTurnAutomation { onCitySettledNearBorders(civInfo, otherCiv) if (diploManager.hasFlag(DiplomacyFlags.SpreadReligionInOurCities)) onReligionSpreadInOurCity(civInfo, otherCiv) + if (diploManager.hasFlag(DiplomacyFlags.DiscoveredSpiesInOurCities)) + onSpyDiscoveredInOurCity(civInfo, otherCiv) } } @@ -637,6 +647,26 @@ object NextTurnAutomation { } diplomacyManager.removeFlag(DiplomacyFlags.SpreadReligionInOurCities) } + + private fun onSpyDiscoveredInOurCity(civInfo: Civilization, otherCiv: Civilization) { + val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! + when { + diplomacyManager.hasFlag(DiplomacyFlags.IgnoreThemSendingSpies) -> {} + diplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSendSpies) -> { + otherCiv.popupAlerts.add(PopupAlert(AlertType.SpyingOnUsDespiteOurPromise, civInfo.civName)) + diplomacyManager.setFlag(DiplomacyFlags.IgnoreThemSendingSpies, 100) + diplomacyManager.setModifier(DiplomaticModifiers.BetrayedPromiseToNotSendingSpiesToUs, -20f) + diplomacyManager.removeFlag(DiplomacyFlags.AgreedToNotSendSpies) + } + else -> { + val threatLevel = Automation.threatAssessment(civInfo, otherCiv) + if (threatLevel < ThreatLevel.High) // don't piss them off for no reason please. + otherCiv.popupAlerts.add(PopupAlert(AlertType.DemandToStopSpyingOnUs, civInfo.civName)) + } + } + diplomacyManager.removeFlag(DiplomacyFlags.DiscoveredSpiesInOurCities) + } + fun getMinDistanceBetweenCities(civ1: Civilization, civ2: Civilization): Int { return getClosestCities(civ1, civ2)?.aerialDistance ?: Int.MAX_VALUE diff --git a/core/src/com/unciv/logic/civilization/PopupAlert.kt b/core/src/com/unciv/logic/civilization/PopupAlert.kt index 787d691327..75f7bfe1fc 100644 --- a/core/src/com/unciv/logic/civilization/PopupAlert.kt +++ b/core/src/com/unciv/logic/civilization/PopupAlert.kt @@ -18,6 +18,9 @@ enum class AlertType : IsPartOfGameInfoSerialization { DemandToStopSpreadingReligion, ReligionSpreadDespiteOurPromise, + + DemandToStopSpyingOnUs, + SpyingOnUsDespiteOurPromise, GoldenAge, DeclarationOfFriendship, diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 17ac1ebd91..17eaf45bff 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -62,6 +62,10 @@ enum class DiplomacyFlags { AgreedToNotSpreadReligion, IgnoreThemSpreadingReligion, + DiscoveredSpiesInOurCities, + AgreedToNotSendSpies, + IgnoreThemSendingSpies, + ProvideMilitaryUnit, MarriageCooldown, NotifiedAfraid, @@ -94,8 +98,11 @@ enum class DiplomaticModifiers(val text: String) { DenouncedOurAllies("You have denounced our allies"), RefusedToNotSettleCitiesNearUs("You refused to stop settling cities near us"), RefusedToNotSpreadReligionToUs("You refused to stop spreading religion to us"), + RefusedToNotSendingSpiesToUs("You refused to stop spying on us"), BetrayedPromiseToNotSettleCitiesNearUs("You betrayed your promise to not settle cities near us"), BetrayedPromiseToNotSpreadReligionToUs("You betrayed your promise to not spread your religion to us"), + BetrayedPromiseToNotSendingSpiesToUs("You betrayed your promise to stop spying on us"), + UnacceptableDemands("Your arrogant demands are in bad taste"), UsedNuclearWeapons("Your use of nuclear weapons is disgusting!"), StealingTerritory("You have stolen our lands!"), @@ -706,6 +713,21 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { otherCiv().addNotification("[${civInfo.civName}] refused to stop spreading religion to us!", NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName) } + + fun agreeNotToSpreadSpiesTo() { + otherCivDiplomacy().setFlag(DiplomacyFlags.AgreedToNotSendSpies, 100) + addModifier(DiplomaticModifiers.UnacceptableDemands, -10f) + otherCiv().addNotification("[${civInfo.civName}] agreed to stop spying on us!", + NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName) + } + + fun refuseNotToSpreadSpiesTo() { + addModifier(DiplomaticModifiers.UnacceptableDemands, -20f) + otherCivDiplomacy().setFlag(DiplomacyFlags.IgnoreThemSendingSpies, 100) + otherCivDiplomacy().addModifier(DiplomaticModifiers.RefusedToNotSendingSpiesToUs, -15f) + otherCiv().addNotification("[${civInfo.civName}] refused to stop spying on us!", + NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName) + } fun sideWithCityState() { otherCivDiplomacy().setModifier(DiplomaticModifiers.SidedWithProtectedMinor, -5f) diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyTurnManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyTurnManager.kt index ed3e9c4f0c..23509cdfa7 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyTurnManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyTurnManager.kt @@ -286,6 +286,7 @@ object DiplomacyTurnManager { revertToZero(DiplomaticModifiers.RefusedToNotSettleCitiesNearUs, 1 / 4f) revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, 1 / 8f) // That's a bastardly thing to do revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSpreadReligionToUs, 1 / 8f) + revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSendingSpiesToUs, 1 / 8f) revertToZero(DiplomaticModifiers.UnacceptableDemands, 1 / 4f) revertToZero(DiplomaticModifiers.StealingTerritory, 1 / 4f) revertToZero(DiplomaticModifiers.DenouncedOurAllies, 1 / 4f) diff --git a/core/src/com/unciv/models/Spy.kt b/core/src/com/unciv/models/Spy.kt index 4496e6d439..69a51922e2 100644 --- a/core/src/com/unciv/models/Spy.kt +++ b/core/src/com/unciv/models/Spy.kt @@ -9,6 +9,7 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.EspionageAction import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.NotificationIcon +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.managers.EspionageManager import com.unciv.models.ruleset.unique.Unique @@ -226,7 +227,6 @@ class Spy private constructor() : IsPartOfGameInfoSerialization { if (detectionString != null) // Not using Spy.addNotification, shouldn't open the espionage screen otherCiv.addNotification(detectionString, city.location, NotificationCategory.Espionage, NotificationIcon.Spy) - if (spyResult < 200 && stolenTech != null) { civInfo.tech.addTechnology(stolenTech) addNotification("Your spy [$name] stole the Technology [$stolenTech] from [$city]!") @@ -237,12 +237,20 @@ class Spy private constructor() : IsPartOfGameInfoSerialization { addNotification("Your spy [$name] was killed trying to steal Technology in [$city]!") defendingSpy?.levelUpSpy() killSpy() + // if they kill your spy they should know that you are spying on them and able to demande to not be speid on. + demandeToNotBeSpiedOn(otherCiv) } else startStealingTech() // reset progress if (spyResult >= 100) { - otherCiv.getDiplomacyManager(civInfo)?.addModifier(DiplomaticModifiers.SpiedOnUs, -15f) + demandeToNotBeSpiedOn(otherCiv) } } + + private fun demandeToNotBeSpiedOn(otherCiv: Civilization) { + val otherCivDiplomacyManager = otherCiv.getDiplomacyManager(civInfo)!! + otherCivDiplomacyManager.addModifier(DiplomaticModifiers.SpiedOnUs, -15f) + otherCivDiplomacyManager.setFlag(DiplomacyFlags.DiscoveredSpiesInOurCities, 30) + } fun canDoCoup(): Boolean = getCityOrNull() != null && getCity().civ.isCityState && isSetUp() && getCity().civ.getAllyCivName() != civInfo.civName @@ -340,8 +348,30 @@ class Spy private constructor() : IsPartOfGameInfoSerialization { this.city = city setAction(SpyAction.Moving, 1) } + + private fun canDismissAgreementToNotSendSpies(city: City): Boolean { + val otherCivDiplomacyManager = city.civ.getDiplomacyManager(civInfo) ?: return false + val otherCiv = otherCivDiplomacyManager.civInfo + + val techSize = civInfo.gameInfo.ruleset.technologies.values.size.toFloat() + val ourResearchTechSize = civInfo.tech.techsResearched.size.toFloat() + val otherCivResearchTechSize = otherCiv.tech.techsResearched.size.toFloat() + + val ourTechResearchLevel = ((ourResearchTechSize/techSize)*100f) + val otherCivTechResearchLevel = ((otherCivResearchTechSize/techSize)*100f) + if (otherCivDiplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSendSpies) && + /* + * if there is equal or more than 10% difference in tech, + * compare to the other civ in tech we the Ai will bypass the agreement + * */ + otherCivTechResearchLevel <= ourTechResearchLevel + 10f) { + return true + } + return false + } fun canMoveTo(city: City): Boolean { + if (canDismissAgreementToNotSendSpies(city)) return false if (getCityOrNull() == city) return true if (!city.getCenterTile().isExplored(civInfo)) return false return espionageManager.getSpyAssignedToCity(city) == null diff --git a/core/src/com/unciv/ui/screens/diplomacyscreen/MajorCivDiplomacyTable.kt b/core/src/com/unciv/ui/screens/diplomacyscreen/MajorCivDiplomacyTable.kt index 294bf4ed9b..6f61d1ff56 100644 --- a/core/src/com/unciv/ui/screens/diplomacyscreen/MajorCivDiplomacyTable.kt +++ b/core/src/com/unciv/ui/screens/diplomacyscreen/MajorCivDiplomacyTable.kt @@ -203,6 +203,16 @@ class MajorCivDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) { "They promised not to spread religion to us ([${diplomacyManager.getFlag(DiplomacyFlags.AgreedToNotSpreadReligion)}] turns remaining)" promisesTable.add(text.toLabel(Color.LIGHT_GRAY)).row() } + if (otherCivDiplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSendSpies)) { + val text = + "We promised not to send spies to them ([${otherCivDiplomacyManager.getFlag(DiplomacyFlags.AgreedToNotSendSpies)}] turns remaining)" + promisesTable.add(text.toLabel(Color.LIGHT_GRAY)).row() + } + if (diplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSendSpies)) { + val text = + "They promised not to send spies to us ([${diplomacyManager.getFlag(DiplomacyFlags.AgreedToNotSendSpies)}] turns remaining)" + promisesTable.add(text.toLabel(Color.LIGHT_GRAY)).row() + } return if (promisesTable.cells.isEmpty) null else promisesTable } @@ -257,6 +267,24 @@ class MajorCivDiplomacyTable(private val diplomacyScreen: DiplomacyScreen) { dontSpreadReligionButton.disable() } demandsTable.add(dontSpreadReligionButton).row() + + if (viewingCiv.gameInfo.gameParameters.espionageEnabled) { + val dontSpyButton = "Stop spying on us.".toTextButton() + val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv)!! + if (otherCiv.popupAlerts.any { it.type == AlertType.DemandToStopSpyingOnUs && it.value == viewingCiv.civName} || + diplomacyManager.hasFlag(DiplomacyFlags.AgreedToNotSendSpies)) + dontSpyButton.disable() + dontSpyButton.onClick { + otherCiv.popupAlerts.add( + PopupAlert( + AlertType.DemandToStopSpyingOnUs, + viewingCiv.civName + ) + ) + dontSpyButton.disable() + } + demandsTable.add(dontSpyButton).row() + } demandsTable.add(Constants.close.toTextButton().onClick { diplomacyScreen.updateRightSide(otherCiv) }) return demandsTable diff --git a/core/src/com/unciv/ui/screens/worldscreen/AlertPopup.kt b/core/src/com/unciv/ui/screens/worldscreen/AlertPopup.kt index 1344f039c9..a5dc177414 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/AlertPopup.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/AlertPopup.kt @@ -98,6 +98,8 @@ class AlertPopup( AlertType.CitySettledNearOtherCivDespiteOurPromise -> shouldOpen = addCitySettledNearOtherCivDespiteOurPromise() AlertType.DemandToStopSpreadingReligion -> shouldOpen = addDemandToStopSpreadingReligion() AlertType.ReligionSpreadDespiteOurPromise -> shouldOpen = addReligionSpreadDespiteOurPromise() + AlertType.DemandToStopSpyingOnUs -> shouldOpen = addDemandToStopSendingSpiesToUs() + AlertType.SpyingOnUsDespiteOurPromise -> shouldOpen = addSpyingOnUsDespiteOurPromise() AlertType.DeclarationOfFriendship -> shouldOpen = addDeclarationOfFriendship() AlertType.BulliedProtectedMinor, AlertType.AttackedProtectedMinor, AlertType.AttackedAllyMinor -> shouldOpen = addBulliedOrAttackedProtectedOrAlliedMinor() @@ -298,6 +300,30 @@ class AlertPopup( addCloseButton("Very well.") return true } + private fun addDemandToStopSendingSpiesToUs(): Boolean { + val otherciv = getCiv(popupAlert.value) + if (otherciv.isDefeated()) return false + val playerDiploManager = viewingCiv.getDiplomacyManager(otherciv)!! + addLeaderName(otherciv) + addGoodSizedLabel("Stop spying on us.").row() + addCloseButton("We see our people are not welcome in your lands... we will take our attention elsewhere.", KeyboardBinding.Confirm) { + playerDiploManager.agreeNotToSpreadSpiesTo() + }.row() + addCloseButton("I'll do what's necessary for my empire to survive.", KeyboardBinding.Cancel) { + playerDiploManager.refuseNotToSpreadSpiesTo() + } + return true + } + + private fun addSpyingOnUsDespiteOurPromise(): Boolean { + val otherciv = getCiv(popupAlert.value) + if (otherciv.isDefeated()) return false + addGoodSizedLabel("Take back your spy and your broken promises.").row() + addCloseButton("Very well.") + return true + } + + private fun addDiplomaticMarriage() { val city = getCity(popupAlert.value)