diff --git a/android/assets/jsons/Civ V - Gods & Kings/Techs.json b/android/assets/jsons/Civ V - Gods & Kings/Techs.json index a3ab86faaa..260272fb97 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Techs.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Techs.json @@ -234,7 +234,8 @@ "name": "Chivalry", "row": 6, "prerequisites": ["Civil Service","Guilds"], - "quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory" + "uniques": ["Enables Defensive Pacts"], + "quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory" }, { "name": "Machinery", diff --git a/android/assets/jsons/Civ V - Vanilla/Techs.json b/android/assets/jsons/Civ V - Vanilla/Techs.json index 7822b17b9d..a81941bd67 100644 --- a/android/assets/jsons/Civ V - Vanilla/Techs.json +++ b/android/assets/jsons/Civ V - Vanilla/Techs.json @@ -214,8 +214,9 @@ "name": "Chivalry", "row": 5, "prerequisites": ["Civil Service","Currency","Horseback Riding"], + "uniques": ["Enables Defensive Pacts"], "quote": "'Whoso pulleth out this sword of this stone and anvil, is rightwise king born of all England.' - Malory" - }, // Allows for defensive pacts + }, { "name": "Machinery", "row": 8, diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json index 38a2808b2e..acdbba7391 100644 --- a/android/assets/jsons/Tutorials.json +++ b/android/assets/jsons/Tutorials.json @@ -198,6 +198,14 @@ "The amount of ⍾Science you receive at the end is dependent on the ⍾Science generated by your cities and the other civilization's cities during the agreement - the more, the better!" ] }, + { + "name": "Defensive Pacts", + "steps": [ + "Defensive pacts allow you and another civ to protect one another from aggressors.\nOnce the defensive pact is signed, you will be drawn into their future defensive wars, just as they will be drawn into your future defensive wars. Declaring war on any Civ will remove all of your defensive pacts. You will have to re-sign them to use their effect.", + "Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in.", + "The AI is very careful and will not accept defensive pacts with less than 80 attitude." + ] + }, { "name": "City-States", "steps": [ diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index 1cf560801f..5d38615f11 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -43,6 +43,7 @@ object Constants { const val peaceTreaty = "Peace Treaty" const val researchAgreement = "Research Agreement" const val openBorders = "Open Borders" + const val defensivePact = "Defensive Pact" /** Used as origin in StatMap or ResourceSupplyList, or the toggle button in DiplomacyOverviewTab */ const val cityStates = "City-States" /** Used as origin in ResourceSupplyList */ diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index 5bfd4c8f50..26322c7660 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -69,6 +69,7 @@ object NextTurnAutomation { ReligionAutomation.spendFaithOnReligion(civInfo) } offerResearchAgreement(civInfo) + offerDefensivePact(civInfo) exchangeLuxuries(civInfo) issueRequests(civInfo) adoptPolicy(civInfo) // todo can take a second - why? @@ -172,6 +173,8 @@ object NextTurnAutomation { if (tradeLogic.currentTrade.theirOffers.any { it.type == offer.type && it.name == offer.name }) continue // So you don't get double offers of open borders declarations of war etc. + if (offer.type == TradeType.Treaty) + continue // Don't try to counter with a defensive pact or research pact val value = evaluation.evaluateBuyCost(offer, civInfo, otherCiv) if (value > 0) @@ -801,6 +804,29 @@ object NextTurnAutomation { } } + private fun offerDefensivePact(civInfo: Civilization) { + if (!civInfo.diplomacyFunctions.canSignDefensivePact()) return // don't waste your time + + val canSignDefensivePactCiv = civInfo.getKnownCivs() + .filter { + civInfo.diplomacyFunctions.canSignDefensivePactWith(it) + && !civInfo.getDiplomacyManager(it).hasFlag(DiplomacyFlags.DeclinedDefensivePact) + && civInfo.getDiplomacyManager(it).relationshipIgnoreAfraid() == RelationshipLevel.Ally + } + .sortedByDescending { it.stats.statsForNextTurn.science } + + for (otherCiv in canSignDefensivePactCiv) { + // Default setting is 1, this will be changed according to different civ. + if ((1..10).random() > 1) continue + //todo: Add more in depth evaluation here + val tradeLogic = TradeLogic(civInfo, otherCiv) + tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty)) + tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty)) + + otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) + } + } + private fun declareWar(civInfo: Civilization) { if (civInfo.wantsToFocusOn(Victory.Focus.Culture)) return if (civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt index 5afe404dd9..73ac6c426c 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt @@ -87,7 +87,6 @@ class DiplomacyFunctions(val civInfo: Civilization){ } } - fun canSignResearchAgreement(): Boolean { if (!civInfo.isMajorCiv()) return false if (!civInfo.hasUnique(UniqueType.EnablesResearchAgreements)) return false @@ -113,6 +112,21 @@ class DiplomacyFunctions(val civInfo: Civilization){ ).toInt() } + fun canSignDefensivePact(): Boolean { + if (!civInfo.isMajorCiv()) return false + if (!civInfo.hasUnique(UniqueType.EnablesDefensivePacts)) return false + return true + } + + fun canSignDefensivePactWith(otherCiv: Civilization): Boolean { + val diplomacyManager = civInfo.getDiplomacyManager(otherCiv) + return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact() + && diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) + && !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact) + && !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact) + && diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact + } + /** * @returns whether units of this civilization can pass through the tiles owned by [otherCiv], diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 96ec229f07..e18e0d029d 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -44,6 +44,8 @@ enum class DiplomacyFlags { DeclinedResearchAgreement, DeclaredWar, DeclarationOfFriendship, + DefensivePact, + DeclinedDefensivePact, ResearchAgreement, BorderConflict, SettledCitiesNearUs, @@ -72,6 +74,8 @@ enum class DiplomaticModifiers(val text:String) { CapturedOurCities("You have captured our cities!"), DeclaredFriendshipWithOurEnemies("You have declared friendship with our enemies!"), BetrayedDeclarationOfFriendship("Your so-called 'friendship' is worth nothing."), + SignedDefensivePactWithOurEnemies("You have declared a defensive pact with our enemies!"), + BetrayedDefensivePact("Your so-called 'defensive pact' is worth nothing."), Denunciation("You have publicly denounced us!"), DenouncedOurAllies("You have denounced our allies"), RefusedToNotSettleCitiesNearUs("You refused to stop settling cities near us"), @@ -90,6 +94,8 @@ enum class DiplomaticModifiers(val text:String) { LiberatedCity("We applaud your liberation of conquered cities!"), DeclarationOfFriendship("We have signed a public declaration of friendship"), DeclaredFriendshipWithOurAllies("You have declared friendship with our allies"), + DefensivePact("We have signed a promise to protect each other."), + SignedDefensivePactWithOurAllies("You have declared a defensive pact with our allies"), DenouncedOurEnemies("You have denounced our enemies"), OpenBorders("Our open borders have brought us closer together."), FulfilledPromiseToNotSettleCitiesNearUs("You fulfilled your promise to stop settling cities near us!"), @@ -376,7 +382,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { return max(0f, increment) * max(0f, modifierPercent).toPercent() } - + fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War //Used for nuke @@ -591,6 +597,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement)) scienceFromResearchAgreement() } + DiplomacyFlags.DefensivePact.name -> { + diplomaticStatus = DiplomaticStatus.Peace + } // This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ DiplomacyFlags.ProvideMilitaryUnit.name -> { // Do not unset the flag - they may return soon, and we'll continue from that point on @@ -673,6 +682,7 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { revertToZero(DiplomaticModifiers.WarMongerer, 1 / 2f) // warmongering gives a big negative boost when it happens but they're forgotten relatively quickly, like WWII amirite revertToZero(DiplomaticModifiers.CapturedOurCities, 1 / 4f) // if you captured our cities, though, that's harder to forget revertToZero(DiplomaticModifiers.BetrayedDeclarationOfFriendship, 1 / 8f) // That's a bastardly thing to do + revertToZero(DiplomaticModifiers.BetrayedDefensivePact, 1 / 16f) // That's an outrageous thing to do revertToZero(DiplomaticModifiers.RefusedToNotSettleCitiesNearUs, 1 / 4f) revertToZero(DiplomaticModifiers.BetrayedPromiseToNotSettleCitiesNearUs, 1 / 8f) // That's a bastardly thing to do revertToZero(DiplomaticModifiers.UnacceptableDemands, 1 / 4f) @@ -688,9 +698,14 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { setFriendshipBasedModifier() + setDefensivePactBasedModifier() + if (!hasFlag(DiplomacyFlags.DeclarationOfFriendship)) revertToZero(DiplomaticModifiers.DeclarationOfFriendship, 1 / 2f) //decreases slowly and will revert to full if it is declared later + if (!hasFlag(DiplomacyFlags.DefensivePact)) + revertToZero(DiplomaticModifiers.DefensivePact, 1f) + if (!otherCiv().isCityState()) return if (isRelationshipLevelLT(RelationshipLevel.Friend)) { @@ -714,41 +729,101 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { } /** Everything that happens to both sides equally when war is declared by one side on the other */ - private fun onWarDeclared() { + private fun onWarDeclared(isOffensiveWar: Boolean) { // Cancel all trades. for (trade in trades) - for (offer in trade.theirOffers.filter { it.duration > 0 }) + for (offer in trade.theirOffers.filter { it.duration > 0 && it.name != Constants.defensivePact}) civInfo.addNotification("[${offer.name}] from [$otherCivName] has ended", NotificationCategory.Trade, otherCivName, NotificationIcon.Trade) trades.clear() - updateHasOpenBorders() val civAtWarWith = otherCiv() - - if (civInfo.isCityState() && civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) { - civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true) + + // If we attacked, then we need to end all of our defensive pacts acording to Civ 5 + if (isOffensiveWar) { + removeDefensivePacts() } - diplomaticStatus = DiplomaticStatus.War + if (civInfo.isMajorCiv()) { + if (!isOffensiveWar) callInDefensivePactAllies() + callInCityStateAllies() + } + + if (civInfo.isCityState() && civInfo.cityStateFunctions.getProtectorCivs().contains(civAtWarWith)) { + civInfo.cityStateFunctions.removeProtectorCiv(civAtWarWith, forced = true) + } + + updateHasOpenBorders() + removeModifier(DiplomaticModifiers.YearsOfPeace) setFlag(DiplomacyFlags.DeclinedPeace, 10)/// AI won't propose peace for 10 turns setFlag(DiplomacyFlags.DeclaredWar, 10) // AI won't agree to trade for 10 turns 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. + * This is so that we can apply more negative modifiers later. + */ + private fun removeDefensivePacts() { + val civAtWarWith = otherCiv() + civInfo.diplomacy.values.filter { it.diplomaticStatus == DiplomaticStatus.DefensivePact }.forEach { + // We already removed the trades and we don't want to remove the flags yet. + if (it.otherCiv() != civAtWarWith) { + // Trades with defensive pact are now invalid + val defensivePactOffer = it.trades.firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } } + it.trades.remove(defensivePactOffer) + val theirDefensivePactOffer = it.otherCivDiplomacy().trades.firstOrNull { trade -> trade.ourOffers.any { offer -> offer.name == Constants.defensivePact } } + it.otherCivDiplomacy().trades.remove(theirDefensivePactOffer) + it.removeFlag(DiplomacyFlags.DefensivePact) + it.otherCivDiplomacy().removeFlag(DiplomacyFlags.DefensivePact) + it.diplomaticStatus = DiplomaticStatus.Peace + it.otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.Peace + } + for (civ in getCommonKnownCivs().filter { civ -> civ.isMajorCiv() }) { + civ.addNotification("[${civInfo.civName}] canceled their Defensive Pact with [${it.otherCivName}]!", + NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, it.otherCivName) + } + civInfo.addNotification("We have canceled our Defensive Pact with [${it.otherCivName}]!", + NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, it.otherCivName) + it.otherCiv().addNotification("[${civInfo.civName}] has canceled our Defensive Pact with us!", + NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy) + } + } - // Go through city state allies. - if (!civInfo.isCityState()) { - for (thirdCiv in civInfo.getKnownCivs() - .filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }) { + /** + * Goes through each DiplomacyManager with a defensive pact that is not already in the war. + * The civ that we are calling them in against should no longer have a defensive pact with us. + */ + private fun callInDefensivePactAllies() { + val civAtWarWith = otherCiv() + for (ourDefensivePact in civInfo.diplomacy.values.filter { ourDipManager -> + ourDipManager.diplomaticStatus == DiplomaticStatus.DefensivePact + && !ourDipManager.otherCiv().isDefeated() + && !ourDipManager.otherCiv().isAtWarWith(civAtWarWith) + }) { + val ally = ourDefensivePact.otherCiv() + // 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 [${civInfo.civName}]!", + NotificationCategory.Diplomacy, ally.civName, NotificationIcon.Diplomacy, civInfo.civName) + } + } + + private fun callInCityStateAllies() { + val civAtWarWith = otherCiv() + for (thirdCiv in civInfo.getKnownCivs() + .filter { it.isCityState() && it.getAllyCiv() == 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.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) } } } @@ -780,8 +855,8 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { } } - onWarDeclared() - otherCivDiplomacy.onWarDeclared() + onWarDeclared(true) + otherCivDiplomacy.onWarDeclared(false) otherCiv.addNotification("[${civInfo.civName}] has declared war on us!", NotificationCategory.Diplomacy, NotificationIcon.War, civInfo.civName) @@ -801,29 +876,57 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f) } else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f) } + + breakTreaties() + if (otherCiv.isMajorCiv()) + for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar)) + UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + } + + private fun breakTreaties() { + val otherCiv = otherCiv() + val otherCivDiplomacy = otherCivDiplomacy() + + var betrayedFriendship = false + var betrayedDefensivePact = false if (hasFlag(DiplomacyFlags.DeclarationOfFriendship)) { + betrayedFriendship = true removeFlag(DiplomacyFlags.DeclarationOfFriendship) otherCivDiplomacy.removeModifier(DiplomaticModifiers.DeclarationOfFriendship) - for (knownCiv in civInfo.getKnownCivs()) { - val amount = if (knownCiv == otherCiv) -40f else -20f - val diploManager = knownCiv.getDiplomacyManager(civInfo) - diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount) - diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much. - } } otherCivDiplomacy.removeFlag(DiplomacyFlags.DeclarationOfFriendship) + if (hasFlag(DiplomacyFlags.DefensivePact)) { + betrayedDefensivePact = true + removeFlag(DiplomacyFlags.DefensivePact) + otherCivDiplomacy.removeModifier(DiplomaticModifiers.DefensivePact) + } + otherCivDiplomacy.removeFlag(DiplomacyFlags.DefensivePact) + + if (betrayedFriendship || betrayedDefensivePact) { + for (knownCiv in civInfo.getKnownCivs()) { + val diploManager = knownCiv.getDiplomacyManager(civInfo) + if (betrayedFriendship) { + val amount = if (knownCiv == otherCiv) -40f else -20f + diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount) + } + if (betrayedDefensivePact) { + //Note: this stacks with Declaration of Friendship + val amount = if (knownCiv == otherCiv) -20f else -10f + diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount) + } + diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much. + diploManager.removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies) + } + } + if (hasFlag(DiplomacyFlags.ResearchAgreement)) { removeFlag(DiplomacyFlags.ResearchAgreement) totalOfScienceDuringRA = 0 otherCivDiplomacy.totalOfScienceDuringRA = 0 } otherCivDiplomacy.removeFlag(DiplomacyFlags.ResearchAgreement) - - if (otherCiv.isMajorCiv()) - for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponDeclaringWar)) - UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) } /** Should only be called from makePeace */ @@ -919,16 +1022,70 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies) for (thirdCiv in getCommonKnownCivs() .filter { it.getDiplomacyManager(civInfo).hasFlag(DiplomacyFlags.DeclarationOfFriendship) }) { - when (otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid()) { - RelationshipLevel.Unforgivable -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies, -15f) - RelationshipLevel.Enemy -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurEnemies, -5f) - RelationshipLevel.Friend -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies, 5f) - RelationshipLevel.Ally -> addModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies, 15f) - else -> {} + + val relationshipLevel = otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid() + val modifierType = when (relationshipLevel) { + RelationshipLevel.Unforgivable, RelationshipLevel.Enemy -> DiplomaticModifiers.DeclaredFriendshipWithOurEnemies + else -> DiplomaticModifiers.DeclaredFriendshipWithOurAllies } + val modifierValue = when (relationshipLevel) { + RelationshipLevel.Unforgivable -> -15f + RelationshipLevel.Enemy -> -5f + RelationshipLevel.Friend -> 5f + RelationshipLevel.Ally -> 15f + else -> 0f + } + addModifier(modifierType, modifierValue) } } + fun signDefensivePact(duration: Int) { + //Note: These modifiers are additive to the friendship modifiers + setModifier(DiplomaticModifiers.DefensivePact, 10f) + otherCivDiplomacy().setModifier(DiplomaticModifiers.DefensivePact, 10f) + setFlag(DiplomacyFlags.DefensivePact, duration) + otherCivDiplomacy().setFlag(DiplomacyFlags.DefensivePact, duration) + diplomaticStatus = DiplomaticStatus.DefensivePact + otherCivDiplomacy().diplomaticStatus = DiplomaticStatus.DefensivePact + + + for (thirdCiv in getCommonKnownCivs().filter { it.isMajorCiv() }) { + thirdCiv.addNotification("[${civInfo.civName}] and [$otherCivName] have signed the Defensive Pact!", + NotificationCategory.Diplomacy, civInfo.civName, NotificationIcon.Diplomacy, otherCivName) + thirdCiv.getDiplomacyManager(civInfo).setDefensivePactBasedModifier() + } + + // Ignore contitionals as triggerCivwideUnique will check again, and that would break + // UniqueType.ConditionalChance - 25% declared chance would work as 6% actual chance + for (unique in civInfo.getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals)) + UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) + for (unique in otherCiv().getTriggeredUniques(UniqueType.TriggerUponSigningDefensivePact, StateForConditionals.IgnoreConditionals)) + UniqueTriggerActivation.triggerCivwideUnique(unique, otherCiv()) + } + + private fun setDefensivePactBasedModifier() { + removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies) + removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurEnemies) + for (thirdCiv in getCommonKnownCivs() + .filter { it.getDiplomacyManager(civInfo).hasFlag(DiplomacyFlags.DefensivePact) }) { + //Note: These modifiers are additive to the friendship modifiers + val relationshipLevel = otherCiv().getDiplomacyManager(thirdCiv).relationshipIgnoreAfraid() + val modifierType = when (relationshipLevel) { + RelationshipLevel.Unforgivable, RelationshipLevel.Enemy -> DiplomaticModifiers.SignedDefensivePactWithOurEnemies + else -> DiplomaticModifiers.SignedDefensivePactWithOurAllies + } + val modifierValue = when (relationshipLevel) { + RelationshipLevel.Unforgivable -> -15f + RelationshipLevel.Enemy -> -10f + RelationshipLevel.Friend -> 2f + RelationshipLevel.Ally -> 5f + else -> 0f + } + addModifier(modifierType, modifierValue) + } + } + + fun denounce() { setModifier(DiplomaticModifiers.Denunciation, -35f) otherCivDiplomacy().setModifier(DiplomaticModifiers.Denunciation, -35f) diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomaticStatus.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomaticStatus.kt index b4f78739fc..64c7519aa4 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomaticStatus.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomaticStatus.kt @@ -5,5 +5,6 @@ import com.unciv.logic.IsPartOfGameInfoSerialization enum class DiplomaticStatus : IsPartOfGameInfoSerialization { Peace, Protector, //city state's diplomacy for major civ can be marked as Protector, not vice versa. - War + War, + DefensivePact, } diff --git a/core/src/com/unciv/logic/trade/Trade.kt b/core/src/com/unciv/logic/trade/Trade.kt index 0b37697f2a..29dfdb35e8 100644 --- a/core/src/com/unciv/logic/trade/Trade.kt +++ b/core/src/com/unciv/logic/trade/Trade.kt @@ -60,6 +60,9 @@ class TradeRequest : IsPartOfGameInfoSerialization { diplomacyManager.setFlag(DiplomacyFlags.DeclinedLuxExchange,20) if (trade.ourOffers.any { it.name == Constants.researchAgreement }) diplomacyManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,20) + if (trade.ourOffers.any { it.name == Constants.defensivePact }) + diplomacyManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10) + if (trade.isPeaceTreaty()) diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, 5) requestingCivInfo.addNotification("[${decliningCiv.civName}] has denied your trade request", diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index d341f23e6e..1c0545e5fe 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -5,6 +5,7 @@ import com.unciv.logic.automation.Automation import com.unciv.logic.automation.ThreatLevel import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization +import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.ModOptionsConstants @@ -74,13 +75,20 @@ class TradeEvaluation { var sumOfOurOffers = trade.ourOffers.sumOf { evaluateSellCost(it, evaluator, tradePartner) } + val relationshipLevel = evaluator.getDiplomacyManager(tradePartner).relationshipIgnoreAfraid() // If we're making a peace treaty, don't try to up the bargain for people you don't like. // Leads to spartan behaviour where you demand more, the more you hate the enemy...unhelpful - if (trade.ourOffers.none { it.name == Constants.peaceTreaty || it.name == Constants.researchAgreement }) { - val relationshipLevel = evaluator.getDiplomacyManager(tradePartner).relationshipIgnoreAfraid() + if (trade.ourOffers.none { it.name == Constants.peaceTreaty || it.name == Constants.researchAgreement}) { if (relationshipLevel == RelationshipLevel.Enemy) sumOfOurOffers = (sumOfOurOffers * 1.5).toInt() else if (relationshipLevel == RelationshipLevel.Unforgivable) sumOfOurOffers *= 2 } + if (trade.ourOffers.firstOrNull { it.name == Constants.defensivePact } != null) { + if (relationshipLevel == RelationshipLevel.Ally) { + //todo: Add more in depth evaluation here + } else { + return Int.MIN_VALUE + } + } return sumOfTheirOffers - sumOfOurOffers } @@ -195,6 +203,7 @@ class TradeEvaluation { Constants.peaceTreaty -> evaluatePeaceCostForThem(civInfo, tradePartner) Constants.researchAgreement -> -offer.amount else -> 1000 + //Todo:AddDefensiveTreatyHere } } TradeType.Luxury_Resource -> { diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 4941e5beb4..9392532768 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -7,6 +7,7 @@ import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.diplomacy.CityStateFunctions import com.unciv.logic.civilization.diplomacy.DiplomacyFlags import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers +import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.models.ruleset.ModOptionsConstants import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unique.UniqueType @@ -31,6 +32,12 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili offers.add(TradeOffer(Constants.openBorders, TradeType.Agreement)) } + if (civInfo.diplomacyFunctions.canSignResearchAgreementsWith(otherCivilization)) + offers.add(TradeOffer(Constants.researchAgreement, TradeType.Treaty, civInfo.diplomacyFunctions.getResearchAgreementCost())) + + if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCivilization)) + offers.add(TradeOffer(Constants.defensivePact, TradeType.Treaty)) + for (entry in civInfo.getCivResourcesWithOriginsForTrade() .filterNot { it.resource.resourceType == ResourceType.Bonus } .filter { it.origin == Constants.tradable } @@ -123,6 +130,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili to.getDiplomacyManager(from) .setFlag(DiplomacyFlags.ResearchAgreement, offer.duration) } + if (offer.name == Constants.defensivePact) from.getDiplomacyManager(to).signDefensivePact(offer.duration); } TradeType.Introduction -> to.diplomacyFunctions.makeCivilizationsMeet(to.gameInfo.getCivilization(offer.name)) TradeType.WarDeclaration -> { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 58056b3531..daf07d189b 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -197,6 +197,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: // Should the 'R' in 'Research agreements' be capitalized? EnablesResearchAgreements("Enables Research agreements", UniqueTarget.Global), ScienceFromResearchAgreements("Science gained from research agreements [relativeAmount]%", UniqueTarget.Global), + EnablesDefensivePacts("Enables Defensive Pacts", UniqueTarget.Global), GreatPersonBoostWithFriendship("When declaring friendship, both parties gain a [relativeAmount]% boost to great person generation", UniqueTarget.Global), /// City State Influence @@ -730,6 +731,7 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: TriggerUponAdoptingPolicyOrBelief("upon adopting [policy/belief]", UniqueTarget.TriggerCondition), TriggerUponDeclaringWar("upon declaring war with a major Civilization", UniqueTarget.TriggerCondition), TriggerUponDeclaringFriendship("upon declaring friendship", UniqueTarget.TriggerCondition), + TriggerUponSigningDefensivePact("upon declaring a defensive pact", UniqueTarget.TriggerCondition), TriggerUponEnteringGoldenAge("upon entering a Golden Age", UniqueTarget.TriggerCondition), /** Can be placed upon both units and as global */ TriggerUponConqueringCity("upon conquering a city", UniqueTarget.TriggerCondition, UniqueTarget.UnitTriggerCondition), diff --git a/core/src/com/unciv/ui/screens/diplomacyscreen/DiplomacyScreen.kt b/core/src/com/unciv/ui/screens/diplomacyscreen/DiplomacyScreen.kt index 047d5b26bb..798ef5dd80 100644 --- a/core/src/com/unciv/ui/screens/diplomacyscreen/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/screens/diplomacyscreen/DiplomacyScreen.kt @@ -658,10 +658,6 @@ class DiplomacyScreen( if (!diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)) diplomacyTable.add(getDeclareFriendshipButton(otherCiv)).row() - - if (viewingCiv.diplomacyFunctions.canSignResearchAgreementsWith(otherCiv)) - diplomacyTable.add(getResearchAgreementButton(otherCiv)).row() - if (!diplomacyManager.hasFlag(DiplomacyFlags.Denunciation) && !diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) ) diplomacyTable.add(getDenounceButton(otherCiv, diplomacyManager)).row() @@ -744,30 +740,7 @@ class DiplomacyScreen( if (isNotPlayersTurn()) denounceButton.disable() return denounceButton } - - private fun getResearchAgreementButton(otherCiv: Civilization): TextButton { - val researchAgreementButton = "Research Agreement".toTextButton() - - val requiredGold = viewingCiv.diplomacyFunctions.getResearchAgreementCost() - researchAgreementButton.onClick { - val tradeTable = setTrade(otherCiv) - val researchAgreement = - TradeOffer(Constants.researchAgreement, TradeType.Treaty, requiredGold) - val goldCostOfSignResearchAgreement = - TradeOffer("Gold".tr(), TradeType.Gold, -requiredGold) - tradeTable.tradeLogic.currentTrade.theirOffers.add(researchAgreement) - tradeTable.tradeLogic.ourAvailableOffers.add(researchAgreement) - tradeTable.tradeLogic.ourAvailableOffers.add(goldCostOfSignResearchAgreement) - tradeTable.tradeLogic.currentTrade.ourOffers.add(researchAgreement) - tradeTable.tradeLogic.theirAvailableOffers.add(researchAgreement) - tradeTable.tradeLogic.theirAvailableOffers.add(goldCostOfSignResearchAgreement) - tradeTable.offerColumnsTable.update() - tradeTable.enableOfferButton(true) - } - if (isNotPlayersTurn()) researchAgreementButton.disable() - return researchAgreementButton - } - + private fun getDeclareFriendshipButton(otherCiv: Civilization): TextButton { val declareFriendshipButton = "Offer Declaration of Friendship ([30] turns)".toTextButton() @@ -791,8 +764,6 @@ class DiplomacyScreen( val tradeButton = "Trade".toTextButton() tradeButton.onClick { setTrade(otherCiv).apply { - tradeLogic.ourAvailableOffers.apply { remove(firstOrNull { it.type == TradeType.Treaty }) } - tradeLogic.theirAvailableOffers.apply { remove(firstOrNull { it.type == TradeType.Treaty }) } offerColumnsTable.update() } } diff --git a/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt b/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt index 866ce4174c..0375535c66 100644 --- a/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt +++ b/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt @@ -103,11 +103,13 @@ class OffersListScroll( } val amountPerClick = - if (offer.type == Gold) 50 - else 1 + when (offer.type) { + Gold -> 50 + Treaty -> Int.MAX_VALUE + else -> 1 + } - if (offer.isTradable() && offer.name != Constants.peaceTreaty && // can't disable peace treaty! - offer.name != Constants.researchAgreement) { + if (offer.isTradable() && offer.name != Constants.peaceTreaty) { // can't disable peace treaty! // highlight unique suggestions if (offerType in listOf(Luxury_Resource, Strategic_Resource) diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 8e5bf8a453..596bf8e847 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -629,6 +629,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Global +??? example "Enables Defensive Pacts" + Applicable to: Global + ??? example "When declaring friendship, both parties gain a [relativeAmount]% boost to great person generation" Example: "When declaring friendship, both parties gain a [+20]% boost to great person generation" @@ -2022,6 +2025,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl ??? example "<upon declaring friendship>" Applicable to: TriggerCondition +??? example "<upon declaring a defensive pact>" + Applicable to: TriggerCondition + ??? example "<upon entering a Golden Age>" Applicable to: TriggerCondition