diff --git a/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json index 5a7310bba6..04c02fc4be 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json +++ b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json @@ -1,6 +1,7 @@ { "name": "Global uniques", "uniques": [ + "Requires establishing embassies to conduct advanced diplomacy", "[-75]% growth [in all cities] ", "Nullifies Growth [in all cities] ", "[-50]% [Production] [in all cities] ", diff --git a/android/assets/jsons/Civ V - Gods & Kings/Techs.json b/android/assets/jsons/Civ V - Gods & Kings/Techs.json index 18b589f573..298b6dbcd7 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/Techs.json +++ b/android/assets/jsons/Civ V - Gods & Kings/Techs.json @@ -76,6 +76,7 @@ "name": "Writing", "row": 3, "prerequisites": ["Pottery"], + "uniques": ["Enables establishment of embassies"], "quote": "'He who destroys a good book kills reason itself.' - John Milton" }, { diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json index 45bd48fdb9..437ed39e82 100644 --- a/android/assets/jsons/Tutorials.json +++ b/android/assets/jsons/Tutorials.json @@ -269,21 +269,49 @@ "Ranged attacks can be done from a distance, dependent on the 'Range' value of the unit.\nWhile melee attacks allow the defender to damage the attacker in retaliation, ranged attacks do not." ] }, + { + "name": "Embassies", + "steps": [ + "Establishing an embassy in other players' Capital city shows the location of their Capital, likewise other players who establish an embassy in your Capital can see location of your Capital.", + "Embassies thus allow Spies to be placed in another players' Capital City if Espionage is enabled.", + "Mutual embassies are required to allow trading open borders and to sign Research Agreement and Defensive Pact.", + "Declaring war on another player or denouncing them will remove embassies from both players' Capitals. You will have to re-establish them to use their effect.", + "Re-establishing an embasy after denounciation from either side becomes available next turn, while re-establishing them when at war requires peacy treaty.", + "Be cautious when accepting embassy from other players because they can spy on your capital, or worse may start planning to attack you.", + "Embassies are available using the Gods and Kings ruleset." + ] + }, + { + "name": "Open Borders", + "civilopediaText": [ + { text: "Open Borders from other player and vice versa allow units to freely pass trough their territoy." }, + { text: "They also let Missionaries to enter other players' territory without suffering attrition." }, + { text: "Open Borders expire depending on game speed and are canceled upon Declaration of War, the closure however takes effect only after war ends and need to be traded again." }, + { text: "You gain diplomatic boost with other AI player if you both have Open Borders to each other" }, + { text: "Be cautious when giving Open Borders because other player may settle a City near your lands in an area you are planning to use, they may also use your territory to attack your friends or City State allies." }, + { text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Open Borders can be traded." }, + { text: "See also: Embassies", "link": "Tutorials/Embassies" } + ] + }, { "name": "Research Agreements", - "steps": [ - "In Research Agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of ⍾Science, which will go towards one of your unresearched technologies.", - "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!", - "Note that before you can invest in a research agreement, you must have a Declaraction of Friendship, both Nations need the required Technology, and both Nations need enough Gold on hand for the agreement." + "civilopediaText": [ + { text: "In Research Agreements, you and another civilization decide to jointly research technology.\nAt the end of the agreement, you will both receive a 'lump sum' of ⍾Science, which will go towards one of your unresearched technologies." }, + { text: "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!" }, + { text: "Note that before you can invest in a research agreement, you must have a Declaraction of Friendship, both Nations need the required Technology, and both Nations need enough Gold on hand for the agreement." }, + { text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Research Agreement can be signed." }, + { text: "See also: Embassies", "link": "Tutorials/Embassies" } ] }, { "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 influence." - ] + "civilopediaText": [ + { text: "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." }, + { text: "Be cautious when signing defensive pacts because they can bring you into wars that you might not want to be in." }, + { text: "The AI is very careful and will not accept defensive pacts with less than 80 influence." }, + { text: "When playing with the Gods and Kings ruleset mutual embassies need to be established before Defensive Pact can be signed." }, + { text: "See also: Embassies", "link": "Tutorials/Embassies" } + ] }, { "name": "City-States", diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 3997691e37..11331d63f4 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -329,6 +329,7 @@ Cities = Technologies = Declarations of war = Peace Proposals = +Accept Embassy = Introduction to [nation] = Declare war on [nation] = Make peace with [nation] = diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index f05aa0d27f..ec1db2ff56 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -51,6 +51,11 @@ object Constants { // Agreements const val openBorders = "Open Borders" + // Other trade items + const val acceptEmbassy = "Accept Embassy" + const val goldPerTurn = "Gold per turn" + const val flatGold = "Gold" + /** 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/DiplomacyAutomation.kt b/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt index b0ad895105..994d2541a9 100644 --- a/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/DiplomacyAutomation.kt @@ -108,20 +108,67 @@ object DiplomacyAutomation { return motivation > 0 } + /** + * Try establishing embassy in other civs' capitals + * + * @param civInfo Civilization which initiates trade + */ + internal fun offerToEstablishEmbassy(civInfo: Civilization) { + val civsThatWeCanEstablishEmbassyWith = civInfo.getKnownCivs().filter { + civInfo.diplomacyFunctions.canEstablishEmbassyWith(it) + && !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy) + && !areWeOfferingTrade(civInfo, it, Constants.acceptEmbassy) + }.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() } + + for (otherCiv in civsThatWeCanEstablishEmbassyWith) { + // Default setting is 3 + if ((1..10).random() < 7) continue + if (wantsToAcceptEmbassy(civInfo, otherCiv)) { + val tradeLogic = TradeLogic(civInfo, otherCiv) + val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = civInfo.gameInfo.speed) + tradeLogic.currentTrade.theirOffers.add(embassyOffer) + + // If possible offer mutual embassies (Civ V behavior) so we don't waste gold + if (otherCiv.diplomacyFunctions.canEstablishEmbassyWith(civInfo)) { + tradeLogic.currentTrade.ourOffers.add(embassyOffer) + } + else { // Otherwise offer GPT (prefered) or flat gold for embassy in their capital + val embassyValue = TradeEvaluation().evaluateBuyCostWithInflation(embassyOffer, civInfo, otherCiv, tradeLogic.currentTrade) + val embassyGptValue = embassyValue / civInfo.gameInfo.speed.dealDuration + val ourGpt = civInfo.stats.statsForNextTurn.gold.toInt() + if (embassyGptValue in 1..ourGpt) + tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.goldPerTurn, TradeOfferType.Gold_Per_Turn, embassyGptValue, civInfo.gameInfo.speed)) + else if (civInfo.gold >= embassyValue && ourGpt >= 0) + tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.flatGold, TradeOfferType.Gold, embassyValue, civInfo.gameInfo.speed)) + // else let them make counter offer + } + + otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) + } + else { + // Remember this for a few turns to save computation power + civInfo.getDiplomacyManager(otherCiv)!!.setFlag(DiplomacyFlags.DeclinedEmbassy, 5) + } + } + } + internal fun offerOpenBorders(civInfo: Civilization) { if (!civInfo.hasUnique(UniqueType.EnablesOpenBorders)) return - val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs() - .filter { - it.isMajorCiv() && !civInfo.isAtWarWith(it) - && it.hasUnique(UniqueType.EnablesOpenBorders) - && !civInfo.getDiplomacyManager(it)!!.hasOpenBorders - && !it.getDiplomacyManager(civInfo)!!.hasOpenBorders - && !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedOpenBorders) - && !areWeOfferingTrade(civInfo, it, Constants.openBorders) - }.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }.toList() + + val civsThatWeCanOpenBordersWith = civInfo.getKnownCivs().filter { + val ourDiploManager = civInfo.getDiplomacyManager(it)!! + it.isMajorCiv() + && !civInfo.isAtWarWith(it) + && it.hasUnique(UniqueType.EnablesOpenBorders) + && !ourDiploManager.hasOpenBorders + && !ourDiploManager.otherCivDiplomacy().hasOpenBorders + && civInfo.diplomacyFunctions.hasMutualEmbassyWith(it) + && !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders) + && !areWeOfferingTrade(civInfo, it, Constants.openBorders) + }.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() } for (otherCiv in civsThatWeCanOpenBordersWith) { - // Default setting is 3, this will be changed according to different civ. + // Default setting is 3 if ((1..10).random() < 7) continue if (wantsToOpenBorders(civInfo, otherCiv)) { val tradeLogic = TradeLogic(civInfo, otherCiv) @@ -136,41 +183,67 @@ object DiplomacyAutomation { } } + /** + * Test if [otherCiv] wants to accept our embassy in their capital + */ + fun wantsToAcceptEmbassy(civInfo: Civilization, otherCiv: Civilization): Boolean { + val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!! + if (civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.DeclinedEmbassy)) return false + if (theirDiploManager.isRelationshipLevelLT(RelationshipLevel.Afraid)) return false + + // Being able to see their capital can give us an advantage later on, especially with espionage enabled + if (!civInfo.getCapital()!!.getCenterTile().isExplored(otherCiv)) return true + + // Did they not discovered our capital yet? + if (!otherCiv.getCapital()!!.getCenterTile().isExplored(civInfo)) { + // If we're afraid of them deny embassy + if (theirDiploManager.relationshipLevel() == RelationshipLevel.Afraid) return false + + // If they're much stronger than us deny embassy + val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force) + val theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force) + val ourAbsoluteAdvantage = ourCombatStrength - theirCombatStrength + val percentageAdvantage = ourAbsoluteAdvantage / theirCombatStrength.toFloat() + if (percentageAdvantage > 0.5) return false + } + + return true // Relationship is Afraid or greater + } + fun wantsToOpenBorders(civInfo: Civilization, otherCiv: Civilization): Boolean { - val diploManager = civInfo.getDiplomacyManager(otherCiv)!! - if (diploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false - if (diploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false + val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! + if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedOpenBorders)) return false + if (ourDiploManager.isRelationshipLevelLT(RelationshipLevel.Favorable)) return false // Don't accept if they are at war with our friends, they might use our land to attack them if (civInfo.diplomacy.values.any { it.isRelationshipLevelGE(RelationshipLevel.Friend) && it.otherCiv().isAtWarWith(otherCiv) }) return false // Being able to see their cities can give us an advantage later on, especially with espionage enabled if (otherCiv.cities.count { !it.getCenterTile().isVisible(civInfo) } < otherCiv.cities.count() * .8f) return true - if (hasAtLeastMotivationToAttack(civInfo, otherCiv, - diploManager.opinionOfOtherCiv() * civInfo.getPersonality().modifierFocus(PersonalityValue.Commerce, .3f) / 2) > 0) + if (hasAtLeastMotivationToAttack(civInfo, otherCiv, + ourDiploManager.opinionOfOtherCiv() * civInfo.getPersonality().modifierFocus(PersonalityValue.Commerce, .3f) / 2) > 0) return false return true } internal fun offerResearchAgreement(civInfo: Civilization) { if (!civInfo.diplomacyFunctions.canSignResearchAgreement()) return // don't waste your time + + val civsThatWeCanSignResearchAgreementWith = civInfo.getKnownCivs().filter { + civInfo.diplomacyFunctions.canSignResearchAgreementWith(it) + && !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement) + && !areWeOfferingTrade(civInfo, it, Constants.researchAgreement) + }.sortedByDescending { it.stats.statsForNextTurn.science } - val canSignResearchAgreementCiv = civInfo.getKnownCivs() - .filter { - civInfo.diplomacyFunctions.canSignResearchAgreementsWith(it) - && !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedResearchAgreement) - && !areWeOfferingTrade(civInfo, it, Constants.researchAgreement) - } - .sortedByDescending { it.stats.statsForNextTurn.science } - - for (otherCiv in canSignResearchAgreementCiv) { + for (otherCiv in civsThatWeCanSignResearchAgreementWith) { // Default setting is 5, this will be changed according to different civ. if ((1..10).random() <= 5 * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .3f)) continue val tradeLogic = TradeLogic(civInfo, otherCiv) val cost = civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv) - tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed)) - tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed)) + val tradeOffer = TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, cost, civInfo.gameInfo.speed) + tradeLogic.currentTrade.ourOffers.add(tradeOffer) + tradeLogic.currentTrade.theirOffers.add(tradeOffer) otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) } } @@ -178,23 +251,24 @@ object DiplomacyAutomation { internal 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)!!.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f) - && !areWeOfferingTrade(civInfo, it, Constants.defensivePact) - } + val civsThatWeCanSignDefensivePactWith = civInfo.getKnownCivs().filter { + val ourDiploManager = civInfo.getDiplomacyManager(it)!! + civInfo.diplomacyFunctions.canSignDefensivePactWith(it) + && !ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact) + && ourDiploManager.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f) + && !areWeOfferingTrade(civInfo, it, Constants.defensivePact) + } - for (otherCiv in canSignDefensivePactCiv) { + for (otherCiv in civsThatWeCanSignDefensivePactWith) { // Default setting is 3, this will be changed according to different civ. if ((1..10).random() <= 7 * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Loyal, .3f)) continue if (wantsToSignDefensivePact(civInfo, otherCiv)) { //todo: Add more in depth evaluation here val tradeLogic = TradeLogic(civInfo, otherCiv) - tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) - tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) + val tradeOffer = TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed) + tradeLogic.currentTrade.ourOffers.add(tradeOffer) + tradeLogic.currentTrade.theirOffers.add(tradeOffer) otherCiv.tradeRequests.add(TradeRequest(civInfo.civName, tradeLogic.currentTrade.reverse())) } else { // Remember this for a few turns to save computation power @@ -204,10 +278,11 @@ object DiplomacyAutomation { } fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean { - val diploManager = civInfo.getDiplomacyManager(otherCiv)!! - if (diploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false - if (diploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false - val commonknownCivs = diploManager.getCommonKnownCivs() + val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! + if (ourDiploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false + if (ourDiploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false + val commonknownCivs = ourDiploManager.getCommonKnownCivs() + for (thirdCiv in commonknownCivs) { // If they have bad relations with any of our friends, don't consider it if (civInfo.getDiplomacyManager(thirdCiv)!!.hasFlag(DiplomacyFlags.DeclarationOfFriendship) @@ -230,11 +305,11 @@ object DiplomacyAutomation { val allAliveCivs = allCivs - deadCivs // We have to already be at RelationshipLevel.Ally, so we must have 80 oppinion of them - var motivation = diploManager.opinionOfOtherCiv() - 80 + var motivation = ourDiploManager.opinionOfOtherCiv() - 80 // Warmongerers don't make good allies - if (diploManager.hasModifier(DiplomaticModifiers.WarMongerer)) { - motivation -= diploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f) + if (ourDiploManager.hasModifier(DiplomaticModifiers.WarMongerer)) { + motivation -= ourDiploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f) } // If they are stronger than us, then we value it a lot more @@ -323,9 +398,10 @@ object DiplomacyAutomation { // pay for peace val tradeLogic = TradeLogic(civInfo, enemy) + val tradeOffer = TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed) - tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) - tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) + tradeLogic.currentTrade.ourOffers.add(tradeOffer) + tradeLogic.currentTrade.theirOffers.add(tradeOffer) if (enemy.isMajorCiv()) { var moneyWeNeedToPay = -TradeEvaluation().evaluatePeaceCostForThem(civInfo, enemy) diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index 5faa2afd11..5f88af641a 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -50,7 +50,8 @@ object NextTurnAutomation { if (civInfo.gameInfo.isReligionEnabled()) { ReligionAutomation.spendFaithOnReligion(civInfo) } - + + DiplomacyAutomation.offerToEstablishEmbassy(civInfo) DiplomacyAutomation.offerOpenBorders(civInfo) DiplomacyAutomation.offerResearchAgreement(civInfo) DiplomacyAutomation.offerDefensivePact(civInfo) diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt index bfb062d677..50c3b2ae83 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DeclareWar.kt @@ -164,6 +164,9 @@ object DeclareWar { diplomacyManager.setFlag(DiplomacyFlags.DeclinedPeace, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't propose peace for 10 turns diplomacyManager.setFlag(DiplomacyFlags.DeclaredWar, diplomacyManager.civInfo.gameInfo.ruleset.modOptions.constants.minimumWarDuration) // AI won't agree to trade for 10 turns diplomacyManager.removeFlag(DiplomacyFlags.BorderConflict) + + // War results in removal of embassies for both sides + diplomacyManager.civInfo.diplomacyFunctions.removeEmbassies(civAtWarWith) } private fun changeOpinions(diplomacyManager: DiplomacyManager, declareWarReason: DeclareWarReason) { diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt index f33a4d32a4..6caf5fc157 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt @@ -87,8 +87,78 @@ class DiplomacyFunctions(val civInfo: Civilization) { } } } - + + /** + * If denounciation happened this turn from either side, establishing embassy again is possible only from next turn. + */ @Readonly + private fun isDenouncedThisTurn(diploManager: DiplomacyManager): Boolean { + return diploManager.getFlag(DiplomacyFlags.Denunciation) == 30 + || diploManager.otherCivDiplomacy().getFlag(DiplomacyFlags.Denunciation) == 30 + } + + /** + * Basic check if we can trade embassies, does not check all prerequisities + * Use [canOfferEmbassyTo] and [canEstablishEmbassyWith] instead + */ + @Readonly + private fun canTradeEmbassies(): Boolean { + return civInfo.isMajorCiv() + && civInfo.hasUnique(UniqueType.EnablesEmbassies) + && civInfo.hasUnique(UniqueType.RequiresEmbassiesForDiplomacy) + } + + /** + * Test if we can offer our embassy to [otherCiv] + */ + @Readonly + fun canOfferEmbassyTo(otherCiv: Civilization): Boolean { + if (!canTradeEmbassies() || !otherCiv.isMajorCiv()) return false + val theirDiploManager = otherCiv.getDiplomacyManager(civInfo)!! + return !civInfo.isAtWarWith(otherCiv) && !isDenouncedThisTurn(theirDiploManager) + && !theirDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy) + && !theirDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies) + } + + /** + * Test if we can establish embassy in [otherCiv] capital + */ + @Readonly + fun canEstablishEmbassyWith(otherCiv: Civilization): Boolean { + if (!canTradeEmbassies() || !otherCiv.isMajorCiv()) return false + val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! + return !civInfo.isAtWarWith(otherCiv) && !isDenouncedThisTurn(ourDiploManager) + && !ourDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy) + && !ourDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies) + } + + /** + * Test if both civs have embassies established in each others' capital + * Returns true if base ruleset or mods don't enable embassies + */ + @Readonly + fun hasMutualEmbassyWith(otherCiv: Civilization): Boolean { + return if (civInfo.hasUnique(UniqueType.EnablesEmbassies) + && civInfo.hasUnique(UniqueType.RequiresEmbassiesForDiplomacy)) + civInfo.getDiplomacyManager(otherCiv)!!.hasModifier(DiplomaticModifiers.SharedEmbassies) + else true // Embassies are not enabled + } + + /** + * Remove mutual embassies from both civs + */ + fun removeEmbassies(otherCiv: Civilization) { + val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! + ourDiploManager.removeModifier(DiplomaticModifiers.EstablishedEmbassy) + ourDiploManager.removeModifier(DiplomaticModifiers.ReceivedEmbassy) + ourDiploManager.removeModifier(DiplomaticModifiers.SharedEmbassies) + + val theirDiploManager = ourDiploManager.otherCivDiplomacy() + theirDiploManager.removeModifier(DiplomaticModifiers.EstablishedEmbassy) + theirDiploManager.removeModifier(DiplomaticModifiers.ReceivedEmbassy) + theirDiploManager.removeModifier(DiplomaticModifiers.SharedEmbassies) + } + fun canSignDeclarationOfFriendshipWith(otherCiv: Civilization): Boolean { return otherCiv.isMajorCiv() && !otherCiv.isAtWarWith(civInfo) && !civInfo.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation) @@ -105,15 +175,17 @@ class DiplomacyFunctions(val civInfo: Civilization) { @Readonly fun canSignResearchAgreementNoCostWith (otherCiv: Civilization): Boolean { - val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! - return canSignResearchAgreement() && otherCiv.diplomacyFunctions.canSignResearchAgreement() - && diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) - && !diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement) - && !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement) + val ourDiploManager = civInfo.getDiplomacyManager(otherCiv)!! + return canSignResearchAgreement() + && otherCiv.diplomacyFunctions.canSignResearchAgreement() + && hasMutualEmbassyWith(otherCiv) + && ourDiploManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) + && !ourDiploManager.hasFlag(DiplomacyFlags.ResearchAgreement) + && !ourDiploManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement) } @Readonly - fun canSignResearchAgreementsWith(otherCiv: Civilization): Boolean { + fun canSignResearchAgreementWith(otherCiv: Civilization): Boolean { val cost = getResearchAgreementCost(otherCiv) return canSignResearchAgreementNoCostWith(otherCiv) && civInfo.gold >= cost && otherCiv.gold >= cost @@ -136,16 +208,16 @@ class DiplomacyFunctions(val civInfo: Civilization) { @Readonly fun canSignDefensivePactWith(otherCiv: Civilization): Boolean { - val diplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! - return canSignDefensivePact() && otherCiv.diplomacyFunctions.canSignDefensivePact() - && (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) - || diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DeclarationOfFriendship)) - && !diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact) - && !diplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact) - && diplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact + val ourDiplomacyManager = civInfo.getDiplomacyManager(otherCiv)!! + return canSignDefensivePact() + && otherCiv.diplomacyFunctions.canSignDefensivePact() + && hasMutualEmbassyWith(otherCiv) + && ourDiplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship) + && !ourDiplomacyManager.hasFlag(DiplomacyFlags.DefensivePact) + && !ourDiplomacyManager.otherCivDiplomacy().hasFlag(DiplomacyFlags.DefensivePact) + && ourDiplomacyManager.diplomaticStatus != DiplomaticStatus.DefensivePact } - /** * @returns whether units of this civilization can pass through the tiles owned by [otherCiv], * considering only civ-wide filters. @@ -167,7 +239,4 @@ class DiplomacyFunctions(val civInfo: Civilization) { if (!civInfo.isAIOrAutoPlaying() && otherCiv.isCityState) return true return false } - - - } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 94ea52db6c..058c7cad6a 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -46,6 +46,7 @@ enum class DiplomacyFlags { DeclinedLuxExchange, DeclinedPeace, DeclinedResearchAgreement, + DeclinedEmbassy, DeclinedOpenBorders, DeclaredWar, DeclarationOfFriendship, @@ -118,6 +119,9 @@ enum class DiplomaticModifiers(val text: String) { StoleOurAlly("You took the alliance we had with a City-State"), // Positive + EstablishedEmbassy("We have an embassy in your capital"), + ReceivedEmbassy("You have an embassy in our capital"), + SharedEmbassies("We have shared embassies"), YearsOfPeace("Years of peace have strengthened our relations."), SharedEnemy("Our mutual military struggle brings us closer together."), LiberatedCity("We applaud your liberation of conquered cities!"), @@ -569,6 +573,11 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { internal fun removeModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.remove(modifier.name) @Readonly fun hasModifier(modifier: DiplomaticModifiers) = diplomaticModifiers.containsKey(modifier.name) + + fun replaceModifier(oldModifier: DiplomaticModifiers, newModifier: DiplomaticModifiers, amount: Float) { + removeModifier(oldModifier) + addModifier(newModifier, amount) + } fun signDeclarationOfFriendship() { setModifier(DiplomaticModifiers.DeclarationOfFriendship, 35f) @@ -679,6 +688,9 @@ class DiplomacyManager() : IsPartOfGameInfoSerialization { setFlag(DiplomacyFlags.Denunciation, 30) otherCivDiplomacy().setFlag(DiplomacyFlags.Denunciation, 30) + // Denounciation results in removal of embasies for both sides + civInfo.diplomacyFunctions.removeEmbassies(otherCiv()) + otherCiv().addNotification("[${civInfo.civName}] has denounced us!", NotificationCategory.Diplomacy, NotificationIcon.Diplomacy, civInfo.civName) diff --git a/core/src/com/unciv/logic/trade/Trade.kt b/core/src/com/unciv/logic/trade/Trade.kt index 353443ed0e..c6b828a820 100644 --- a/core/src/com/unciv/logic/trade/Trade.kt +++ b/core/src/com/unciv/logic/trade/Trade.kt @@ -63,6 +63,9 @@ class TradeRequest : IsPartOfGameInfoSerialization { requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10) if (trade.ourOffers.any { it.name == Constants.openBorders }) requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedOpenBorders, if (decliningCiv.isAI()) 5 else 10) + if (trade.ourOffers.any { it.name == Constants.acceptEmbassy } + || trade.theirOffers.any { it.name == Constants.acceptEmbassy }) + requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedEmbassy, if (decliningCiv.isAI()) 5 else 10) if (trade.theirOffers.any { it.type == TradeOfferType.WarDeclaration }) requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedJoinWarOffer, if (decliningCiv.isAI()) 5 else 10) if (trade.ourOffers.any { it.type == TradeOfferType.WarDeclaration }) diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index ef7d1a1aaf..0ee9176763 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -7,6 +7,7 @@ import com.unciv.logic.automation.civilization.DeclareWarPlanEvaluator 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.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.map.tile.Tile import com.unciv.models.ruleset.Ruleset @@ -30,6 +31,23 @@ class TradeEvaluation { || (tradePartner.hasEverOwnedOriginalCapital && trade.theirOffers.count { it.type == TradeOfferType.City } == tradePartner.cities.size)) { return false } + + // No way to tell who offers what in isOfferValid() + val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = offerer.gameInfo.speed) + val theirDiploManager = tradePartner.getDiplomacyManager(offerer)!! + val ourDiploManager = offerer.getDiplomacyManager(tradePartner)!! + + if (trade.ourOffers.contains(embassyOffer) + && (offerer.getCapital() == null + || theirDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy) + || theirDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies))) + return false + + if (trade.theirOffers.contains(embassyOffer) + && (tradePartner.getCapital() == null + || ourDiploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy) + || ourDiploManager.hasModifier(DiplomaticModifiers.SharedEmbassies))) + return false for (offer in trade.ourOffers) if (!isOfferValid(offer, offerer, tradePartner)) { @@ -51,6 +69,7 @@ class TradeEvaluation { } return when (tradeOffer.type) { + TradeOfferType.Embassy -> true // Already checked // if they go a little negative it's okay, but don't allowing going overboard (promising same gold to many) TradeOfferType.Gold -> tradeOffer.amount * 0.9f < offerer.gold TradeOfferType.Gold_Per_Turn -> tradeOffer.amount * 0.9f < offerer.stats.statsForNextTurn.gold @@ -122,6 +141,7 @@ class TradeEvaluation { */ private fun evaluateBuyCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int { when (offer.type) { + TradeOfferType.Embassy -> return (30 * civInfo.gameInfo.speed.goldCostModifier).toInt() TradeOfferType.Gold -> return offer.amount // GPT loses value for each 'future' turn, meaning: gold now is more valuable than gold in the future // Empire-wide production tends to grow at roughly 2% per turn (quick speed), so let's take that as a base line @@ -259,11 +279,9 @@ class TradeEvaluation { */ fun isPeaceProposalEnabled(thirdCiv: Civilization, civInfo: Civilization): Boolean { val diploManager = civInfo.getDiplomacyManager(thirdCiv)!! - val warCountDown = if (diploManager.hasFlag(DiplomacyFlags.DeclaredWar)) - diploManager.getFlag(DiplomacyFlags.DeclaredWar) else 0 // On standard speed 10 turns must pass before peace can be proposed - if (warCountDown > 0) return false + if (diploManager.getFlag(DiplomacyFlags.DeclaredWar) > 0) return false // TODO: We don't know if other human player would agree to peace if (thirdCiv.isHuman()) return false @@ -322,6 +340,13 @@ class TradeEvaluation { */ private fun evaluateSellCost(offer: TradeOffer, civInfo: Civilization, tradePartner: Civilization, trade: Trade): Int { when (offer.type) { + TradeOfferType.Embassy -> { + val tradePartnerDiplo = civInfo.getDiplomacyManager(tradePartner)!! + if (tradePartnerDiplo.isRelationshipLevelLE(RelationshipLevel.Enemy)) return Int.MIN_VALUE + else if (tradePartnerDiplo.isRelationshipLevelLE(RelationshipLevel.Competitor)) + return (60 * civInfo.gameInfo.speed.goldCostModifier).toInt() + return (30 * civInfo.gameInfo.speed.goldCostModifier).toInt() // 30 is Civ V default (on standard only?) + } TradeOfferType.Gold -> return offer.amount TradeOfferType.Gold_Per_Turn -> return offer.amount * offer.duration TradeOfferType.Treaty -> { diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 8da0e08935..ae425f9e72 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -7,6 +7,8 @@ import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.PopupAlert import com.unciv.logic.civilization.diplomacy.DeclareWarReason import com.unciv.logic.civilization.diplomacy.DiplomacyFlags +import com.unciv.logic.civilization.diplomacy.DiplomacyManager +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 @@ -14,28 +16,43 @@ import com.unciv.models.ruleset.unique.UniqueType class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civilization) { /** Contains everything we could offer the other player, whether we've actually offered it or not */ - val ourAvailableOffers = getAvailableOffers(ourCivilization, otherCivilization) - val theirAvailableOffers = getAvailableOffers(otherCivilization, ourCivilization) + var ourAvailableOffers = TradeOffersList() + var theirAvailableOffers = TradeOffersList() val currentTrade = Trade() - private fun getAvailableOffers(civInfo: Civilization, otherCivilization: Civilization): TradeOffersList { - val offers = TradeOffersList() - if (civInfo.isCityState && otherCivilization.isCityState) return offers - if (civInfo.isAtWarWith(otherCivilization)) - offers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) + init { + // Embassy trade availability depends solely on our ability to trade it + val embassyOffer = TradeOffer(Constants.acceptEmbassy, TradeOfferType.Embassy, speed = ourCivilization.gameInfo.speed) + if (ourCivilization.diplomacyFunctions.canEstablishEmbassyWith(otherCivilization)) + theirAvailableOffers.add(embassyOffer) - if (!otherCivilization.getDiplomacyManager(civInfo)!!.hasOpenBorders - && !otherCivilization.isCityState - && civInfo.hasUnique(UniqueType.EnablesOpenBorders) - && otherCivilization.hasUnique(UniqueType.EnablesOpenBorders)) { + if (ourCivilization.diplomacyFunctions.canOfferEmbassyTo(otherCivilization)) + ourAvailableOffers.add(embassyOffer) + + // Other trade items are added as usual for both sides + ourAvailableOffers += getAvailableOffers(ourCivilization, otherCivilization) + theirAvailableOffers += getAvailableOffers(otherCivilization, ourCivilization) + } + + private fun getAvailableOffers(civInfo: Civilization, otherCiv: Civilization): TradeOffersList { + val offers = TradeOffersList() + if (civInfo.isCityState || otherCiv.isCityState) return offers + + if (civInfo.isAtWarWith(otherCiv)) + offers.add(TradeOffer(Constants.peaceTreaty, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) + + if (civInfo.diplomacyFunctions.hasMutualEmbassyWith(otherCiv) + && !otherCiv.getDiplomacyManager(civInfo)!!.hasOpenBorders + && civInfo.hasUnique(UniqueType.EnablesOpenBorders) + && otherCiv.hasUnique(UniqueType.EnablesOpenBorders)) { offers.add(TradeOffer(Constants.openBorders, TradeOfferType.Agreement, speed = civInfo.gameInfo.speed)) } - if (civInfo.diplomacyFunctions.canSignResearchAgreementNoCostWith(otherCivilization)) + if (civInfo.diplomacyFunctions.canSignResearchAgreementNoCostWith(otherCiv)) offers.add(TradeOffer(Constants.researchAgreement, TradeOfferType.Treaty, - civInfo.diplomacyFunctions.getResearchAgreementCost(otherCivilization), civInfo.gameInfo.speed)) + civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv), civInfo.gameInfo.speed)) - if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCivilization)) + if (civInfo.diplomacyFunctions.canSignDefensivePactWith(otherCiv)) offers.add(TradeOffer(Constants.defensivePact, TradeOfferType.Treaty, speed = civInfo.gameInfo.speed)) for (entry in civInfo.getPerTurnResourcesWithOriginsForTrade() @@ -51,48 +68,43 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil offers.add(TradeOffer(entry.resource.name, TradeOfferType.Stockpiled_Resource, entry.amount, speed = civInfo.gameInfo.speed)) } - offers.add(TradeOffer("Gold", TradeOfferType.Gold, civInfo.gold, speed = civInfo.gameInfo.speed)) - offers.add(TradeOffer("Gold per turn", TradeOfferType.Gold_Per_Turn, civInfo.stats.statsForNextTurn.gold.toInt(), civInfo.gameInfo.speed)) + offers.add(TradeOffer(Constants.flatGold, TradeOfferType.Gold, civInfo.gold, speed = civInfo.gameInfo.speed)) + offers.add(TradeOffer(Constants.goldPerTurn, TradeOfferType.Gold_Per_Turn, civInfo.stats.statsForNextTurn.gold.toInt(), civInfo.gameInfo.speed)) - if (!civInfo.isOneCityChallenger() && !otherCivilization.isOneCityChallenger() - && !civInfo.isCityState && !otherCivilization.isCityState - ) { + if (!civInfo.isOneCityChallenger() && !otherCiv.isOneCityChallenger()) for (city in civInfo.cities.filterNot { it.isCapital() || it.isInResistance() }) offers.add(TradeOffer(city.id, TradeOfferType.City, speed = civInfo.gameInfo.speed)) - } val otherCivsWeKnow = civInfo.getKnownCivs() - .filter { it.civName != otherCivilization.civName && it.isMajorCiv() && !it.isDefeated() } + .filter { it.civName != otherCiv.civName && it.isMajorCiv() && !it.isDefeated() } if (civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.TradeCivIntroductions)) { val civsWeKnowAndTheyDont = otherCivsWeKnow - .filter { !otherCivilization.diplomacy.containsKey(it.civName) && !it.isDefeated() } + .filter { !otherCiv.diplomacy.containsKey(it.civName) && !it.isDefeated() } for (thirdCiv in civsWeKnowAndTheyDont) { offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.Introduction, speed = civInfo.gameInfo.speed)) } } - if (!civInfo.isCityState && !otherCivilization.isCityState - && !civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) { + if (!civInfo.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange)) { val civsWeBothKnow = otherCivsWeKnow - .filter { otherCivilization.diplomacy.containsKey(it.civName) } + .filter { otherCiv.diplomacy.containsKey(it.civName) } val civsWeArentAtWarWith = civsWeBothKnow .filter { civInfo.getDiplomacyManager(it)!!.canDeclareWar() } for (thirdCiv in civsWeArentAtWarWith) { offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.WarDeclaration, speed = civInfo.gameInfo.speed)) } } + + val thirdCivsAtWarTheyKnow = otherCiv.getKnownCivs().filter { + it.isAtWarWith(civInfo) && !it.isDefeated() + && !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange) + } - if (!civInfo.isCityState && !otherCivilization.isCityState) { - val thirdCivsAtWarTheyKnow = otherCivilization.getKnownCivs() - .filter { it.isAtWarWith(civInfo) && !it.isDefeated() - && !it.gameInfo.ruleset.modOptions.hasUnique(UniqueType.DiplomaticRelationshipsCannotChange) } - - for (thirdCiv in thirdCivsAtWarTheyKnow) { - // Setting amount to 0 makes TradeOffer.isTradable() return false and also disables the button in trade window - val amount = if (TradeEvaluation().isPeaceProposalEnabled(thirdCiv, civInfo)) 1 else 0 - offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed)) - } + for (thirdCiv in thirdCivsAtWarTheyKnow) { + // Setting amount to 0 makes TradeOffer.isTradable() return false and also disables the button in trade window + val amount = if (TradeEvaluation().isPeaceProposalEnabled(thirdCiv, civInfo)) 1 else 0 + offers.add(TradeOffer(thirdCiv.civName, TradeOfferType.PeaceProposal, amount, civInfo.gameInfo.speed)) } return offers @@ -100,13 +112,13 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil fun acceptTrade(applyGifts: Boolean = true) { val ourDiploManager = ourCivilization.getDiplomacyManager(otherCivilization)!! - val theirDiploManger = otherCivilization.getDiplomacyManager(ourCivilization)!! + val theirDiploManager = otherCivilization.getDiplomacyManager(ourCivilization)!! ourDiploManager.apply { trades.add(currentTrade) updateHasOpenBorders() } - theirDiploManger.apply { + theirDiploManager.apply { trades.add(currentTrade.reverse()) updateHasOpenBorders() } @@ -114,6 +126,10 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil // instant transfers fun transferTrade(from: Civilization, to: Civilization, offer: TradeOffer) { when (offer.type) { + TradeOfferType.Embassy -> { + for (tile in from.getCapital()!!.getCenterTile().getTilesInDistance(2)) + tile.setExplored(to, true) + } TradeOfferType.Gold -> { to.addGold(offer.amount) from.addGold(-offer.amount) @@ -204,7 +220,7 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil ourDiploManager.giftGold(ourGoldValueOfTrade - theirGoldValueOfTrade.coerceAtLeast(0), isPureGift) } else if (theirGoldValueOfTrade > ourGoldValueOfTrade) { val isPureGift = currentTrade.theirOffers.isEmpty() - theirDiploManger.giftGold(theirGoldValueOfTrade - ourGoldValueOfTrade.coerceAtLeast(0), isPureGift) + theirDiploManager.giftGold(theirGoldValueOfTrade - ourGoldValueOfTrade.coerceAtLeast(0), isPureGift) } } @@ -218,6 +234,23 @@ class TradeLogic(val ourCivilization: Civilization, val otherCivilization: Civil for (offer in currentTrade.theirOffers.filter { it.type == TradeOfferType.Treaty }) transferTrade(otherCivilization, ourCivilization, offer) + fun applyEmbassyOffer(diploManager: DiplomacyManager) { + if (diploManager.hasModifier(DiplomaticModifiers.EstablishedEmbassy)) { + diploManager.replaceModifier(DiplomaticModifiers.EstablishedEmbassy, DiplomaticModifiers.SharedEmbassies, 3f) + diploManager.otherCivDiplomacy().replaceModifier(DiplomaticModifiers.ReceivedEmbassy, DiplomaticModifiers.SharedEmbassies, 3f) + } + else { + diploManager.addModifier(DiplomaticModifiers.ReceivedEmbassy, 1f) + diploManager.otherCivDiplomacy().addModifier(DiplomaticModifiers.EstablishedEmbassy, 2f) + } + } + + // Diplomatic modifiers for embassy depend on whether a civ gives its embassy, accepts it or both + if (currentTrade.ourOffers.any { it.type == TradeOfferType.Embassy }) + applyEmbassyOffer(ourDiploManager) + if (currentTrade.theirOffers.any { it.type == TradeOfferType.Embassy }) + applyEmbassyOffer(theirDiploManager) + ourCivilization.cache.updateCivResources() ourCivilization.updateStatsForNextTurn() diff --git a/core/src/com/unciv/logic/trade/TradeOfferType.kt b/core/src/com/unciv/logic/trade/TradeOfferType.kt index 61bdd57422..f1ffd88022 100644 --- a/core/src/com/unciv/logic/trade/TradeOfferType.kt +++ b/core/src/com/unciv/logic/trade/TradeOfferType.kt @@ -6,6 +6,7 @@ package com.unciv.logic.trade */ @Suppress("EnumEntryName") // We do want the underscores in our names enum class TradeOfferType(val numberType: TradeTypeNumberType, val isImmediate: Boolean) { + Embassy (TradeTypeNumberType.None, true), Gold (TradeTypeNumberType.Gold, true), Gold_Per_Turn (TradeTypeNumberType.Gold, false), /** Treaties are shared by both sides - like peace treaty and defensive pact */ diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index d9bd01ddd1..0e9f040b28 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -215,6 +215,10 @@ enum class UniqueType( @Deprecated("As of 4.16.18", ReplaceWith("[+100]% [resource] resource production")) DoubleResourceProduced("Double quantity of [resource] produced", UniqueTarget.Global), + /// Diplomacy + EnablesEmbassies("Enables establishment of embassies", UniqueTarget.Tech), + RequiresEmbassiesForDiplomacy("Requires establishing embassies to conduct advanced diplomacy", UniqueTarget.Global), + /// Agreements EnablesOpenBorders("Enables Open Borders agreements", UniqueTarget.Global), // Should the 'R' in 'Research agreements' be capitalized? diff --git a/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt b/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt index 2bd7cc7eff..4ce163a434 100644 --- a/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt +++ b/core/src/com/unciv/ui/screens/diplomacyscreen/OffersListScroll.kt @@ -57,7 +57,7 @@ class OffersListScroll( for (offerType in TradeOfferType.entries) { val labelName = when(offerType) { - Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> "" + Embassy, Gold, Gold_Per_Turn, Treaty, Agreement, Introduction -> "" Luxury_Resource -> "Luxury resources" Strategic_Resource -> "Strategic resources" Stockpiled_Resource -> "Stockpiled resources" @@ -89,6 +89,7 @@ class OffersListScroll( for (offer in offersOfType) { val tradeLabel = offer.getOfferText(untradableOffers.sumBy(offer.name)) val tradeIcon = when (offer.type) { + Embassy -> ImageGetter.getImage("OtherIcons/Star") Luxury_Resource, Strategic_Resource -> ImageGetter.getResourcePortrait(offer.name, 30f) WarDeclaration, PeaceProposal -> diff --git a/docs/Modders/uniques.md b/docs/Modders/uniques.md index 106dc995fd..a3c7fe6cde 100644 --- a/docs/Modders/uniques.md +++ b/docs/Modders/uniques.md @@ -808,6 +808,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Global +??? example "Requires establishing embassies to conduct advanced diplomacy" + Applicable to: Global + ??? example "Enables Open Borders agreements" Applicable to: Global @@ -1148,6 +1151,9 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl Applicable to: Era ## Tech uniques +??? example "Enables establishment of embassies" + Applicable to: Tech + ??? example "Starting tech" Applicable to: Tech