diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 6a71f7a62c..2640013edf 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -155,6 +155,15 @@ Denounce ([numberOfTurns] turns) = We will remember this. = [civName] has declared war on [targetCivName]! = +[civName] has joined [allyCivName] in the war against us! = +We have joined [allyCivName] in the war against [enemyCivName]! = +[civName] has joined [allyCivName] in the war against [enemyCivName]! = +[civName] has joined us in the war against [enemyCivName]! = + +[civName] cancelled their Defensive Pact with [otherCivName]! = +[civName] cancelled their Defensive Pact with us! = +We have cancelled our Defensive Pact with [civName]! = + [civName] and [targetCivName] have signed a Peace Treaty! = [civName] and [targetCivName] have signed the Declaration of Friendship! = [civName] has denounced [targetCivName]! = @@ -1540,6 +1549,7 @@ My friend, shall we declare our friendship to the world? = Sign Declaration of Friendship ([30] turns) = We are not interested. = We have signed a Declaration of Friendship with [otherCiv]! = +[civName] and [otherCivName] have signed a Defensive Pact! = [otherCiv] has denied our Declaration of Friendship! = Basics = diff --git a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt index df76650e76..d190e1e80c 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/CityStateFunctions.kt @@ -245,12 +245,11 @@ class CityStateFunctions(val civInfo: Civilization) { // Join the wars of our new ally - loop through all civs they are at war with for (newEnemy in civInfo.gameInfo.civilizations.filter { it.isAtWarWith(newAllyCiv) && it.isAlive() } ) { - if (civInfo.knows(newEnemy) && !civInfo.isAtWarWith(newEnemy)) - civInfo.getDiplomacyManager(newEnemy).declareWar() - else if (!civInfo.knows(newEnemy)) { - // We have to meet first - civInfo.diplomacyFunctions.makeCivilizationsMeet(newEnemy, warOnContact = true) - civInfo.getDiplomacyManager(newEnemy).declareWar() + if (!civInfo.isAtWarWith(newEnemy)) { + if (!civInfo.knows(newEnemy)) + // We have to meet first + civInfo.diplomacyFunctions.makeCivilizationsMeet(newEnemy, warOnContact = true) + civInfo.getDiplomacyManager(newEnemy).declareWar(DeclareWarReason(WarType.CityStateAllianceWar, newAllyCiv)) } } } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt index b250ff139a..c6a6353358 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt @@ -2,6 +2,7 @@ package com.unciv.logic.civilization.diplomacy import com.unciv.Constants import com.unciv.logic.civilization.AlertType +import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.DiplomacyAction import com.unciv.logic.civilization.NotificationCategory import com.unciv.logic.civilization.NotificationIcon @@ -14,52 +15,27 @@ object DeclareWar { /** Declares war with the other civ in this diplomacy manager. * Handles all war effects and diplomatic changes with other civs and such. * - * @param indirectCityStateAttack Influence with city states should only be set to -60 + * @param declareWarReason Changes what sort of effects the war has depending on how it was initiated. + * If it was a direct attack put [WarType.DirectWar] for the following effects. + * Influence with city states should only be set to -60 * when they are attacked directly, not when their ally is attacked. * When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state. * Should only ever be set to true for calls originating from within this function. */ - internal fun declareWar(diplomacyManager: DiplomacyManager, indirectCityStateAttack: Boolean = false) { + internal fun declareWar(diplomacyManager: DiplomacyManager, declareWarReason: DeclareWarReason) { val civInfo = diplomacyManager.civInfo val otherCiv = diplomacyManager.otherCiv() val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy() - if (otherCiv.isCityState() && !indirectCityStateAttack) { - otherCivDiplomacy.setInfluence(-60f) - civInfo.numMinorCivsAttacked += 1 - otherCiv.cityStateFunctions.cityStateAttacked(civInfo) + if (otherCiv.isCityState() && declareWarReason.warType == WarType.DirectWar) + handleCityStateDirectAttack(diplomacyManager) - // You attacked your own ally, you're a right bastard - if (otherCiv.getAllyCiv() == civInfo.civName) { - otherCiv.cityStateFunctions.updateAllyCivForCityState() - otherCivDiplomacy.setInfluence(-120f) - for (knownCiv in civInfo.getKnownCivs()) { - knownCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, -10f) - } - } - } + notifyOfWar(diplomacyManager, declareWarReason) onWarDeclared(diplomacyManager, true) onWarDeclared(otherCivDiplomacy, false) - otherCiv.addNotification("[${civInfo.civName}] has declared war on us!", - NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName) - otherCiv.popupAlerts.add(PopupAlert(AlertType.WarDeclaration, civInfo.civName)) - - diplomacyManager.getCommonKnownCivsWithSpectators().forEach { - it.addNotification("[${civInfo.civName}] has declared war on [${diplomacyManager.otherCivName}]!", - NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.War, diplomacyManager.otherCivName) - } - - otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f) - otherCivDiplomacy.removeModifier(DiplomaticModifiers.ReturnedCapturedUnits) - - for (thirdCiv in civInfo.getKnownCivs()) { - if (thirdCiv.isAtWarWith(otherCiv)) { - if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).addInfluence(10f) - else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f) - } else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f) - } + changeOpinions(diplomacyManager, declareWarReason.warType) breakTreaties(diplomacyManager) @@ -68,6 +44,134 @@ object DeclareWar { UniqueTriggerActivation.triggerUnique(unique, civInfo) } + private fun handleCityStateDirectAttack(diplomacyManager: DiplomacyManager) { + val civInfo = diplomacyManager.civInfo + val otherCiv = diplomacyManager.otherCiv() + val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy() + + otherCivDiplomacy.setInfluence(-60f) + civInfo.numMinorCivsAttacked += 1 + otherCiv.cityStateFunctions.cityStateAttacked(civInfo) + + // You attacked your own ally, you're a right bastard + if (otherCiv.getAllyCiv() == civInfo.civName) { + otherCiv.cityStateFunctions.updateAllyCivForCityState() + otherCivDiplomacy.setInfluence(-120f) + for (knownCiv in civInfo.getKnownCivs()) { + knownCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, -10f) + } + } + } + + private fun notifyOfWar(diplomacyManager: DiplomacyManager, declareWarReason: DeclareWarReason) { + val civInfo = diplomacyManager.civInfo + val otherCiv = diplomacyManager.otherCiv() + + when (declareWarReason.warType) { + WarType.DirectWar -> { + otherCiv.popupAlerts.add(PopupAlert(AlertType.WarDeclaration, civInfo.civName)) + + otherCiv.addNotification("[${civInfo.civName}] has declared war on us!", + NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName) + + diplomacyManager.getCommonKnownCivsWithSpectators().forEach { + it.addNotification("[${civInfo.civName}] has declared war on [${diplomacyManager.otherCivName}]!", + NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.War, otherCiv.civName) + } + } + WarType.DefensivePactWar, WarType.CityStateAllianceWar, WarType.JoinWar -> { + val allyCiv = declareWarReason.allyCiv!! + otherCiv.popupAlerts.add(PopupAlert(AlertType.WarDeclaration, civInfo.civName)) + val agressor = if (declareWarReason.warType == WarType.JoinWar) civInfo else otherCiv + val defender = if (declareWarReason.warType == WarType.JoinWar) otherCiv else civInfo + + defender.addNotification("[${agressor.civName}] has joined [${allyCiv.civName}] in the war against us!", + NotificationCategory.Diplomacy, NotificationIcon.War, agressor.civName) + + agressor.addNotification("We have joined [${allyCiv.civName}] in the war against [${defender.civName}]!", + NotificationCategory.Diplomacy, NotificationIcon.War, defender.civName) + + diplomacyManager.getCommonKnownCivsWithSpectators().filterNot { it == allyCiv }.forEach { + it.addNotification("[${agressor.civName}] has joined [${allyCiv.civName}] in the war against [${defender.civName}]!", + NotificationCategory.Diplomacy, agressor.civName, NotificationIcon.War, defender.civName) + } + + allyCiv.addNotification("[${agressor.civName}] has joined us in the war against [${defender.civName}]!", + NotificationCategory.Diplomacy, agressor.civName, NotificationIcon.War, defender.civName) + } + } + } + + /** Everything that happens to both sides equally when war is declared by one side on the other */ + private fun onWarDeclared(diplomacyManager: DiplomacyManager, isOffensiveWar: Boolean) { + // Cancel all trades. + for (trade in diplomacyManager.trades) + for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact}) + diplomacyManager.civInfo.addNotification("[${offer.name}] from [${diplomacyManager.otherCivName}] has ended", + DiplomacyAction(diplomacyManager.otherCivName, true), + NotificationCategory.Trade, diplomacyManager.otherCivName, NotificationIcon.Trade) + diplomacyManager.trades.clear() + diplomacyManager.civInfo.tradeRequests.removeAll { it.requestingCiv == diplomacyManager.otherCivName } + + val civAtWarWith = diplomacyManager.otherCiv() + + // If we attacked, then we need to end all of our defensive pacts acording to Civ 5 + if (isOffensiveWar) { + removeDefensivePacts(diplomacyManager) + } + diplomacyManager.diplomaticStatus = DiplomaticStatus.War + + if (diplomacyManager.civInfo.isMajorCiv()) { + if (!isOffensiveWar && !civAtWarWith.isCityState()) + callInDefensivePactAllies(diplomacyManager) + callInCityStateAllies(diplomacyManager) + } + + if (diplomacyManager.civInfo.isCityState() && + diplomacyManager.civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) { + diplomacyManager.civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true) + } + + diplomacyManager.updateHasOpenBorders() + + diplomacyManager.removeModifier(DiplomaticModifiers.YearsOfPeace) + diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns + diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns + diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict) + } + + private fun changeOpinions(diplomacyManager: DiplomacyManager, warType: WarType) { + val civInfo = diplomacyManager.civInfo + val otherCiv = diplomacyManager.otherCiv() + val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy() + + otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f) + otherCivDiplomacy.removeModifier(DiplomaticModifiers.ReturnedCapturedUnits) + + // Apply warmongering + if (warType == WarType.DirectWar || warType == WarType.JoinWar) { + for (thirdCiv in civInfo.getKnownCivs()) { + if (!thirdCiv.isAtWarWith(otherCiv)) + // We don't want this modify to stack if there is a defensive pact + thirdCiv.getDiplomacyManager(civInfo) + .addModifier(DiplomaticModifiers.WarMongerer, -5f) + } + } + + // Apply shared enemy modifiers + for (thirdCiv in diplomacyManager.getCommonKnownCivs()) { + if (thirdCiv.isAtWarWith(otherCiv) && !thirdCiv.isAtWarWith(civInfo)) { + // Improve our relations + if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).addInfluence(10f) + else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f) + } else if (thirdCiv.isAtWarWith(civInfo)) { + // Improve their relations + if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(otherCiv).addInfluence(10f) + else thirdCiv.getDiplomacyManager(otherCiv).addModifier(DiplomaticModifiers.SharedEnemy, 5f) + } + } + } + private fun breakTreaties(diplomacyManager: DiplomacyManager) { val otherCiv = diplomacyManager.otherCiv() val otherCivDiplomacy = diplomacyManager.otherCivDiplomacy() @@ -113,45 +217,6 @@ object DeclareWar { otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement) } - - - /** Everything that happens to both sides equally when war is declared by one side on the other */ - private fun onWarDeclared(diplomacyManager: DiplomacyManager, isOffensiveWar: Boolean) { - // Cancel all trades. - for (trade in diplomacyManager.trades) - for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact}) - diplomacyManager.civInfo.addNotification("[${offer.name}] from [${diplomacyManager.otherCivName}] has ended", - DiplomacyAction(diplomacyManager.otherCivName, true), - NotificationCategory.Trade, diplomacyManager.otherCivName, NotificationIcon.Trade) - diplomacyManager.trades.clear() - diplomacyManager.civInfo.tradeRequests.removeAll { it.requestingCiv == diplomacyManager.otherCivName } - - val civAtWarWith = diplomacyManager.otherCiv() - - // If we attacked, then we need to end all of our defensive pacts acording to Civ 5 - if (isOffensiveWar) { - removeDefensivePacts(diplomacyManager) - } - diplomacyManager.diplomaticStatus = DiplomaticStatus.War - - if (diplomacyManager.civInfo.isMajorCiv()) { - if (!isOffensiveWar && !civAtWarWith.isCityState()) callInDefensivePactAllies(diplomacyManager) - callInCityStateAllies(diplomacyManager) - } - - if (diplomacyManager.civInfo.isCityState() && - diplomacyManager.civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) { - diplomacyManager.civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true) - } - - diplomacyManager.updateHasOpenBorders() - - diplomacyManager.removeModifier(DiplomaticModifiers.YearsOfPeace) - diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns - diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns - diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict) - } - /** * Removes all defensive Pacts and trades. Notifies other civs. * Note: Does not remove the flags and modifiers of the otherCiv if there is a defensive pact. @@ -180,11 +245,15 @@ object DeclareWar { thirdPartyDiploManager.removeFlag(DiplomacyFlags.DefensivePact) thirdPartyDiploManager.otherCivDiplomacy().removeFlag(DiplomacyFlags.DefensivePact) } - for (civ in diplomacyManager.getCommonKnownCivsWithSpectators()) { - civ.addNotification("[${diplomacyManager.civInfo.civName}] canceled their Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!", + for (civ in thirdPartyDiploManager.getCommonKnownCivsWithSpectators()) { + civ.addNotification("[${diplomacyManager.civInfo.civName}] cancelled their Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!", NotificationCategory.Diplomacy, diplomacyManager.civInfo.civName, NotificationIcon.Diplomacy, thirdPartyDiploManager.otherCivName) } - diplomacyManager.civInfo.addNotification("We have canceled our Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!", + + thirdPartyDiploManager.otherCiv().addNotification("[${diplomacyManager.civInfo.civName}] cancelled their Defensive Pact with us!", + NotificationCategory.Diplomacy, diplomacyManager.civInfo.civName, NotificationIcon.Diplomacy, thirdPartyDiploManager.otherCivName) + + thirdPartyDiploManager.civInfo.addNotification("We have cancelled our Defensive Pact with [${thirdPartyDiploManager.otherCivName}]!", NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, thirdPartyDiploManager.otherCivName) } } @@ -204,10 +273,7 @@ object DeclareWar { val ally = ourDefensivePact.otherCiv() if (!civAtWarWith.knows(ally)) civAtWarWith.diplomacyFunctions.makeCivilizationsMeet(ally, true) // Have the aggressor declare war on the ally. - civAtWarWith.getDiplomacyManager(ally).declareWar(true) - // Notify the aggressor - civAtWarWith.addNotification("[${ally.civName}] has joined the defensive war with [${diplomacyManager.civInfo.civName}]!", - NotificationCategory.Diplomacy, ally.civName, NotificationIcon.Diplomacy, diplomacyManager.civInfo.civName) + civAtWarWith.getDiplomacyManager(ally).declareWar(DeclareWarReason(WarType.DefensivePactWar, diplomacyManager.civInfo)) } } @@ -216,13 +282,31 @@ object DeclareWar { for (thirdCiv in diplomacyManager.civInfo.getKnownCivs() .filter { it.isCityState() && it.getAllyCiv() == diplomacyManager.civInfo.civName }) { - if (thirdCiv.knows(civAtWarWith) && !thirdCiv.isAtWarWith(civAtWarWith)) - thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true) - else if (!thirdCiv.knows(civAtWarWith)) { - // Our city state ally has not met them yet, so they have to meet first - thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true) - thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(true) + if (!thirdCiv.isAtWarWith(civAtWarWith)) { + if (!thirdCiv.knows(civAtWarWith)) + // Our city state ally has not met them yet, so they have to meet first + thirdCiv.diplomacyFunctions.makeCivilizationsMeet(civAtWarWith, warOnContact = true) + thirdCiv.getDiplomacyManager(civAtWarWith).declareWar(DeclareWarReason(WarType.CityStateAllianceWar, diplomacyManager.civInfo)) } } } } + +enum class WarType { + /** One civ declared war on the other. */ + DirectWar, + /** A city state has joined a war through it's alliance. */ + CityStateAllianceWar, + /** A civilization has joined a war through it's defensive pact. */ + DefensivePactWar, + /** A civilization has joined a war through a trade. Has the same diplomatic repercussions as direct war.*/ + JoinWar, +} + +/** + * Stores the reason for the war. We might want to add justified wars in the future. + * @param allyCiv If the given [WarType] is [WarType.CityStateAllianceWar], [WarType.DefensivePactWar] or [WarType.JoinWar] + * the allyCiv needs to be given. + */ +class DeclareWarReason(val warType: WarType, val allyCiv: Civilization? = null) + diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 93785dc057..cb9e9871c6 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -379,7 +379,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War - fun declareWar(indirectCityStateAttack: Boolean = false) = DeclareWar.declareWar(this, indirectCityStateAttack) + fun declareWar(declareWarReason: DeclareWarReason = DeclareWarReason(WarType.DirectWar)) = DeclareWar.declareWar(this, declareWarReason) //Used for nuke fun canAttack() = turnsToPeaceTreaty() == 0 @@ -563,7 +563,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { for (thirdCiv in getCommonKnownCivsWithSpectators()) { - thirdCiv.addNotification("[${civInfo.civName}] and [$otherCivName] have signed the Defensive Pact!", + thirdCiv.addNotification("[${civInfo.civName}] and [$otherCivName] have signed a Defensive Pact!", NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, otherCivName) if (thirdCiv.isSpectator()) return thirdCiv.getDiplomacyManager(civInfo).setDefensivePactBasedModifier() diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 70d84f65e9..d00d7d9d94 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -5,8 +5,10 @@ import com.unciv.logic.civilization.AlertType import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.diplomacy.CityStateFunctions +import com.unciv.logic.civilization.diplomacy.DeclareWarReason import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers +import com.unciv.logic.civilization.diplomacy.WarType import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.UniqueType @@ -137,7 +139,7 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil TradeType.Introduction -> to.diplomacyFunctions.makeCivilizationsMeet(to.gameInfo.getCivilization(offer.name)) TradeType.WarDeclaration -> { val nameOfCivToDeclareWarOn = offer.name - from.getDiplomacyManager(nameOfCivToDeclareWarOn).declareWar() + from.getDiplomacyManager(nameOfCivToDeclareWarOn).declareWar(DeclareWarReason(WarType.JoinWar, to)) } else -> {} } diff --git a/tests/src/com/unciv/logic/civilization/diplomacy/DiplomacyManagerTests.kt b/tests/src/com/unciv/logic/civilization/diplomacy/DiplomacyManagerTests.kt index 7721a35384..6f3399c65d 100644 --- a/tests/src/com/unciv/logic/civilization/diplomacy/DiplomacyManagerTests.kt +++ b/tests/src/com/unciv/logic/civilization/diplomacy/DiplomacyManagerTests.kt @@ -243,7 +243,7 @@ class DiplomacyManagerTests { meet(e, cityState) // we cannot be allied and simoultaneously having a city state declare indirect war on us cityState.getDiplomacyManager(e).addInfluence(31f) - cityState.getDiplomacyManager(e).declareWar(indirectCityStateAttack = true) + cityState.getDiplomacyManager(e).declareWar(DeclareWarReason(WarType.DefensivePactWar, a)) // when e.getDiplomacyManager(cityState).makePeace()