From d59fe45f5121ba0cd577b166e9f87d105bb199f5 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Sat, 11 Sep 2021 20:10:45 +0200 Subject: [PATCH] Proper implementation of pledge to protect (#5165) * penalties for attacking * proper timers on penalties * template.properties * space * Player can pick sides when protected civs attacked/bullied Protect gives resting point 10 for influence * correct string --- .../jsons/translations/template.properties | 14 ++++ core/src/com/unciv/logic/battle/Battle.kt | 2 + .../logic/civilization/CityStateFunctions.kt | 77 ++++++++++++++++++- .../logic/civilization/CivilizationInfo.kt | 11 ++- .../unciv/logic/civilization/PopupAlert.kt | 2 + .../diplomacy/DiplomacyManager.kt | 54 ++++++++++++- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 14 +++- .../com/unciv/ui/worldscreen/AlertPopup.kt | 57 +++++++++++++- 8 files changed, 216 insertions(+), 15 deletions(-) diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index ac10799c9f..68b29fb713 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -103,6 +103,11 @@ Do you want to break your promise to [leaderName]? = We promised not to settle near them ([count] turns remaining) = They promised not to settle near us ([count] turns remaining) = +[civName] is upset that you demanded tribute from [cityState], whom they have pledged to protect! = +[civName] is upset that you attacked [cityState], whom they have pledged to protect! = +[civName] is outraged that you destroyed [cityState], whom they had pledged to protect! = +[civName] has destroyed [cityState], whom you had pledged to protect! = + Unforgivable = Afraid = Enemy = @@ -139,12 +144,20 @@ Your arrogant demands are in bad taste = Your use of nuclear weapons is disgusting! = You have stolen our lands! = You gave us units! = +You destroyed City States that were under our protection! = +You attacked City States that were under our protection! = +You demanded tribute from City States that were under our protection! = +You sided with a City State over us = Demands = Please don't settle new cities near us. = Very well, we shall look for new lands to settle. = We shall do as we please. = We noticed your new city near our borders, despite your promise. This will have....implications. = +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], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action. = +I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [civName] will make a fine addition to my own. = Enter the amount of gold = @@ -168,6 +181,7 @@ Gift Improvement = Diplomatic Marriage ([amount] Gold) = We have married into the ruling family of [civName], bringing them under our control. = [civName] has married into the ruling family of [civName2], bringing them under their control. = +You have broken your Pledge to Protect [civName]! = Cultured = Maritime = diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index efc9290496..42a589c735 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -495,6 +495,8 @@ object Battle { fun destroyIfDefeated(attackedCiv: CivilizationInfo, attacker: CivilizationInfo) { if (attackedCiv.isDefeated()) { + if (attackedCiv.isCityState()) + attackedCiv.cityStateDestroyed(attacker) attackedCiv.destroy() attacker.popupAlerts.add(PopupAlert(AlertType.Defeated, attackedCiv.civName)) } diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 8b63963c67..f720633040 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -3,6 +3,7 @@ package com.unciv.logic.civilization import com.unciv.Constants import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.models.metadata.GameSpeed @@ -152,8 +153,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { diplomacy.setFlag(DiplomacyFlags.RecentlyPledgedProtection, 10) // Can't break for 10 turns } - fun removeProtectorCiv(otherCiv: CivilizationInfo) { - if(!otherCivCanWithdrawProtection(otherCiv)) + fun removeProtectorCiv(otherCiv: CivilizationInfo, forced: Boolean = false) { + if(!otherCivCanWithdrawProtection(otherCiv) && !forced) return val diplomacy = civInfo.getDiplomacyManager(otherCiv) @@ -378,6 +379,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { val goldAmount = goldGainedByTribute() demandingCiv.addGold(goldAmount) civInfo.getDiplomacyManager(demandingCiv).addInfluence(-15f) + cityStateBullied(demandingCiv) civInfo.addFlag(CivFlags.RecentlyBullied.name, 20) updateAllyCivForCityState() civInfo.updateStatsForNextTurn() @@ -395,6 +397,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { demandingCiv.placeUnitNearTile(civInfo.getCapital().location, buildableWorkerLikeUnits.keys.random()) civInfo.getDiplomacyManager(demandingCiv).addInfluence(-50f) + cityStateBullied(demandingCiv) civInfo.addFlag(CivFlags.RecentlyBullied.name, 20) updateAllyCivForCityState() } @@ -478,4 +481,74 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { diplomacy.setFlag(DiplomacyFlags.AngerFreeIntrusion, 5) } + /** A city state was bullied. What are its protectors going to do about it??? */ + private fun cityStateBullied(bully: CivilizationInfo) { + if (!civInfo.isCityState()) return // What are we doing here? + + for (protector in civInfo.getProtectorCivs()) { + if (!protector.knows(bully)) // Who? + continue + val protectorDiplomacy = protector.getDiplomacyManager(bully) + if (protectorDiplomacy.hasModifier(DiplomaticModifiers.BulliedProtectedMinor) + && protectorDiplomacy.getFlag(DiplomacyFlags.RememberBulliedProtectedMinor) > 50) + protectorDiplomacy.addModifier(DiplomaticModifiers.BulliedProtectedMinor, -10f) // Penalty less severe for second offence + else + protectorDiplomacy.addModifier(DiplomaticModifiers.BulliedProtectedMinor, -15f) + protectorDiplomacy.setFlag(DiplomacyFlags.RememberBulliedProtectedMinor, 75) // Reset their memory + + if (protector.playerType != PlayerType.Human) // Humans can have their own emotions + bully.addNotification("[${protector.civName}] is upset that you demanded tribute from [${civInfo.civName}], whom they have pledged to protect!", + NotificationIcon.Diplomacy, protector.civName) + else // Let humans choose who to side with + protector.popupAlerts.add(PopupAlert(AlertType.BulliedProtectedMinor, + bully.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera + } + } + + /** A city state was attacked. What are its protectors going to do about it??? */ + fun cityStateAttacked(attacker: CivilizationInfo) { + if (!civInfo.isCityState()) return // What are we doing here? + + for (protector in civInfo.getProtectorCivs()) { + if (!protector.knows(attacker)) // Who? + continue + val protectorDiplomacy = protector.getDiplomacyManager(attacker) + if (protectorDiplomacy.hasModifier(DiplomaticModifiers.AttackedProtectedMinor) + && protectorDiplomacy.getFlag(DiplomacyFlags.RememberAttackedProtectedMinor) > 50) + protectorDiplomacy.addModifier(DiplomaticModifiers.AttackedProtectedMinor, -15f) // Penalty less severe for second offence + else + protectorDiplomacy.addModifier(DiplomaticModifiers.AttackedProtectedMinor, -20f) + protectorDiplomacy.setFlag(DiplomacyFlags.RememberAttackedProtectedMinor, 75) // Reset their memory + + if (protector.playerType != PlayerType.Human) // Humans can have their own emotions + attacker.addNotification("[${protector.civName}] is upset that you attacked [${civInfo.civName}], whom they have pledged to protect!", + NotificationIcon.Diplomacy, protector.civName) + else // Let humans choose who to side with + protector.popupAlerts.add(PopupAlert(AlertType.AttackedProtectedMinor, + attacker.civName + "@" + civInfo.civName)) // we need to pass both civs as argument, hence the horrible chimera + } + } + + /** A city state was destroyed. Its protectors are going to be upset! */ + fun cityStateDestroyed(attacker: CivilizationInfo) { + if (!civInfo.isCityState()) return // What are we doing here? + + for (protector in civInfo.getProtectorCivs()) { + if (!protector.knows(attacker)) // Who? + continue + val protectorDiplomacy = protector.getDiplomacyManager(attacker) + if (protectorDiplomacy.hasModifier(DiplomaticModifiers.DestroyedProtectedMinor)) + protectorDiplomacy.addModifier(DiplomaticModifiers.DestroyedProtectedMinor, -10f) // Penalty less severe for second offence + else + protectorDiplomacy.addModifier(DiplomaticModifiers.DestroyedProtectedMinor, -40f) // Oof + protectorDiplomacy.setFlag(DiplomacyFlags.RememberDestroyedProtectedMinor, 125) // Reset their memory + + if (protector.playerType != PlayerType.Human) // Humans can have their own emotions + attacker.addNotification("[${protector.civName}] is outraged that you destroyed [${civInfo.civName}], whom they had pledged to protect!", + NotificationIcon.Diplomacy, protector.civName) + protector.addNotification("[${attacker.civName}] has destroyed [${civInfo.civName}], whom you had pledged to protect!", attacker.civName, + NotificationIcon.Death, civInfo.civName) + } + } + } \ No newline at end of file diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 843077094a..c6d65f76f2 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -916,8 +916,8 @@ class CivilizationInfo { fun addProtectorCiv(otherCiv: CivilizationInfo) { cityStateFunctions.addProtectorCiv(otherCiv) } - fun removeProtectorCiv(otherCiv: CivilizationInfo) { - cityStateFunctions.removeProtectorCiv(otherCiv) + fun removeProtectorCiv(otherCiv: CivilizationInfo, forced: Boolean = false) { + cityStateFunctions.removeProtectorCiv(otherCiv, forced) } fun otherCivCanPledgeProtection(otherCiv: CivilizationInfo) = cityStateFunctions.otherCivCanPledgeProtection(otherCiv) fun otherCivCanWithdrawProtection(otherCiv: CivilizationInfo) = cityStateFunctions.otherCivCanWithdrawProtection(otherCiv) @@ -954,6 +954,13 @@ class CivilizationInfo { fun getAllyCiv() = allyCivName fun setAllyCiv(newAllyName: String?) { allyCivName = newAllyName } + fun cityStateAttacked(attacker: CivilizationInfo) { + cityStateFunctions.cityStateAttacked(attacker) + } + fun cityStateDestroyed(attacker: CivilizationInfo) { + cityStateFunctions.cityStateDestroyed(attacker) + } + //endregion } diff --git a/core/src/com/unciv/logic/civilization/PopupAlert.kt b/core/src/com/unciv/logic/civilization/PopupAlert.kt index 672f475623..9dc7e1fb2a 100644 --- a/core/src/com/unciv/logic/civilization/PopupAlert.kt +++ b/core/src/com/unciv/logic/civilization/PopupAlert.kt @@ -14,6 +14,8 @@ enum class AlertType { DeclarationOfFriendship, StartIntro, DiplomaticMarriage, + BulliedProtectedMinor, + AttackedProtectedMinor, } class PopupAlert { diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index d354137863..9ae2d47a7b 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -45,6 +45,10 @@ enum class DiplomacyFlags { RecentlyPledgedProtection, RecentlyWithdrewProtection, AngerFreeIntrusion, + RememberDestroyedProtectedMinor, + RememberAttackedProtectedMinor, + RememberBulliedProtectedMinor, + RememberSidedWithProtectedMinor, Denunciation } @@ -70,7 +74,11 @@ enum class DiplomaticModifiers { DenouncedOurEnemies, OpenBorders, FulfilledPromiseToNotSettleCitiesNearUs, - GaveUsUnits + GaveUsUnits, + DestroyedProtectedMinor, + AttackedProtectedMinor, + BulliedProtectedMinor, + SidedWithProtectedMinor, } class DiplomacyManager() { @@ -140,7 +148,13 @@ class DiplomacyManager() { return 0 } - fun opinionOfOtherCiv() = diplomaticModifiers.values.sum() + fun opinionOfOtherCiv(): Float { + var modifierSum = diplomaticModifiers.values.sum() + // Angry about attacked CS and destroyed CS do not stack + if (hasModifier(DiplomaticModifiers.DestroyedProtectedMinor) && hasModifier(DiplomaticModifiers.AttackedProtectedMinor)) + modifierSum -= getModifier(DiplomaticModifiers.AttackedProtectedMinor) + return modifierSum + } fun relationshipLevel(): RelationshipLevel { if (civInfo.isPlayerCivilization() && otherCiv().isPlayerCivilization()) @@ -223,7 +237,7 @@ class DiplomacyManager() { if (otherCiv().religionManager.religion?.name == civInfo.getCapital().religion.getMajorityReligionName()) restingPoint += unique.params[0].toInt() - if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 5 + if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10 return restingPoint } @@ -458,6 +472,18 @@ class DiplomacyManager() { if (flag == DiplomacyFlags.ResearchAgreement.name) totalOfScienceDuringRA += civInfo.statsForNextTurn.science.toInt() + // These modifiers decrease slightly @ 50 + if (flagsCountdown[flag] == 50) { + when (flag) { + DiplomacyFlags.RememberAttackedProtectedMinor.name -> { + addModifier(DiplomaticModifiers.AttackedProtectedMinor, 5f) + } + DiplomacyFlags.RememberBulliedProtectedMinor.name -> { + addModifier(DiplomaticModifiers.BulliedProtectedMinor, 5f) + } + } + } + // Only when flag is expired if (flagsCountdown[flag] == 0) { when (flag) { @@ -476,6 +502,19 @@ class DiplomacyManager() { DiplomacyFlags.AgreedToNotSettleNearUs.name -> { addModifier(DiplomaticModifiers.FulfilledPromiseToNotSettleCitiesNearUs, 10f) } + // These modifiers don't tick down normally, instead there is a threshold number of turns + DiplomacyFlags.RememberDestroyedProtectedMinor.name -> { // 125 + removeModifier(DiplomaticModifiers.DestroyedProtectedMinor) + } + DiplomacyFlags.RememberAttackedProtectedMinor.name -> { // 75 + removeModifier(DiplomaticModifiers.AttackedProtectedMinor) + } + DiplomacyFlags.RememberBulliedProtectedMinor.name -> { // 75 + removeModifier(DiplomaticModifiers.BulliedProtectedMinor) + } + DiplomacyFlags.RememberSidedWithProtectedMinor.name -> { // 25 + removeModifier(DiplomaticModifiers.SidedWithProtectedMinor) + } } flagsCountdown.remove(flag) @@ -601,7 +640,10 @@ class DiplomacyManager() { } otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f) - if (otherCiv.isCityState()) otherCivDiplomacy.setInfluence(-60f) + if (otherCiv.isCityState()) { + otherCivDiplomacy.setInfluence(-60f) + otherCiv.cityStateAttacked(civInfo) + } for (thirdCiv in civInfo.getKnownCivs()) { if (thirdCiv.isAtWarWith(otherCiv)) { @@ -785,6 +827,10 @@ class DiplomacyManager() { otherCiv().addNotification("[${civInfo.civName}] refused to stop settling cities near us!", NotificationIcon.Diplomacy, civInfo.civName) } + fun sideWithCityState() { + otherCivDiplomacy().setModifier(DiplomaticModifiers.SidedWithProtectedMinor, -5f) + otherCivDiplomacy().setFlag(DiplomacyFlags.RememberSidedWithProtectedMinor, 25) + } //endregion } diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 4a3818b786..0199e0f816 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -8,11 +8,8 @@ import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.civilization.* -import com.unciv.logic.civilization.diplomacy.DiplomacyFlags -import com.unciv.logic.civilization.diplomacy.DiplomacyManager +import com.unciv.logic.civilization.diplomacy.* import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers.* -import com.unciv.logic.civilization.diplomacy.DiplomaticStatus -import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.trade.TradeLogic import com.unciv.logic.trade.TradeOffer import com.unciv.logic.trade.TradeType @@ -667,6 +664,11 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { private fun getDiplomacyModifiersTable(otherCivDiplomacyManager: DiplomacyManager): Table { val diplomacyModifiersTable = Table() for (modifier in otherCivDiplomacyManager.diplomaticModifiers) { + // Angry about attacked CS and destroyed CS do not stack + if (modifier.key == AttackedProtectedMinor.name + && otherCivDiplomacyManager.hasModifier(DestroyedProtectedMinor)) + continue + var text = when (valueOf(modifier.key)) { DeclaredWarOnUs -> "You declared war on us!" WarMongerer -> "Your warmongering ways are unacceptable to us." @@ -689,6 +691,10 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() { UsedNuclearWeapons -> "Your use of nuclear weapons is disgusting!" StealingTerritory -> "You have stolen our lands!" GaveUsUnits -> "You gave us units!" + DestroyedProtectedMinor -> "You destroyed City States that were under our protection!" + AttackedProtectedMinor -> "You attacked City States that were under our protection!" + BulliedProtectedMinor -> "You demanded tribute from City States that were under our protection!" + SidedWithProtectedMinor -> "You sided with a City State over us" } text = text.tr() + " " if (modifier.value > 0) text += "+" diff --git a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt index ffe41e1d39..1252f02baf 100644 --- a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt @@ -4,9 +4,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.Constants -import com.unciv.logic.civilization.AlertType -import com.unciv.logic.civilization.CivilizationInfo -import com.unciv.logic.civilization.PopupAlert +import com.unciv.logic.civilization.* +import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.models.translations.fillPlaceholders import com.unciv.models.translations.tr import com.unciv.ui.trade.LeaderIntroTable @@ -262,6 +261,58 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu addPuppetOption(puppetAction) } } + AlertType.BulliedProtectedMinor -> { + val involvedCivs = popupAlert.value.split('@') + val bully = worldScreen.gameInfo.getCivilization(involvedCivs[0]) + val cityState = worldScreen.gameInfo.getCivilization(involvedCivs[1]) + val player = worldScreen.viewingCiv + addLeaderName(bully) + + val text = if (bully.getDiplomacyManager(player).relationshipLevel() >= RelationshipLevel.Neutral) // Nice message + "I've been informed that my armies have taken tribute from [${cityState.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." + else // Nasty message + "We asked [${cityState.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." + addGoodSizedLabel(text).row() + + add(getCloseButton("You'll pay for this!", 'y') { + player.getDiplomacyManager(bully).sideWithCityState() + }).row() + add(getCloseButton("Very well.", 'n') { + if(cityState.cities.isEmpty()) + player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", cityState.civName) + else { + val capitalLocation = LocationAction(listOf(cityState.getCapital().location)) + player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", capitalLocation, cityState.civName) + } + cityState.removeProtectorCiv(player, forced = true) + }).row() + } + AlertType.AttackedProtectedMinor -> { + val involvedCivs = popupAlert.value.split('@') + val attacker = worldScreen.gameInfo.getCivilization(involvedCivs[0]) + val cityState = worldScreen.gameInfo.getCivilization(involvedCivs[1]) + val player = worldScreen.viewingCiv + addLeaderName(attacker) + + val text = if (attacker.getDiplomacyManager(player).relationshipLevel() >= RelationshipLevel.Neutral) // Nice message + "It's come to my attention that I may have attacked [${cityState.civName}], a city-state under your protection.\nWhile it was not my goal to be at odds with your empire, this was deemed a necessary course of action." + else // Nasty message + "I thought you might like to know that I've launched an invasion of one of your little pet states.\nThe lands of [${cityState.civName}] will make a fine addition to my own." + addGoodSizedLabel(text).row() + + add(getCloseButton("You'll pay for this!", 'y') { + player.getDiplomacyManager(attacker).sideWithCityState() + }).row() + add(getCloseButton("Very well.", 'n') { + if(cityState.cities.isEmpty()) + player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", cityState.civName) + else { + val capitalLocation = LocationAction(listOf(cityState.getCapital().location)) + player.addNotification("You have broken your Pledge to Protect [${cityState.civName}]!", capitalLocation, cityState.civName) + } + cityState.removeProtectorCiv(player, forced = true) + }).row() + } } }